"""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