from __future__ import annotations import importlib.util import json import tempfile import unittest from pathlib import Path from build123d import Box, Cylinder, Pos, export_step, import_step SCRIPT_PATH = Path(__file__).resolve().parents[1] / "scripts" / "model_spec.py" MODULE_SPEC = importlib.util.spec_from_file_location("cad_router_model_spec", SCRIPT_PATH) assert MODULE_SPEC is not None and MODULE_SPEC.loader is not None MODEL_SPEC = importlib.util.module_from_spec(MODULE_SPEC) MODULE_SPEC.loader.exec_module(MODEL_SPEC) class ModelSpecTests(unittest.TestCase): def test_affine_parameter_expression(self) -> None: payload = { "parameters": { "extension": { "value": 2.0, } } } self.assertEqual( MODEL_SPEC.resolve_number( payload, { "parameter": "extension", "scale": 0.5, "offset": 14.1, }, "center[2]", ), 15.1, ) def test_import_step_exact_base_round_trip(self) -> None: with tempfile.TemporaryDirectory() as temporary: root = Path(temporary) source = root / "teacher.step" export_step(Box(30, 20, 10) - Pos(0, 0, -1) * Cylinder(3, 12), source) result = MODEL_SPEC.import_step_model( source, root / "task", "round_trip", ) verification = MODEL_SPEC.verify_model( Path(result["model_spec"]) ) self.assertTrue(verification["valid"]) self.assertEqual( verification["checks"][0]["check"], "exact_base_geometry_signature", ) self.assertTrue((root / "task" / "round_trip.step").is_file()) self.assertTrue((root / "task" / "round_trip.stl").is_file()) def test_parameterized_cut_rebuilds_from_json(self) -> None: with tempfile.TemporaryDirectory() as temporary: root = Path(temporary) source = root / "source.step" export_step(Box(40, 30, 12), source) imported = MODEL_SPEC.import_step_model( source, root / "task", "parameterized_cut", ) spec_path = Path(imported["model_spec"]) payload = json.loads(spec_path.read_text(encoding="utf-8")) payload["parameters"] = { "bore_diameter": { "value": 6, "unit": "mm", "role": "central_bore_diameter", }, "bore_length": { "value": 16, "unit": "mm", "role": "through_cut_length", }, } payload["modifications"] = [ { "id": "central_bore", "operation": "cut_cylinder", "axis": "z", "center": [0, 0, 0], "diameter": {"parameter": "bore_diameter"}, "length": {"parameter": "bore_length"}, "enabled": True, } ] spec_path.write_text( json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8", ) first = MODEL_SPEC.build_model(spec_path) first_volume = first["facts"]["volume_mm3"] MODEL_SPEC.set_parameter(spec_path, "bore_diameter", 12) second = MODEL_SPEC.build_model(spec_path) self.assertLess(second["facts"]["volume_mm3"], first_volume) rebuilt = import_step(second["step"]) self.assertGreater(len(rebuilt.faces()), 0) def test_exact_base_rejects_changed_source(self) -> None: with tempfile.TemporaryDirectory() as temporary: root = Path(temporary) source = root / "source.step" export_step(Box(10, 10, 10), source) imported = MODEL_SPEC.import_step_model( source, root / "task", "checksum_guard", ) export_step(Box(12, 10, 10), root / "task" / "source.step") with self.assertRaises(MODEL_SPEC.ModelSpecError): MODEL_SPEC.build_model(Path(imported["model_spec"])) if __name__ == "__main__": unittest.main()