feat(cadfs): expand reconstruction capability coverage
This commit is contained in:
@@ -1,10 +1,14 @@
|
||||
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
|
||||
|
||||
|
||||
@@ -33,6 +37,66 @@ class SelectorBindingTests(unittest.TestCase):
|
||||
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}))
|
||||
@@ -50,28 +114,79 @@ class SelectorBindingTests(unittest.TestCase):
|
||||
|
||||
self.assertEqual(score, 1.0)
|
||||
|
||||
def test_intersection_vertex_binds_pattern_and_current_body_prefixes(self) -> None:
|
||||
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"), {})
|
||||
bound, evidence = bind_candidate_selectors(candidate.cdsl)
|
||||
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]))
|
||||
|
||||
f7 = next(item for item in bound["features"] if item["id"] == "f_F7")
|
||||
reference = f7["params"]["end_condition"]["reference"]
|
||||
components = reference["intersection_of"]
|
||||
self.assertEqual(len(components), 3)
|
||||
self.assertEqual([item["binding_feature_id"] for item in components], ["f_F4", "f_F4", "f_F6"])
|
||||
# 绑定发生在 F4 完整 pattern 前缀;owner 保留 instance 4 的语义来源,
|
||||
# snapshot 则指向该前缀的 active Compound B-rep。
|
||||
self.assertEqual([item["owner_feature_id"] for item in components[:2]], ["f_F4.c4.f_F1", "f_F4.c4.f_F1"])
|
||||
self.assertTrue(all(item["snapshot_id"].startswith("body:f_F4:") for item in components[:2]))
|
||||
self.assertEqual(components[2]["match_mode"], "all")
|
||||
# 同一圆柱面可能被前缀 boolean 切成不同数量的 B-rep face;关键是
|
||||
# match_mode=all 保留每个可匹配片段,而不是把它收缩为任意一个面。
|
||||
matched = components[2]["matched_selectors"]
|
||||
self.assertTrue(matched)
|
||||
self.assertTrue(all(item["owner_feature_id"] == "f_F6" for item in matched))
|
||||
self.assertTrue(all(item["snapshot_id"].startswith("body:f_F6:") for item in matched))
|
||||
f7_evidence = next(item for item in evidence if item["feature_id"] == "f_F7")
|
||||
self.assertEqual(len(f7_evidence["resolved"]), len(matched) + 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)
|
||||
|
||||
Reference in New Issue
Block a user