Files
geMoldInsight/tests/test_mold_generator_contract.py
T
2026-09-01 18:05:18 +08:00

160 lines
5.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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