feat(selector): 增加离线候选遍历与严格回放验证 Demo

- 新增 selector_candidate_demo,移除 provenance intent 后枚举候选 selector
- 对候选分支执行有界重建与严格 STEP 比较
- 仅在候选遍历完整且唯一 strict 通过时生成 selector 映射记录
- 增加 selector 候选搜索、预算限制和记录生成的测试
- 保持生产 selector resolver 不受 Demo 逻辑影响
- 更新 CADFS 能力台账,记录 IMPRINT 派生 profile 的 lineage selector 缺口
This commit is contained in:
2026-09-10 15:12:57 +08:00
parent d8fc2a9207
commit 994d06aaea
36 changed files with 9268 additions and 753 deletions
+260 -62
View File
@@ -10,7 +10,7 @@ from cadfs_to_cdsl.rebuild import rebuild_candidate
class IntegrationTests(unittest.TestCase):
def test_known_rp_roundtrip_00000173(self):
def test_known_rp_source_preserves_prefix_when_selector_query_is_deferred(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
feature = root / "featurescript_rp/0000/00000173.txt"; gold = root / "step_abc/0000/00000173.step"
if not feature.exists() or not gold.exists(): self.skipTest("CADFS sample is not installed")
@@ -18,11 +18,9 @@ class IntegrationTests(unittest.TestCase):
with tempfile.TemporaryDirectory() as tmp:
rebuilt = Path(tmp) / "rebuild.step"
result = rebuild_candidate(cdsl, rebuilt)
self.assertEqual(result["status"], "rebuilt")
comparison = compare_steps(gold, rebuilt)
self.assertFalse(comparison["strict"]["passed"])
self.assertTrue(comparison["rp"]["passed"])
self.assertEqual(comparison["decision"], "approximate_pass")
self.assertEqual(result["status"], "rebuild_failed")
self.assertEqual(result["error"]["message"], "f_F2: selector_query_unsupported during incremental replay")
self.assertEqual(result["last_executable_prefix"]["last_feature_id"], "f_F1")
def test_standard_tapped_through_counterbore_00002243(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
@@ -43,9 +41,30 @@ class IntegrationTests(unittest.TestCase):
with tempfile.TemporaryDirectory() as tmp:
rebuilt = Path(tmp) / "rebuild.step"
self.assertEqual(rebuild_candidate(result.cdsl, rebuilt)["status"], "rebuilt")
comparison = compare_steps(root / "step_abc/0000/00002243.step", rebuilt)
self.assertTrue(comparison["strict"]["passed"])
outcome = rebuild_candidate(result.cdsl, rebuilt)
self.assertEqual(outcome["status"], "rebuild_failed")
self.assertEqual(outcome["error"]["message"], "f_F4: selector_query_unsupported during incremental replay")
self.assertEqual(outcome["last_executable_prefix"]["last_feature_id"], "f_F3")
def test_direct_sketch_vertex_holes_preserve_scoped_body_00406667(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
feature = root / "featurescript_rp/0040/00406667.txt"
if not feature.exists(): self.skipTest("CADFS sample is not installed")
candidate = lower_model(parse_featurescript(feature.read_text(), "00406667"), {}).cdsl
holes = [item for item in candidate["features"] if item["atomic_id"] == "hole_wizard"]
self.assertEqual([item["id"] for item in holes], ["f_F3", "f_F5"])
self.assertEqual([item["params"]["scope_feature_id"] for item in holes], ["f_F1", "f_F1"])
self.assertEqual([item["params"]["positions"] for item in holes], [
[{"mm": [0.0, 0.0, 0.0]}], [{"mm": [0.0, 26.69, 0.0]}],
])
with tempfile.TemporaryDirectory() as tmp:
outcome = rebuild_candidate(candidate, Path(tmp) / "rebuild.step")
self.assertEqual(outcome["status"], "rebuilt")
self.assertEqual(
[item["feature_id"] for item in outcome["result"]["feature_results"]],
["f_F1", "f_F3", "f_F5"],
)
def test_rectilinear_fillet_arcs_00129362_preserve_recovered_sketch_geometry(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
@@ -75,9 +94,15 @@ class IntegrationTests(unittest.TestCase):
fillet = next(item for item in result.cdsl["features"] if item["id"] == "f_F3")
self.assertEqual(fillet["atomic_id"], "fillet")
self.assertEqual(fillet["selectors"][0]["kind"], "face")
self.assertNotIn("geometry", fillet["selectors"][0])
self.assertEqual(fillet["selectors"][0]["selector_intent"]["query_family"], "SWEPT_FACE")
self.assertEqual(
fillet["selectors"][0]["geometry"],
{"axis_origin_mm": [0.0, 0.0, 0.0], "axis_direction": [0.0, -1.0, 0.0], "radius_mm": 45.66},
fillet["selectors"][0]["selector_intent"]["source_entity"],
{"sketch_id": "F0", "entity_id": "E0"},
)
self.assertEqual(
fillet["selectors"][0]["selector_intent"]["derivation_policy"],
{"allowed": ["boundary"], "multiplicity": "one"},
)
chamfer = next(item for item in result.cdsl["features"] if item["id"] == "f_F5")
@@ -101,18 +126,183 @@ class IntegrationTests(unittest.TestCase):
self.assertNotIn("result_mode", next(item for item in result.cdsl["features"] if item["id"] == "f_F7")["params"])
self.assertEqual(next(item for item in result.cdsl["features"] if item["id"] == "f_F9")["params"]["result_mode"], "new_body")
from build123d import import_step
with tempfile.TemporaryDirectory() as tmp:
rebuilt = Path(tmp) / "rebuild.step"
outcome = rebuild_candidate(result.cdsl, rebuilt)
self.assertEqual(outcome["status"], "rebuilt")
runtime_box = outcome["result"]["bbox_mm"]
imported_box = import_step(str(rebuilt)).bounding_box()
actual = [imported_box.min.X, imported_box.min.Y, imported_box.min.Z, imported_box.max.X, imported_box.max.Y, imported_box.max.Z]
expected = runtime_box["min"] + runtime_box["max"]
for value, target in zip(actual, expected): self.assertAlmostEqual(value, target, places=5)
self.assertEqual(outcome["status"], "rebuild_failed")
self.assertEqual(outcome["error"]["message"], "f_F5: selector_query_unsupported during incremental replay")
self.assertEqual(outcome["last_executable_prefix"]["last_feature_id"], "f_F4")
def test_inward_shells_00789939_lower_and_rebuild(self):
def test_swept_face_extent_continues_through_primary_cuts_00925274(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
feature = root / "featurescript_rp/0092/00925274.txt"
if not feature.exists(): self.skipTest("CADFS sample is not installed")
candidate = lower_model(parse_featurescript(feature.read_text(), "00925274"), {}).cdsl
with tempfile.TemporaryDirectory() as tmp:
outcome = rebuild_candidate(candidate, Path(tmp) / "rebuild.step")
self.assertEqual(outcome["status"], "rebuilt")
result = outcome["result"]
self.assertEqual(
[item["feature_id"] for item in result["feature_results"]],
["f_F1", "f_F3", "f_F4", "f_F6", "f_F7", "f_F9", "f_F10", "f_F12", "f_F13", "f_F15", "f_F17", "f_F18_plane", "f_F18"],
)
f9_resolution = next(
item for item in result["selector_resolution"] if item["feature_id"] == "f_F9"
)
self.assertEqual(f9_resolution["status"], "resolved")
self.assertEqual(f9_resolution["resolution_mode"], "kernel_lineage")
self.assertEqual(f9_resolution["evidence"]["result_records"], ["body:f_F6:face:0"])
self.assertEqual(
[item["feature_id"] for item in f9_resolution["evidence"]["relations"]],
["f_F1", "f_F3", "f_F6"],
)
def test_direct_hole_prism_swept_edge_resolves_00007264(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
feature = root / "featurescript_rp/0000/00007264.txt"
if not feature.exists(): self.skipTest("CADFS sample is not installed")
result = lower_model(parse_featurescript(feature.read_text(), "00007264"), {})
fillet = next(item for item in result.cdsl["features"] if item["id"] == "f_F2")
self.assertEqual(fillet["atomic_id"], "fillet")
self.assertTrue(all(
selector["selector_intent"]["query_family"] == "SWEPT_EDGE"
for selector in fillet["selectors"]
))
with tempfile.TemporaryDirectory() as tmp:
outcome = rebuild_candidate(result.cdsl, Path(tmp) / "rebuild.step")
self.assertEqual(outcome["status"], "rebuilt")
binding = next(item for item in outcome["selector_binding"] if item["feature_id"] == "f_F2")
self.assertEqual(len(binding["resolved"]), 4)
vertical_edges = [
relation
for delta in outcome["result"]["topology_deltas"]
if not any(str(snapshot_id).startswith("transient:") for snapshot_id in delta["output_snapshot_ids"])
for relation in delta["relations"]
if relation["source_kind"] == "vertex"
and relation["result_kind"] == "edge"
and relation["source_record_ids"]
]
self.assertEqual(
len({(tuple(item["source_record_ids"]), tuple(item["result_record_ids"])) for item in vertical_edges}),
4,
)
self.assertTrue(all(
relation["coverage"] == "complete" and relation["lineage_status"] == "proven"
for relation in vertical_edges
))
def test_annular_swept_face_selector_00974931(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
feature = root / "featurescript_rp/0097/00974931.txt"
gold = root / "step_abc/0097/00974931.step"
if not feature.exists() or not gold.exists(): self.skipTest("CADFS sample is not installed")
candidate = lower_model(parse_featurescript(feature.read_text(), "00974931"), {}).cdsl
fillet = next(item for item in candidate["features"] if item["id"] == "f_F2")
selector = fillet["selectors"][0]
self.assertEqual(fillet["atomic_id"], "fillet")
self.assertNotIn("geometry", selector)
self.assertNotIn("stable_id", selector)
self.assertEqual(selector["selector_intent"]["query_family"], "SWEPT_FACE")
self.assertEqual(
selector["selector_intent"]["source_entity"],
{"sketch_id": "F0", "entity_id": "E0"},
)
with tempfile.TemporaryDirectory() as tmp:
rebuilt = Path(tmp) / "rebuild.step"
outcome = rebuild_candidate(candidate, rebuilt)
self.assertEqual(outcome["status"], "rebuild_failed")
f2_resolution = next(
item for item in outcome["last_executable_prefix"]["result"]["selector_resolution"]
if item["feature_id"] == "f_F2"
)
self.assertEqual(f2_resolution["status"], "resolved")
self.assertEqual(f2_resolution["resolution_mode"], "kernel_lineage")
self.assertEqual(f2_resolution["evidence"]["semantic_anchor"], {
"type": "source_entity", "sketch_id": "F0", "entity_id": "E0",
})
self.assertEqual(
[item["feature_id"] for item in outcome["last_executable_prefix"]["result"]["feature_results"]],
["f_F1", "f_F2"],
)
self.assertEqual(outcome["error"]["message"], "f_F3: selector_query_unsupported during incremental replay")
def test_direct_prism_cap_edge_lineage_00021014(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
feature = root / "featurescript_rp/0002/00021014.txt"
gold = root / "step_abc/0002/00021014.step"
if not feature.exists() or not gold.exists(): self.skipTest("CADFS sample is not installed")
candidate = lower_model(parse_featurescript(feature.read_text(), "00021014"), {}).cdsl
fillet = next(item for item in candidate["features"] if item["id"] == "f_F2")
cap_edges = [
selector for selector in fillet["selectors"]
if (selector.get("selector_intent") or {}).get("query_family") == "CAP_EDGE"
]
self.assertEqual(len(cap_edges), 9)
self.assertTrue(all("stable_id" not in selector and "geometry" not in selector for selector in cap_edges))
self.assertEqual(
{selector["selector_intent"]["lineage_role"] for selector in cap_edges},
{"extrude.start", "extrude.end"},
)
with tempfile.TemporaryDirectory() as tmp:
rebuilt = Path(tmp) / "rebuild.step"
outcome = rebuild_candidate(candidate, rebuilt)
self.assertEqual(outcome["status"], "rebuilt")
comparison = compare_steps(gold, rebuilt)
cap_resolutions = [
item for item in outcome["result"]["selector_resolution"]
if item.get("feature_id") == "f_F2"
and item.get("evidence", {}).get("semantic_anchor", {}).get("type") == "source_entity"
]
self.assertEqual(len(cap_resolutions), 10)
direct_caps = [
item for item in cap_resolutions
if item["evidence"]["relations"][0].get("output_role") in {"extrude.start", "extrude.end"}
]
self.assertEqual(len(direct_caps), 9)
self.assertTrue(all(item["resolution_mode"] == "kernel_lineage" for item in direct_caps))
self.assertTrue(comparison["strict"]["passed"])
def test_direct_mixed_hole_cap_edge_lineage_00735367(self):
"""A solver-split circular hole restores one proven source cap edge."""
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
feature = root / "featurescript_rp/0073/00735367.txt"
if not feature.exists(): self.skipTest("CADFS sample is not installed")
candidate = lower_model(parse_featurescript(feature.read_text(), "00735367"), {}).cdsl
fillet = next(item for item in candidate["features"] if item["id"] == "f_F2")
selector = fillet["selectors"][0]
self.assertEqual(selector["selector_intent"]["query_family"], "CAP_EDGE")
self.assertEqual(selector["selector_intent"]["source_entity"], {"sketch_id": "F0", "entity_id": "E1"})
self.assertEqual(selector["selector_intent"]["lineage_role"], "extrude.end")
self.assertNotIn("geometry", selector)
self.assertNotIn("stable_id", selector)
with tempfile.TemporaryDirectory() as tmp:
outcome = rebuild_candidate(candidate, Path(tmp) / "rebuild.step")
self.assertEqual(outcome["status"], "rebuild_failed")
self.assertEqual(outcome["error"]["message"], "f_F4: selector_query_unsupported during incremental replay")
prefix = outcome["last_executable_prefix"]["result"]
self.assertEqual([item["feature_id"] for item in prefix["feature_results"]], ["f_F1", "f_F2", "f_F3"])
resolution = next(item for item in prefix["selector_resolution"] if item["feature_id"] == "f_F2")
self.assertEqual(resolution["status"], "resolved")
self.assertEqual(resolution["resolution_mode"], "kernel_lineage")
self.assertEqual(resolution["evidence"]["semantic_anchor"], {
"type": "source_entity", "sketch_id": "F0", "entity_id": "E1",
})
self.assertEqual(resolution["evidence"]["relations"][0]["output_role"], "extrude.end")
self.assertTrue(resolution["evidence"]["relations"][0]["source_record_ids"])
def test_inward_shells_00789939_preserve_prefix_when_face_query_is_deferred(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
feature = root / "featurescript_rp/0078/00789939.txt"
if not feature.exists(): self.skipTest("CADFS sample is not installed")
@@ -128,8 +318,10 @@ class IntegrationTests(unittest.TestCase):
with tempfile.TemporaryDirectory() as tmp:
rebuilt = Path(tmp) / "rebuild.step"
outcome = rebuild_candidate(result.cdsl, rebuilt)
self.assertEqual(outcome["status"], "rebuilt")
self.assertEqual(outcome["result"]["solid_count"], 2)
self.assertEqual(outcome["status"], "rebuild_failed")
self.assertEqual(outcome["error"]["message"], "f_F6: selector_query_unsupported during incremental replay")
self.assertEqual(outcome["last_executable_prefix"]["last_feature_id"], "f_F5")
self.assertEqual(outcome["last_executable_prefix"]["result"]["solid_count"], 2)
def test_outward_cap_shells_lower_and_rebuild_prefixes(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
@@ -155,7 +347,10 @@ class IntegrationTests(unittest.TestCase):
with tempfile.TemporaryDirectory() as tmp:
outcome = rebuild_candidate(prefix, Path(tmp) / "outward-shell.step")
self.assertEqual(outcome["status"], "rebuilt")
self.assertEqual(outcome["result"]["solid_count"], 1)
self.assertEqual(
[item["feature_id"] for item in outcome["result"]["feature_results"]],
["f_F1", "f_F2"],
)
def test_sweep_00542223_preserves_its_open_bspline_path(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
@@ -173,6 +368,42 @@ class IntegrationTests(unittest.TestCase):
self.assertNotIn("F2", [item.get("feature_id") for item in result.diagnostics])
self.assertNotIn("F5", [item.get("feature_id") for item in result.diagnostics])
def test_direct_sketch_wire_qbodytype_path_lowers_and_rebuilds_00896761(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
feature = root / "featurescript_rp/0089/00896761.txt"
if not feature.exists(): self.skipTest("CADFS sample is not installed")
candidate = lower_model(parse_featurescript(feature.read_text(), "00896761"), {}).cdsl
sweep = next(item for item in candidate["features"] if item["id"] == "f_F2")
segment = sweep["params"]["path"]["segment"]
self.assertEqual(sweep["atomic_id"], "sweep_add")
self.assertEqual(segment["type"], "bspline")
self.assertEqual(segment["source_entity_id"], "E2")
self.assertEqual(segment["points"], [[0.0, 0.0], [87.21, 282.41]])
self.assertEqual(segment["start_tangent"], [0.0, 936.12])
self.assertEqual(segment["end_tangent"], [168.9, 48.72])
from engine.cdsl_engine.runtime import analyze_cdsl
self.assertTrue(analyze_cdsl(candidate).runtime_eligible)
with tempfile.TemporaryDirectory() as tmp:
outcome = rebuild_candidate(candidate, Path(tmp) / "rebuild.step")
self.assertEqual(outcome["status"], "rebuilt")
self.assertEqual([item["feature_id"] for item in outcome["result"]["feature_results"]], ["f_F2"])
def test_multi_entity_sketch_wire_qbodytype_path_remains_deferred_00786708(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
feature = root / "featurescript_rp/0078/00786708.txt"
if not feature.exists(): self.skipTest("CADFS sample is not installed")
result = lower_model(parse_featurescript(feature.read_text(), "00786708"), {})
self.assertIn({
"code": "unsupported_engine_capability", "feature_id": "F2", "operation": "sweep", "capability": "sweep_path_query",
"message": "current CDSL sweep requires one direct sketch line or B-spline path",
}, result.diagnostics)
self.assertNotIn("f_F2", {item["id"] for item in result.cdsl["features"]})
later_sweep = next(item for item in result.cdsl["features"] if item["id"] == "f_F5")
self.assertEqual(later_sweep["params"]["path"]["segment"]["source_entity_id"], "E5")
def test_circular_pattern_00542223_preserves_all_sweep_arms(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
feature = root / "featurescript_rp/0054/00542223.txt"
@@ -193,7 +424,7 @@ class IntegrationTests(unittest.TestCase):
self.assertAlmostEqual(bbox["max"][0], 52.14101625137762)
self.assertAlmostEqual(bbox["max"][1], 37.5000001000001)
def test_fused_body_circular_copy_faces_bind_and_shell_00542223(self):
def test_fused_body_circular_copy_faces_preserve_shell_prefix_00542223(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
feature = root / "featurescript_rp/0054/00542223.txt"
if not feature.exists(): self.skipTest("CADFS sample is not installed")
@@ -202,53 +433,20 @@ class IntegrationTests(unittest.TestCase):
with tempfile.TemporaryDirectory() as tmp:
outcome = rebuild_candidate(candidate, Path(tmp) / "rebuild.step")
self.assertEqual(outcome["status"], "rebuilt")
result = outcome["result"]
self.assertIn("f_F7", [item["feature_id"] for item in result["feature_results"]])
shell_selectors = [
item for item in result["selector_resolution"]
if item["feature_id"] == "f_F7"
]
self.assertEqual(
{item["selector"]["owner_feature_id"] for item in shell_selectors},
{"f_F5", "f_F6.c1.f_F5", "f_F6.c2.f_F5", "f_F1"},
)
self.assertTrue(all(item["status"] == "resolved" for item in shell_selectors))
self.assertEqual(outcome["status"], "rebuild_failed")
self.assertEqual(outcome["error"]["message"], "f_F7: selector_query_unsupported during incremental replay")
self.assertEqual(outcome["last_executable_prefix"]["last_feature_id"], "f_F6")
def test_face_chamfer_ignores_periodic_seams_00111611(self):
def test_face_chamfer_source_query_is_not_geometry_bound_00111611(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
feature = root / "featurescript_rp/0011/00111611.txt"
if not feature.exists(): self.skipTest("CADFS sample is not installed")
from copy import deepcopy
from engine.cdsl_engine.capabilities import CapabilityAnalyzer, sketch_ids_required_by_contract
from engine.cdsl_engine.runtime import EXECUTORS, ExecutionSession, _execute_node, _selector_edges
from engine.cdsl_engine.sketch_solver import CORE_SHAPE_GENERATORS, resolve_required_sketches
from cadfs_to_cdsl.selector_binding import bind_candidate_selectors
candidate = lower_model(parse_featurescript(feature.read_text(), "00111611"), {}).cdsl
bound, _ = bind_candidate_selectors(candidate)
resolved = resolve_required_sketches(
deepcopy(bound), sketch_ids_required_by_contract(bound), errors={},
)
analysis = CapabilityAnalyzer(
atomic_ids=EXECUTORS, profile_types=CORE_SHAPE_GENERATORS,
).analyze(resolved)
session = ExecutionSession(
sketches={str(item["id"]): item for item in resolved["geometry"]["sketches"]},
nodes={node.feature_id: node for node in analysis.plan},
)
for node in analysis.plan:
if node.feature_id == "f_F5":
edges = _selector_edges(node, session, tangent_propagation=True)
break
_execute_node(node, session)
else:
self.fail("F5 chamfer was not planned")
self.assertEqual(len(edges), 8)
self.assertTrue(all(str(edge.geom_type).endswith("CIRCLE") for edge in edges))
with self.assertRaisesRegex(ValueError, "f_F5: selector_query_unsupported after prefix rebuild"):
bind_candidate_selectors(candidate)
def test_circular_remove_pattern_replays_cut_sources_00159804(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"