from __future__ import annotations import copy import importlib.util import json import tempfile import unittest from pathlib import Path ROOT = Path(__file__).resolve().parents[1] SCRIPT = ROOT / "scripts" / "designir_pipeline.py" SPEC = importlib.util.spec_from_file_location("designir_pipeline", SCRIPT) MODULE = importlib.util.module_from_spec(SPEC) assert SPEC.loader SPEC.loader.exec_module(MODULE) FEATURE_TREE_SCRIPT = ROOT / "scripts" / "feature_tree.py" FEATURE_TREE_SPEC = importlib.util.spec_from_file_location( "feature_tree", FEATURE_TREE_SCRIPT ) FEATURE_TREE = importlib.util.module_from_spec(FEATURE_TREE_SPEC) assert FEATURE_TREE_SPEC.loader FEATURE_TREE_SPEC.loader.exec_module(FEATURE_TREE) class DesignIRPipelineTests(unittest.TestCase): def payload(self) -> dict: return json.loads( (ROOT / "examples" / "raised_hub_flange.designir.json").read_text() ) def test_example_is_valid_and_compiles(self) -> None: payload = MODULE.validate_designir(self.payload()) with tempfile.TemporaryDirectory() as temporary: output = Path(temporary) / "flange.step" result = MODULE.compile_designir(payload, output) self.assertTrue(output.is_file()) self.assertEqual(1, result["facts"]["solid_count"]) self.assertGreater(result["facts"]["volume_mm3"], 30_000) def test_legacy_model_migrates_to_semantic_designir_3(self) -> None: migrated = MODULE.migrate_designir_2_to_3(self.payload()) self.assertEqual("3.0", migrated["schema_version"]) self.assertEqual( "fully_semantic_parametric", migrated["reconstruction_mode"] ) self.assertIn( "outer_radius", migrated["edit_interface"]["semantic_parameters"] ) with tempfile.TemporaryDirectory() as temporary: output = Path(temporary) / "flange.step" result = MODULE.compile_designir(migrated, output) self.assertEqual(1, result["facts"]["solid_count"]) def test_generated_step_materializes_hybrid_surface_snapshot(self) -> None: migrated = MODULE.migrate_designir_2_to_3(self.payload()) with tempfile.TemporaryDirectory() as temporary: output = Path(temporary) / "flange.step" MODULE.compile_designir(migrated, output) hybrid = MODULE.materialize_hybrid_designir(migrated, output) self.assertEqual( "hybrid_semantic_surface_parametric", hybrid["reconstruction_mode"], ) self.assertTrue(hybrid["surface_layer"]["solids"]) self.assertTrue( hybrid["compiled_surface_provenance"][ "semantic_layer_authoritative" ] ) self.assertTrue(MODULE.anti_cheat(hybrid)["valid"]) rebuilt = Path(temporary) / "hybrid-rebuilt.step" MODULE.compile_designir(hybrid, rebuilt) self.assertTrue(rebuilt.is_file()) def test_feature_tree_is_derived_from_semantic_designir(self) -> None: migrated = MODULE.migrate_designir_2_to_3(self.payload()) tree = FEATURE_TREE.generate_feature_tree( migrated, designir_path="flange.designir.json", step_path="flange.step", backend="build123d", compiled=True, validated=True, ) self.assertEqual("solidworks_feature_manager", tree["tree_kind"]) self.assertEqual("1.0", tree["schema_version"]) serialized = json.dumps(tree) self.assertNotIn('"designir_3"', serialized) self.assertNotIn('"source_designir_feature"', serialized) self.assertNotIn('"manufacturing"', serialized) self.assertEqual( "authored_semantic", tree["source"]["history_kind"], ) self.assertEqual( "valid", tree["validation"]["structure"]["status"], ) nodes = { node["id"]: node for node in tree["feature_manager"]["nodes"] } self.assertIn("sw:front_plane", nodes) self.assertIn("sketch:base_flange", nodes) self.assertIn("feature:base_flange", nodes) self.assertIn("feature:central_bore", nodes) self.assertEqual( "BossExtrude", nodes["feature:base_flange"]["solidworks_type"], ) self.assertEqual( "BossExtrude", nodes["feature:base_flange"]["definition"]["feature_type"], ) self.assertTrue(nodes["feature:base_flange"]["dimensions"]) self.assertIn( "feature:central_bore", nodes["feature:base_flange"]["references"]["child_features"], ) self.assertEqual( "CutExtrude", nodes["feature:central_bore"]["solidworks_type"], ) self.assertEqual( "ThroughAll", nodes["feature:central_bore"]["definition"]["end_condition"], ) self.assertEqual( [], nodes["feature:central_bore"]["selection_sets"]["selected_edges"], ) self.assertFalse(nodes["feature:central_bore"]["rebuild"]["suppressed"]) self.assertIn( "凸台-拉伸", nodes["feature:base_flange"]["display_name"], ) self.assertEqual( ["bore_diameter"], [ parameter["binding_id"] for parameter in nodes["feature:central_bore"]["parameters"] ], ) replay_designir = FEATURE_TREE.designir_from_feature_tree(tree) self.assertEqual("3.0", replay_designir["schema_version"]) self.assertEqual( "feature_manager_tree_replay", replay_designir["authoring_mode"], ) with tempfile.TemporaryDirectory() as temporary: original_step = Path(temporary) / "original.step" replay_step = Path(temporary) / "replayed.step" original = MODULE.compile_designir(migrated, original_step) result = FEATURE_TREE.compile_feature_tree(tree, replay_step) self.assertTrue(replay_step.is_file()) self.assertEqual("feature_manager_native", result["replay_kind"]) for key in ("solid_count", "face_count", "edge_count"): self.assertEqual(original["facts"][key], result["facts"][key]) self.assertEqual(original["facts"]["size_mm"], result["facts"]["size_mm"]) self.assertAlmostEqual( original["facts"]["volume_mm3"], result["facts"]["volume_mm3"], places=7, ) def test_llm_flange_variant_normalizes_and_compiles(self) -> None: payload = { "designir_kind": "independent_parametric_cad", "reconstruction_mode": "fully_semantic_parametric", "authoring_mode": "semantic_feature_program", "semantic_layer": { "reconstruction_status": "ready", "coordinate_system": { "origin": [0, 0, 0], "x_axis": [1, 0, 0], "y_axis": [0, 1, 0], "z_axis": [0, 0, 1], }, "datums": [ { "id": "datum_xy", "plane": {"normal": [0, 0, 1], "offset": 0}, } ], "parameters": [ { "name": "flange_outer_diameter", "value": 120, "unit": "mm", "editable": True, }, { "name": "flange_thickness", "value": 16, "unit": "mm", "editable": True, }, { "name": "bore_diameter", "value": 40, "unit": "mm", "editable": True, }, { "name": "bolt_circle_diameter", "value": 90, "unit": "mm", "editable": True, }, { "name": "bolt_hole_diameter", "value": 11, "unit": "mm", "editable": True, }, { "name": "bolt_count", "value": 4, "unit": "count", "editable": True, }, ], "expressions": [ { "name": "flange_outer_radius", "expression": "flange_outer_diameter / 2", } ], "sketches": [], "constraints": [], "features": [ { "id": "feat_flange_body", "operation": "extrude_circle", "radius": { "expression": "flange_outer_diameter / 2" }, "height": {"parameter": "flange_thickness"}, }, { "id": "feat_central_bore", "operation": "through_hole", "diameter": {"parameter": "bore_diameter"}, "reference": {"feature": "feat_flange_body"}, }, { "id": "feat_bolt_holes", "operation": "polar_hole_pattern", "count": {"parameter": "bolt_count"}, "pitch_diameter": { "parameter": "bolt_circle_diameter" }, "hole_diameter": { "parameter": "bolt_hole_diameter" }, "reference": {"feature": "feat_flange_body"}, }, ], "patterns": [], "attachments": [], "construction_stages": [], }, "edit_interface": { "semantic_parameters": [ {"name": "flange_outer_diameter", "label": "法兰外径"}, {"name": "flange_thickness", "label": "法兰厚度"}, {"name": "bore_diameter", "label": "中心孔径"}, {"name": "bolt_circle_diameter", "label": "分布圆直径"}, {"name": "bolt_hole_diameter", "label": "螺栓孔径"}, {"name": "bolt_count", "label": "螺栓孔数量"}, ], "surface_parameter_groups": [], "modification_levels": ["semantic_feature"], "preserved_interfaces": [], }, "validation_contract": { "source_independence": True, "geometry_checks": [], "edit_checks": [], "invariants": [], "perturbations": [ {"parameter": "flange_outer_diameter", "delta": 10} ], "thresholds": {}, }, "backend_hint": "build123d", } normalized = MODULE._normalize_tool_authored_designir(payload) self.assertEqual("3.0", normalized["schema_version"]) self.assertEqual("mm", normalized["units"]) self.assertIsInstance( normalized["semantic_layer"]["parameters"], dict ) self.assertEqual( ["flange_outer_diameter", "flange_thickness", "bore_diameter", "bolt_circle_diameter", "bolt_hole_diameter", "bolt_count"], normalized["edit_interface"]["semantic_parameters"], ) compiled = MODULE.validate_designir(payload) self.assertEqual( {"parameter": "bolt_hole_diameter"}, compiled["features"][2]["diameter"], ) self.assertEqual("feat_flange_body", compiled["features"][1]["host"]) with tempfile.TemporaryDirectory() as temporary: output = Path(temporary) / "flange.step" result = MODULE.compile_designir(payload, output) self.assertEqual(1, result["facts"]["solid_count"]) self.assertEqual([120.0, 120.0, 16.0], result["facts"]["size_mm"]) tree = FEATURE_TREE.generate_feature_tree( normalized, designir_path="flange.designir.json", step_path="flange.step", backend="build123d", compiled=True, validated=True, ) serialized = json.dumps(tree) self.assertNotIn('"designir_3"', serialized) self.assertNotIn('"source_designir_feature"', serialized) replay_step = Path(temporary) / "flange-replayed.step" replay = FEATURE_TREE.compile_feature_tree(tree, replay_step) self.assertEqual("feature_manager_native", replay["replay_kind"]) self.assertEqual(result["facts"]["size_mm"], replay["facts"]["size_mm"]) def test_teacher_dependency_is_rejected(self) -> None: payload = self.payload() payload["source_step"] = "teacher.step" with self.assertRaisesRegex(MODULE.DesignIRError, "Teacher geometry"): MODULE.validate_designir(payload) def test_example_compiles_with_simplecadapi_and_exports_graph(self) -> None: payload = self.payload() with tempfile.TemporaryDirectory() as temporary: output = Path(temporary) / "flange.step" result = MODULE.compile_designir( payload, output, backend="simplecadapi" ) self.assertEqual("simplecadapi", result["backend"]) self.assertGreater(result["facts"]["volume_mm3"], 30_000) model_json_path = Path(result["model_json"]) self.assertTrue(model_json_path.is_file()) model_json = json.loads(model_json_path.read_text(encoding="utf-8")) serialized = json.dumps(model_json) self.assertIn("feature.base.flange", serialized) self.assertIn("feature.central.bore", serialized) self.assertTrue(model_json.get("leaf_ids")) def test_arbitrary_axis_geometric_center_preserves_pose(self) -> None: payload = self.payload() payload["family"] = "axis_aligned_cylinder" payload["parameters"] = { "outer_radius": {"value": 10, "unit": "mm", "editable": True}, "overall_length": {"value": 400, "unit": "mm", "editable": True}, } payload["expressions"] = {} payload["features"] = [ { "id": "main_body", "operation": "extrude_circle", "radius": {"parameter": "outer_radius"}, "height": {"parameter": "overall_length"}, "axis": [0, -1, 0], "center": [7, 11, 13], "anchor": "geometric_center", } ] payload["edit_interface"] = { "editable_parameters": ["outer_radius", "overall_length"], "preserved_interfaces": ["part_center", "primary_axis"], } payload["validation_contract"]["perturbations"] = [ { "parameter": "outer_radius", "scale": 1.1, "expected_change": "radius", "preserve": ["overall_length", "part_center"], }, { "parameter": "overall_length", "scale": 1.1, "expected_change": "length", "preserve": ["outer_radius", "part_center"], }, ] shape = MODULE.build_shape(payload) bounds = shape.bounding_box() self.assertAlmostEqual(-189, bounds.min.Y, places=6) self.assertAlmostEqual(211, bounds.max.Y, places=6) self.assertAlmostEqual(7, shape.center(MODULE.CenterOf.MASS).X, places=6) self.assertAlmostEqual(11, shape.center(MODULE.CenterOf.MASS).Y, places=6) self.assertAlmostEqual(13, shape.center(MODULE.CenterOf.MASS).Z, places=6) def test_embedded_mesh_is_rejected(self) -> None: payload = self.payload() payload["features"][0]["mesh"] = {"vertices": [[0, 0, 0]]} with self.assertRaisesRegex(MODULE.DesignIRError, "Teacher geometry"): MODULE.validate_designir(payload) def test_isolated_rebuild_has_no_teacher(self) -> None: with tempfile.TemporaryDirectory() as temporary: root = Path(temporary) designir = root / "flange.designir.json" designir.write_text(json.dumps(self.payload()), encoding="utf-8") output = root / "rebuilt.step" result = MODULE.isolated_rebuild(designir, output) self.assertTrue(result["valid"]) self.assertFalse(result["teacher_step_available"]) self.assertTrue(output.is_file()) def test_acceptance_passes_identical_designir_rebuild(self) -> None: payload = self.payload() with tempfile.TemporaryDirectory() as temporary: root = Path(temporary) teacher = root / "teacher.step" rebuilt = root / "rebuilt.step" MODULE.compile_designir(payload, teacher) MODULE.compile_designir(payload, rebuilt) report = MODULE.acceptance_report(teacher, rebuilt, payload) self.assertEqual("pass", report["verdict"]) self.assertEqual("replay_validated", report["next_state"]) self.assertEqual( 1.0, report["editability"]["parameter_perturbation_success_rate"] ) def test_unsupported_vocabulary_is_honest_failure(self) -> None: payload = self.payload() payload["reconstruction_status"] = "unsupported_feature_vocabulary" payload["missing_capabilities"] = ["multi_rail_sweep"] with self.assertRaisesRegex(MODULE.DesignIRError, "multi_rail_sweep"): MODULE.build_shape(payload) def test_promotion_requires_independent_replay_validated_cases(self) -> None: with tempfile.TemporaryDirectory() as temporary: root = Path(temporary) reports = [] for index in range(3): report = root / f"case-{index}.json" report.write_text( json.dumps( { "model_id": f"case-{index}", "teacher_sha256": f"teacher-{index}", "applied_experience_ids": [ "reconstruction.flange.primary" ], "next_state": "replay_validated", } ), encoding="utf-8", ) reports.append(report) result = MODULE.promotion_check( reports, ROOT / "config" / "promotion-policy.json" ) self.assertTrue(result["eligible"]) self.assertEqual( ["reconstruction.flange.primary"], result["validated_experience_ids"], ) self.assertEqual("promoted", result["next_state"]) if __name__ == "__main__": unittest.main()