from __future__ import annotations from copy import deepcopy from pathlib import Path import tempfile import unittest from unittest.mock import patch from cadfs_to_cdsl.featurescript_parser import parse_featurescript from cadfs_to_cdsl.lowering import lower_model from cadfs_to_cdsl.rebuild import rebuild_candidate from cadfs_to_cdsl.selector_binding import _score, bind_candidate_selectors class SelectorBindingTests(unittest.TestCase): def test_face_normal_match_is_orientation_independent(self) -> None: score = _score( {"normal": [0.0, 0.0, 1.0], "plane_offset_mm": 12.0}, {"normal": [0.0, 0.0, -1.0], "plane_offset_mm": 12.0}, ) self.assertEqual(score, 1.0) def test_rotational_face_axis_direction_is_orientation_independent(self) -> None: score = _score( {"axis_direction": [0.0, 0.0, 1.0]}, {"axis_direction": [0.0, 0.0, -1.0]}, ) self.assertEqual(score, 1.0) def test_axis_origin_distinguishes_parallel_cylinders(self) -> None: score = _score( {"axis_origin_mm": [0.0, 25.4, 33.37]}, {"axis_origin_mm": [33.38, 25.4, 0.0]}, ) self.assertEqual(score, 0.0) def test_empty_snapshot_score_is_not_treated_as_a_match(self) -> None: self.assertEqual(_score({}, {"normal": [0.0, 0.0, 1.0]}), 0.0) def test_geometry_free_context_selector_uses_unique_active_record_when_owner_is_stale(self) -> None: cdsl = { "features": [ {"id": "f_source", "atomic_id": "reference_plane"}, { "id": "f_mirror", "atomic_id": "pattern_mirror", "params": {}, "selectors": [{"kind": "plane", "owner_feature_id": "f_source"}], }, ], } report = { "feature_results": [], "topology_records": [{ "kind": "plane", "record_id": "context:plane:1", "owner_feature_ids": ["f_live_plane"], "geometry": {}, }], } with patch("engine.cdsl_engine.runtime.rebuild_cdsl", return_value=report): bound, _ = bind_candidate_selectors(cdsl) selector = bound["features"][1]["selectors"][0] self.assertEqual(selector["owner_feature_id"], "f_live_plane") self.assertEqual(selector["stable_id"], "context:plane:1") self.assertEqual(bound["features"][1]["params"]["mirror_plane"], selector) def test_geometry_free_instance_selector_does_not_fall_back_to_active_record(self) -> None: cdsl = { "features": [ {"id": "f_source", "atomic_id": "reference_plane"}, { "id": "f_mirror", "atomic_id": "pattern_mirror", "params": {}, "selectors": [{ "kind": "plane", "owner_feature_id": "f_source", "owner_match_required": True, }], }, ], } report = { "feature_results": [], "topology_records": [{ "kind": "plane", "record_id": "context:plane:1", "owner_feature_ids": ["f_live_plane"], "geometry": {}, }], } with patch("engine.cdsl_engine.runtime.rebuild_cdsl", return_value=report): with self.assertRaisesRegex(ValueError, "f_mirror: selector_not_found after prefix rebuild"): bind_candidate_selectors(cdsl) def test_swept_face_area_lower_bound_rejects_coplanar_fragment(self) -> None: expected = {"normal": [0.0, 1.0, 0.0], "plane_offset_mm": 54.69, "minimum_area_mm2": 285.0} self.assertIsNone(_score(expected, {"normal": [0.0, 1.0, 0.0], "plane_offset_mm": 54.69, "area_mm2": 0.64})) self.assertEqual(_score(expected, {"normal": [0.0, 1.0, 0.0], "plane_offset_mm": 54.69, "area_mm2": 463.7}), 1.0) def test_reversed_cap_normal_reverses_its_plane_offset(self) -> None: score = _score( {"normal": [0.0, 0.0, 1.0], "plane_offset_mm": 10.0}, { "normal": [0.0, 0.0, 1.0], "plane_normal": [0.0, 0.0, -1.0], "plane_offset_mm": -10.0, }, ) self.assertEqual(score, 1.0) def test_cap_face_output_role_is_validated_without_snapshot_rebinding(self) -> None: root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test" feature = root / "featurescript_rp/0001/00016195.txt" if not feature.exists(): self.skipTest("CADFS sample is not installed") candidate = lower_model(parse_featurescript(feature.read_text(), "00016195"), {}) bound, evidence = bind_candidate_selectors(candidate.cdsl) selector = next(item for item in bound["features"] if item["id"] == "f_F3")["selectors"][0] self.assertEqual(selector, { "kind": "face", "owner_feature_id": "f_F1", "output_role": "extrude.end", "source": "runtime_snapshot", "confidence": 1.0, }) self.assertNotIn("stable_id", selector) self.assertNotIn("snapshot_id", selector) self.assertNotIn("geometry", selector) binding = next(item for item in evidence if item["feature_id"] == "f_F3") self.assertEqual(len(binding["resolved"]), 1) self.assertEqual(binding["resolved"][0]["output_roles"], ["extrude.end"]) def test_shell_offset_face_role_binds_only_its_true_dependency_source(self) -> None: root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test" feature = root / "featurescript_rp/0010/00107631.txt" if not feature.exists(): self.skipTest("CADFS sample is not installed") candidate = lower_model(parse_featurescript(feature.read_text(), "00107631"), {}) self.assertEqual(candidate.status, "converted_complete") # F2 retains F1's end-cap provenance, but F3 also selects the distinct # start-cap plane. Its direct owner has no geometry-qualified active # candidate, so binding must consider the unique active F2 face without # weakening the exact qualified OFFSET_FACE role. prefix = deepcopy(candidate.cdsl) prefix["features"] = prefix["features"][:3] bound, evidence = bind_candidate_selectors(prefix) first, offset = bound["features"][-1]["selectors"] self.assertEqual(first["owner_feature_id"], "f_F2") self.assertEqual(first["stable_id"], "body:f_F2:face:1") self.assertEqual(first["snapshot_id"], "body:f_F2:face:1") self.assertEqual(first["source"], "runtime_snapshot") self.assertEqual(first["confidence"], 1.0) self.assertEqual(offset, { "kind": "face", "owner_feature_id": "f_F2", "output_role": "shell.offset_face", "output_role_source": {"owner_feature_id": "f_F1", "output_role": "extrude.start"}, "source": "runtime_snapshot", "confidence": 1.0, }) binding = next(item for item in evidence if item["feature_id"] == "f_F3") self.assertEqual(binding["resolved"][0]["snapshot_id"], "body:f_F2:face:1") self.assertEqual(binding["resolved"][1]["record_id"], "body:f_F2:face:5") # Binding must not hide the OCC feasibility boundary. The requested # second shell currently produces an invalid shape, so preserve the # F2 prefix instead of changing thickness or removal faces. with tempfile.TemporaryDirectory() as directory: outcome = rebuild_candidate(candidate.cdsl, Path(directory) / "00107631.step") self.assertEqual(outcome["status"], "rebuild_failed") self.assertEqual(outcome["error"]["type"], "RuntimeExecutionError") self.assertEqual(outcome["error"]["message"], "OCC shell operation produced an invalid shape") self.assertEqual(outcome["last_executable_prefix"]["failed_feature_id"], "f_F3") self.assertEqual(outcome["last_executable_prefix"]["last_feature_id"], "f_F2") def test_intersection_vertex_binds_a_pattern_copy_with_exact_instance_owner_evidence(self) -> None: root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test" feature = root / "featurescript_rp/0042/00423838.txt" if not feature.exists(): self.skipTest("CADFS sample is not installed") candidate = lower_model(parse_featurescript(feature.read_text(), "00423838"), {}) reference = next(item for item in candidate.cdsl["features"] if item["id"] == "f_F7")["params"]["end_condition"]["reference"] self.assertTrue(all(item["owner_match_required"] for item in reference["intersection_of"][:2])) bound, evidence = bind_candidate_selectors(candidate.cdsl) reference = next(item for item in bound["features"] if item["id"] == "f_F7")["params"]["end_condition"]["reference"] copy_components = reference["intersection_of"][:2] self.assertTrue(all(item["owner_feature_id"] == "f_F4.c4.f_F1" for item in copy_components)) self.assertTrue(all(item.get("snapshot_id") for item in copy_components)) binding = next(item for item in evidence if item["feature_id"] == "f_F7") self.assertGreaterEqual(len(binding["resolved"]), 2)