110 lines
3.9 KiB
Python
110 lines
3.9 KiB
Python
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_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()
|