160 lines
5.4 KiB
Python
160 lines
5.4 KiB
Python
"""Generator 公共接口契约测试(技术债计划 P2-⑩)。
|
||
|
||
BaseMoldGenerator 的公共契约原先以 `_` 私有方法形式暴露给 MultiSchemeMoldPlanner,
|
||
本文件锁定提取后的公共接口:方法存在性、参数覆写行为、废弃入口告警,
|
||
并用真实几何(OCC box)走一遍 generate_plan 集成路径。
|
||
|
||
OCC(pythonocc)仅 conda 环境提供(本地 gemold / 服务器 py_3.12),
|
||
无 OCC 的环境自动跳过本文件,不影响其余测试。
|
||
"""
|
||
|
||
import pytest
|
||
|
||
pytest.importorskip("OCC.Core.BRepPrimAPI", reason="需要 pythonocc 运行生成器契约测试")
|
||
|
||
from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox
|
||
from OCC.Core.gp import gp_Pnt
|
||
|
||
from moldinsight.core.aluminum_foam_mold import AluminumFoamMoldGenerator
|
||
from moldinsight.core.base_mold_generator import BaseMoldGenerator
|
||
from moldinsight.core.mold_generator import MoldCavityGenerator
|
||
from moldinsight.core.multi_scheme_planner import MultiSchemeMoldPlanner
|
||
from moldinsight.services.material_service import MaterialService
|
||
|
||
# BaseMoldGenerator 上的公共契约(含子类必须可调用的基类默认实现)
|
||
BASE_CONTRACT_METHODS = (
|
||
"analyze_product_geometry",
|
||
"apply_shrinkage_compensation",
|
||
"apply_draft_angles",
|
||
"split_cavity_core",
|
||
"create_mold_block",
|
||
"split_mold_block_by_plane",
|
||
"get_parting_plane",
|
||
"get_face_normal",
|
||
"calculate_parting_line",
|
||
"apply_process_params",
|
||
)
|
||
|
||
# 两个子类共同实现的完整契约
|
||
SUBCLASS_CONTRACT_METHODS = BASE_CONTRACT_METHODS + (
|
||
"calculate_mold_size",
|
||
"build_undercut_regions",
|
||
"set_material",
|
||
"side_action_designer",
|
||
"generate_detailed_cavity_json",
|
||
"generate_cavity_key_info",
|
||
"generate_mold_cavities", # 已废弃但保留兼容
|
||
)
|
||
|
||
|
||
def _box(dx=50.0, dy=40.0, dz=30.0):
|
||
return BRepPrimAPI_MakeBox(
|
||
gp_Pnt(-dx / 2, -dy / 2, -dz / 2), gp_Pnt(dx / 2, dy / 2, dz / 2)
|
||
).Shape()
|
||
|
||
|
||
@pytest.mark.parametrize("method", SUBCLASS_CONTRACT_METHODS)
|
||
@pytest.mark.parametrize(
|
||
"generator_cls", [MoldCavityGenerator, AluminumFoamMoldGenerator]
|
||
)
|
||
def test_generator_exposes_public_contract(generator_cls, method):
|
||
generator = generator_cls()
|
||
attr = getattr(generator, method, None)
|
||
assert attr is not None, f"{generator_cls.__name__} 缺少公共接口 {method}"
|
||
assert callable(attr) or isinstance(attr, object)
|
||
|
||
|
||
def test_foam_generator_implements_generate_mold_block():
|
||
assert callable(AluminumFoamMoldGenerator().generate_mold_block)
|
||
|
||
|
||
def test_base_generate_mold_block_is_not_implemented():
|
||
with pytest.raises(NotImplementedError):
|
||
BaseMoldGenerator().generate_mold_block(None, {})
|
||
|
||
|
||
def test_apply_process_params_overrides_generator_state():
|
||
generator = MoldCavityGenerator(shrinkage_rate=0.005, draft_angle=2.0)
|
||
material = MaterialService.get_material("ABS")
|
||
|
||
generator.apply_process_params(
|
||
material,
|
||
{
|
||
"draft_angle": 1.5,
|
||
"shrinkage_rate": 1.2, # 百分数
|
||
"parting_precision": 0.05,
|
||
"cavity_match": 97.0,
|
||
},
|
||
)
|
||
|
||
assert generator.draft_angle == pytest.approx(1.5)
|
||
assert generator.shrinkage_rate == pytest.approx(0.012)
|
||
assert generator.parting_line_tolerance == pytest.approx(0.05)
|
||
assert generator.cavity_match_rate == pytest.approx(97.0)
|
||
|
||
|
||
def test_apply_process_params_defaults_to_material_shrinkage():
|
||
generator = MoldCavityGenerator(shrinkage_rate=0.005, draft_angle=2.0)
|
||
material = MaterialService.get_material("PP") # shrinkage 0.016
|
||
|
||
generator.apply_process_params(material, None)
|
||
|
||
assert generator.shrinkage_rate == pytest.approx(0.016)
|
||
assert generator.draft_angle == pytest.approx(2.0)
|
||
|
||
|
||
def test_generate_mold_cavities_is_deprecated():
|
||
generator = MoldCavityGenerator()
|
||
|
||
with pytest.warns(DeprecationWarning):
|
||
result = generator.generate_mold_cavities(_box())
|
||
|
||
assert result["cavity"] is not None
|
||
assert result["core"] is not None
|
||
|
||
|
||
def test_planner_generate_plan_injection_box():
|
||
planner = MultiSchemeMoldPlanner()
|
||
|
||
plan = planner.generate_plan(
|
||
_box(),
|
||
MaterialService.get_material("ABS"),
|
||
is_foam_material=False,
|
||
max_schemes=2,
|
||
)
|
||
|
||
schemes = plan["candidate_schemes"]
|
||
assert 1 <= len(schemes) <= 2
|
||
assert plan["best_scheme_id"] == schemes[0]["scheme_id"]
|
||
|
||
best = schemes[0]
|
||
assert best["cavity_data"]["mold_cavities"]["cavity"]["vertex_count"] > 0
|
||
assert best["cavity_data"]["mold_cavities"]["core"]["vertex_count"] > 0
|
||
assert best["cavity_data"]["metadata"]["scheme_id"] == best["scheme_id"]
|
||
assert best["parting"]["line"]
|
||
|
||
export_shapes = plan["_export_shapes"][best["scheme_id"]]
|
||
assert export_shapes["cavity"] is not None
|
||
assert export_shapes["product"] is not None
|
||
|
||
|
||
def test_planner_generate_plan_foam_box():
|
||
planner = MultiSchemeMoldPlanner()
|
||
|
||
plan = planner.generate_plan(
|
||
_box(120, 100, 60),
|
||
MaterialService.get_material("AlSi10Mg"),
|
||
is_foam_material=True,
|
||
max_schemes=1,
|
||
)
|
||
|
||
best = plan["candidate_schemes"][0]
|
||
assert plan["best_scheme_id"] == best["scheme_id"]
|
||
# 泡沫材料优先 Z 轴上下开模
|
||
assert best["axis"] == "Z"
|
||
assert best["cavity_data"]["metadata"]["foam_material"] == "AlSi10Mg"
|
||
assert best["cavity_data"]["mold_cavities"]["cavity"]["vertex_count"] > 0
|
||
|
||
export_shapes = plan["_export_shapes"][best["scheme_id"]]
|
||
assert export_shapes["cavity"] is not None
|