from __future__ import annotations import importlib.util import json import tempfile import unittest from pathlib import Path from build123d import Align, Box, Plane, Rectangle, Sphere, export_step ROOT = Path(__file__).resolve().parents[1] def load_module(name: str, path: Path): spec = importlib.util.spec_from_file_location(name, path) module = importlib.util.module_from_spec(spec) assert spec.loader spec.loader.exec_module(module) return module SURFACEIR = load_module( "surfaceir_pipeline", ROOT / "scripts" / "surfaceir_pipeline.py" ) DESIGNIR = load_module( "designir_pipeline_surface_test", ROOT / "scripts" / "designir_pipeline.py" ) class SurfaceIRPipelineTests(unittest.TestCase): def test_closed_single_face_sphere_roundtrip(self) -> None: with tempfile.TemporaryDirectory() as temporary: directory = Path(temporary) teacher = directory / "sphere.step" rebuilt_path = directory / "sphere-rebuilt.step" expected = Sphere(1) export_step(expected, teacher) payload = SURFACEIR.extract_surfaceir(teacher) shell = payload["surface_layer"]["solids"][0]["shells"][0] self.assertEqual(1, len(shell["faces"])) rebuilt = SURFACEIR.build_surfaceir( payload, "exact_3d_pcurve" ) SURFACEIR.export_step_shape(rebuilt, rebuilt_path) actual = DESIGNIR.import_step(rebuilt_path) self.assertTrue(actual.is_valid) self.assertEqual(1, len(actual.solids())) self.assertAlmostEqual(expected.volume, actual.volume, places=9) def test_multi_shell_solid_preserves_periodic_inner_shell(self) -> None: with tempfile.TemporaryDirectory() as temporary: directory = Path(temporary) teacher = directory / "cavity.step" rebuilt_path = directory / "cavity-rebuilt.step" expected = Box( 10, 10, 10, align=(Align.CENTER, Align.CENTER, Align.CENTER), ) - Sphere(1) export_step(expected, teacher) payload = SURFACEIR.extract_surfaceir(teacher) shells = payload["surface_layer"]["solids"][0]["shells"] self.assertEqual(2, len(shells)) self.assertTrue(any(len(shell["faces"]) == 1 for shell in shells)) rebuilt = SURFACEIR.build_surfaceir( payload, "exact_3d_pcurve" ) SURFACEIR.export_step_shape(rebuilt, rebuilt_path) actual = DESIGNIR.import_step(rebuilt_path) self.assertTrue(actual.is_valid) self.assertEqual(1, len(actual.solids())) self.assertEqual(2, len(actual.solids()[0].shells())) self.assertLess( abs(expected.volume - actual.volume) / expected.volume, 1e-10 ) def test_independent_json_roundtrip_preserves_exact_geometry(self) -> None: with tempfile.TemporaryDirectory() as temporary: directory = Path(temporary) teacher = directory / "teacher.step" portable_json = directory / "model.designir.json" rebuilt = directory / "rebuilt.step" expected = DESIGNIR.Box(10, 20, 30) DESIGNIR.export_step(expected, teacher) surfaceir = SURFACEIR.extract_surfaceir(teacher) SURFACEIR.write_json(portable_json, surfaceir) teacher.unlink() serialized = portable_json.read_text(encoding="utf-8").lower() stored = json.loads(serialized) self.assertEqual( "gzip+base64+json", stored["surface_layer"]["encoding"], ) self.assertEqual( "surfaceir-3.0", stored["surface_layer"]["format"], ) self.assertNotIn("teacher.step", serialized) self.assertNotIn("source_path", serialized) self.assertNotIn("import_step", serialized) self.assertNotIn('"mesh"', serialized) decoded = SURFACEIR.read_designir(portable_json) self.assertEqual(surfaceir["surface_layer"], decoded["surface_layer"]) self.assertIn( "overall_scale", decoded["edit_interface"]["semantic_parameters"], ) rebuilt_shape = SURFACEIR.build_surfaceir(decoded) SURFACEIR.export_step_shape(rebuilt_shape, rebuilt) self.assertTrue(rebuilt.is_file()) actual = DESIGNIR.import_step(rebuilt) self.assertEqual(len(expected.solids()), len(actual.solids())) self.assertEqual(len(expected.faces()), len(actual.faces())) self.assertEqual(len(expected.edges()), len(actual.edges())) self.assertLess( abs(expected.volume - actual.volume) / expected.volume, 1e-10 ) self.assertLess( DESIGNIR._symmetric_difference_volume(expected, actual), 1e-8 ) def test_empty_step_document_is_represented_honestly(self) -> None: payload = { "schema_version": "3.0", "designir_kind": "independent_parametric_cad", "document_status": "empty_geometry", "surface_layer": { "vertices": [], "solids": [], "free_shells": [], }, } rebuilt = SURFACEIR.build_surfaceir(payload) self.assertEqual(SURFACEIR._facts_from_shape(rebuilt)["face_count"], 0) def test_open_shell_roundtrip_preserves_surface_geometry(self) -> None: with tempfile.TemporaryDirectory() as temporary: directory = Path(temporary) teacher = directory / "surface.step" rebuilt_path = directory / "surface-rebuilt.step" face = (Plane.XY * Rectangle(10, 20)).face() builder = SURFACEIR.BRep_Builder() shell = SURFACEIR.TopoDS_Shell() builder.MakeShell(shell) builder.Add(shell, face.wrapped) SURFACEIR.export_step_shape(shell, teacher) payload = SURFACEIR.extract_surfaceir(teacher) self.assertEqual(payload["document_status"], "geometry_present") self.assertEqual(len(payload["surface_layer"]["solids"]), 0) self.assertEqual(len(payload["surface_layer"]["free_shells"]), 1) rebuilt = SURFACEIR.build_surfaceir( payload, "exact_3d_pcurve" ) SURFACEIR.export_step_shape(rebuilt, rebuilt_path) _, teacher_facts = SURFACEIR._shape_facts(teacher) _, rebuilt_facts = SURFACEIR._shape_facts(rebuilt_path) self.assertEqual(teacher_facts["face_count"], 1) self.assertEqual( teacher_facts["face_count"], rebuilt_facts["face_count"] ) self.assertAlmostEqual( teacher_facts["surface_area"], rebuilt_facts["surface_area"], places=8, ) def test_overall_scale_is_executable_and_independently_validated(self) -> None: with tempfile.TemporaryDirectory() as temporary: directory = Path(temporary) teacher = directory / "teacher.step" designir_path = directory / "box.designir.json" expected = DESIGNIR.Box(10, 20, 30) DESIGNIR.export_step(expected, teacher) payload = SURFACEIR.extract_surfaceir(teacher) self.assertIn( "overall_scale", payload["edit_interface"]["semantic_parameters"], ) edited = SURFACEIR.apply_semantic_parameter( payload, "overall_scale", 1.1 ) self.assertEqual( edited["edit_operations"], [ { "operation": "uniform_scale", "factor": 1.1, "pivot": [0.0, 0.0, 0.0], } ], ) self.assertIsNotNone(SURFACEIR.build_surfaceir(edited)) SURFACEIR.write_json(designir_path, payload) acceptance = SURFACEIR.validate_semantic_edit( teacher, designir_path, "overall_scale", 1.1, directory / "acceptance", ) self.assertTrue(acceptance["accepted"]) self.assertTrue( acceptance["checks"]["target_parameter"]["pass"] ) def test_primary_axis_dimensions_are_executable_and_validated(self) -> None: with tempfile.TemporaryDirectory() as temporary: directory = Path(temporary) teacher = directory / "shaft.step" designir_path = directory / "shaft.designir.json" DESIGNIR.export_step(DESIGNIR.Cylinder(10, 40), teacher) payload = SURFACEIR.extract_surfaceir(teacher) SURFACEIR.write_json(designir_path, payload) for parameter, value in ( ("outer_diameter", 22.0), ("body_length", 44.0), ): acceptance = SURFACEIR.validate_semantic_edit( teacher, designir_path, parameter, value, directory / parameter, ) self.assertTrue(acceptance["accepted"]) self.assertTrue( acceptance["checks"]["target_parameter"]["pass"] ) def test_canonical_semantics_name_bore_and_polar_hole_pattern(self) -> None: with tempfile.TemporaryDirectory() as temporary: teacher = Path(temporary) / "flange.step" expected = DESIGNIR.Cylinder(20, 5) - DESIGNIR.Cylinder(5, 5) for x, y in ((10, 0), (0, 10), (-10, 0), (0, -10)): expected -= DESIGNIR.Pos(x, y, 0) * DESIGNIR.Cylinder(1, 5) DESIGNIR.export_step(expected, teacher) payload = SURFACEIR.extract_surfaceir(teacher) semantic = payload["semantic_layer"] parameters = semantic["parameters"] self.assertEqual(parameters["hole_count"]["value"], 4) self.assertAlmostEqual(parameters["hole_diameter"]["value"], 2) self.assertAlmostEqual( parameters["bolt_circle_diameter"]["value"], 20 ) self.assertAlmostEqual(parameters["bore_diameter"]["value"], 10) self.assertAlmostEqual(parameters["outer_diameter"]["value"], 40) self.assertAlmostEqual(parameters["plate_thickness"]["value"], 5) self.assertTrue( any( feature["operation"] == "polar_hole_pattern" for feature in semantic["features"] ) ) self.assertFalse( semantic["inference_policy"]["claims_original_feature_history"] ) edited = SURFACEIR.apply_semantic_parameter( payload, "hole_diameter", 3 ) self.assertEqual(len(edited["edit_operations"]), 4) self.assertEqual( edited["active_edit"]["acceptance_status"], "pending" ) with self.assertRaisesRegex(ValueError, "increases only"): SURFACEIR.apply_semantic_parameter( payload, "hole_diameter", 1.5 ) edited_step = Path(temporary) / "flange-edited.step" edited_shape = SURFACEIR.build_surfaceir(edited) SURFACEIR.export_step_shape(edited_shape, edited_step) edited_payload = SURFACEIR.extract_surfaceir(edited_step) edited_parameters = edited_payload["semantic_layer"]["parameters"] self.assertAlmostEqual( edited_parameters["hole_diameter"]["value"], 3 ) self.assertAlmostEqual( edited_parameters["bore_diameter"]["value"], 10 ) self.assertEqual(edited_parameters["hole_count"]["value"], 4) actual = DESIGNIR.import_step(edited_step) self.assertTrue(actual.is_valid) self.assertLess(actual.volume, expected.volume) original_ir = Path(temporary) / "flange.designir.json" SURFACEIR.write_json(original_ir, payload) acceptance = SURFACEIR.validate_semantic_edit( teacher, original_ir, "hole_diameter", 2.5, Path(temporary) / "acceptance", ) self.assertTrue(acceptance["accepted"]) self.assertEqual(acceptance["state"], "accepted") if __name__ == "__main__": unittest.main()