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
+719 -55
View File
@@ -6,7 +6,12 @@ from pathlib import Path
from cadfs_to_cdsl.compare import compare_steps
from cadfs_to_cdsl.featurescript_parser import parse_featurescript
from cadfs_to_cdsl.ir import Call
from cadfs_to_cdsl.lowering import _arc, _contours, _global, _number, _record_single_body_successor, lower_model
from cadfs_to_cdsl.lowering import (
UnsupportedCapability, _arc, _contours, _curve_endpoint_tangent,
_direct_hole_location, _direct_line_angle_axis, _direct_sketch_wire_path,
_direct_boolean_intersection_selector, _direct_primary_cut_intersection_selector, _global, _number,
_record_single_body_successor, lower_model,
)
from cadfs_to_cdsl.pipeline import compare_one, convert_one, rebuild_one
from cadfs_to_cdsl.rebuild import rebuild_candidate
from cadfs_to_cdsl.dataset import Sample
@@ -14,7 +19,92 @@ from cadfs_to_cdsl.dataset import scan_dataset
from cadfs_to_cdsl.tests.test_parser import SOURCE, TRANSFORM_SOURCE
LINE_ANGLE_TWO_ENTITY_SOURCE = '''FeatureScript 1511;
import(path : "onshape/std/geometry.fs", version : "1511.0");
const mm = millimeter;
const VERTEX = EntityType.VERTEX;
const EDGE = EntityType.EDGE;
const FACE = EntityType.FACE;
function v(x, y){return vector(x, y);}
annotation { "Feature Type Name" : "Feature" }
export const myFeature = defineFeature(function(context is Context, id is Id, definition is map)
precondition{}
{
{ var Q0; Q0=qCreatedBy(makeId("Top.planeOp"),FACE); var sketch=newSketch(context,id+"F0",{"sketchPlane":qUnion([Q0])}); skLineSegment(sketch,"E0",{"start":v(0,0)*mm,"end":v(10,0)*mm}); skLineSegment(sketch,"E1",{"start":v(0,0)*mm,"end":v(0,10)*mm}); skLineSegment(sketch,"E2",{"start":v(0,5)*mm,"end":v(10,5)*mm}); skPoint(sketch,"P0",{"position":v(0,10)*mm}); skPoint(sketch,"P1",{"position":v(10,0)*mm}); skSolve(sketch); }
{ var Q0; Q0=sQuery(id+"F0.wireOp",EDGE,"E0"); var Q1; Q1=sQuery(id+"F0.wireOp",EDGE,"E1"); cPlane(context,id+"F1",{"entities":qUnion([Q0,Q1]),"cplaneType":CPlaneType.LINE_ANGLE,"angle":0*degree}); }
{ var Q0; Q0=sQuery(id+"F0.wireOp",EDGE,"E0"); var Q1; Q1=sQuery(id+"F0.wireOp",EDGE,"E2"); cPlane(context,id+"F2",{"entities":qUnion([Q0,Q1]),"cplaneType":CPlaneType.LINE_ANGLE,"angle":0*degree}); }
{ var Q0; Q0=sQuery(id+"F0.wireOp",EDGE,"E0"); var Q1; Q1=sQuery(id+"F0.wireOp",VERTEX,"P0"); cPlane(context,id+"F3",{"entities":qUnion([Q0,Q1]),"cplaneType":CPlaneType.LINE_ANGLE,"angle":0*degree}); }
{ var Q0; Q0=sQuery(id+"F0.wireOp",VERTEX,"P0"); var Q1; Q1=sQuery(id+"F0.wireOp",EDGE,"E0"); cPlane(context,id+"F4",{"entities":qUnion([Q0,Q1]),"cplaneType":CPlaneType.LINE_ANGLE,"angle":90*degree,"oppositeDirection":true}); }
{ var Q0; Q0=sQuery(id+"F0.wireOp",EDGE,"E0"); var Q1; Q1=sQuery(id+"F0.wireOp",VERTEX,"P1"); cPlane(context,id+"F5",{"entities":qUnion([Q0,Q1]),"cplaneType":CPlaneType.LINE_ANGLE,"angle":0*degree}); }
{ var Q0; Q0=sQuery(id+"F0.wireOp",EDGE,"E0"); var Q1; Q1=qCreatedBy(id+"F1.planeOp",FACE); cPlane(context,id+"F6",{"entities":qUnion([Q0,Q1]),"cplaneType":CPlaneType.LINE_ANGLE,"angle":0*degree}); }
}'''
class LoweringTests(unittest.TestCase):
def test_direct_sketch_wire_path_accepts_only_one_nonconstruction_line_or_bspline(self):
plane = {"origin_mm": [0.0, 0.0, 0.0], "x_dir": [1.0, 0.0, 0.0], "normal": [0.0, 0.0, 1.0]}
line = {"type": "line", "start": [0.0, 0.0], "end": [10.0, 0.0], "source_entity_id": "E0"}
query = Call("qUnion", [[
Call("qConstructionFilter", [
Call("qBodyType", [
Call("qCreatedBy", [Call("__binary__", ["id", "+", "F0"]), "EDGE"]),
"BodyType.WIRE",
]),
"ConstructionObject.NO",
]),
]])
sketches = {"F0": {"workplane": plane}}
entities = {"F0": {"E0": line, "axis": {"type": "line", "start": [0.0, 0.0], "end": [0.0, 5.0], "construction": True}}}
self.assertEqual(
_direct_sketch_wire_path(
query, sketches, entities, featurescript_version="1511",
standard_library="onshape/std/geometry.fs", standard_library_version="1511.0",
),
("F0", "E0", line, sketches["F0"]),
)
self.assertIsNone(_direct_sketch_wire_path(
query, sketches, entities, featurescript_version="1512",
standard_library="onshape/std/geometry.fs", standard_library_version="1511.0",
))
self.assertIsNone(_direct_sketch_wire_path(
query, sketches, entities, featurescript_version="1511",
standard_library="onshape/std/geometry.fs", standard_library_version="1512.0",
))
self.assertIsNone(_direct_sketch_wire_path(
query, sketches, {"F0": {"E0": line, "E1": {**line, "source_entity_id": "E1"}}},
featurescript_version="1511", standard_library="onshape/std/geometry.fs", standard_library_version="1511.0",
))
self.assertIsNone(_direct_sketch_wire_path(
Call("qBodyType", [Call("qCreatedBy", ["F0", "EDGE"]), "BodyType.WIRE"]),
sketches, entities, featurescript_version="1511",
standard_library="onshape/std/geometry.fs", standard_library_version="1511.0",
))
self.assertIsNone(_direct_sketch_wire_path(
query.args[0][0], sketches, entities, featurescript_version="1511",
standard_library="onshape/std/geometry.fs", standard_library_version="1511.0",
))
def test_direct_hole_location_accepts_only_original_sketch_vertices(self):
plane = {"origin_mm": [0.0, 0.0, 0.0], "x_dir": [1.0, 0.0, 0.0], "normal": [0.0, 0.0, 1.0]}
sketches = {"F2": {"workplane": plane}}
entities = {"F2": {
"P0": {"type": "point", "point": [1.0, 2.0]},
"C0": {"type": "circle", "center": [3.0, 4.0], "radius_mm": 1.0},
"L0": {"type": "line", "start": [5.0, 6.0], "end": [7.0, 8.0]},
"A0": {"type": "arc", "start": [9.0, 10.0], "end": [11.0, 12.0]},
}}
def vertex(token: str) -> Call:
return Call("sQuery", ["F2.wireOp", "VERTEX", token])
self.assertEqual(_direct_hole_location(vertex("P0"), sketches, entities), ([1.0, 2.0, 0.0], plane))
self.assertEqual(_direct_hole_location(vertex("C0.center"), sketches, entities), ([3.0, 4.0, 0.0], plane))
self.assertEqual(_direct_hole_location(vertex("L0.end"), sketches, entities), ([7.0, 8.0, 0.0], plane))
self.assertEqual(_direct_hole_location(vertex("A0.start"), sketches, entities), ([9.0, 10.0, 0.0], plane))
self.assertIsNone(_direct_hole_location(Call("qAdjacent", [vertex("C0.center")]), sketches, entities))
self.assertIsNone(_direct_hole_location(vertex("C0.center.copy"), sketches, entities))
def test_strict_comparison_status_is_not_labeled_approximate(self):
with tempfile.TemporaryDirectory() as tmp:
output = Path(tmp); directory = output / "samples" / "strict-status"; directory.mkdir(parents=True)
@@ -53,7 +143,7 @@ class LoweringTests(unittest.TestCase):
self.assertAlmostEqual(arc["center"][1], 0.5)
self.assertAlmostEqual(arc["radius_mm"], math.hypot(2.5, 0.5))
def test_direct_extrude_and_two_section_loft_swept_edges_bind_for_fillet(self):
def test_direct_extrude_lineage_succeeds_and_unregistered_loft_query_preserves_prefix(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
cases = {
"00000715": ("f_F2", 2, True),
@@ -67,14 +157,37 @@ class LoweringTests(unittest.TestCase):
fillet = next(item for item in result.cdsl["features"] if item["id"] == fillet_id)
self.assertEqual(len(fillet["selectors"]), selector_count)
self.assertTrue(all(item["owner_feature_id"] == fillet["depends_on"][0] for item in fillet["selectors"]))
self.assertTrue(all("bbox_mm" in item["geometry"] for item in fillet["selectors"]))
self.assertEqual(["curve_type" in item["geometry"] for item in fillet["selectors"]], [line_type] * selector_count)
if sample_id == "00000715":
self.assertTrue(all("geometry" not in item for item in fillet["selectors"]))
self.assertTrue(all(
item["selector_intent"]["query_family"] == "SWEPT_EDGE"
and item["selector_intent"]["derivation_policy"] == {"allowed": ["boundary"], "multiplicity": "one"}
and len(item["selector_intent"]["source_entities"]) == 2
for item in fillet["selectors"]
))
else:
self.assertTrue(all("bbox_mm" in item["geometry"] for item in fillet["selectors"]))
self.assertEqual(["curve_type" in item["geometry"] for item in fillet["selectors"]], [line_type] * selector_count)
self.assertTrue(all(
item["selector_intent"]["query_family"] == "SWEPT_EDGE"
and item["selector_intent"]["evidence"] == "feature_script_query"
and item["selector_intent"]["derivation_policy"] == {"allowed": ["continuation"], "multiplicity": "none"}
and item["selector_intent"]["source_query"]["featurescript_version"] == "1793"
for item in fillet["selectors"]
))
with tempfile.TemporaryDirectory() as directory:
rebuilt = Path(directory) / "rebuild.step"
outcome = rebuild_candidate(result.cdsl, rebuilt)
self.assertEqual(outcome["status"], "rebuilt")
comparison = compare_steps(root / "step_abc" / sample_id[:4] / f"{sample_id}.step", rebuilt)
self.assertTrue(comparison["strict"]["passed"])
if sample_id == "00005267":
self.assertEqual(outcome["status"], "rebuild_failed")
self.assertEqual(outcome["error"]["message"], "f_F4: selector_query_unsupported during incremental replay")
self.assertTrue(rebuilt.exists())
self.assertEqual(outcome["last_executable_prefix"]["failed_feature_id"], "f_F4")
self.assertEqual(outcome["last_executable_prefix"]["last_feature_id"], "f_F3")
else:
self.assertEqual(outcome["status"], "rebuilt")
comparison = compare_steps(root / "step_abc" / sample_id[:4] / f"{sample_id}.step", rebuilt)
self.assertTrue(comparison["strict"]["passed"])
def test_swept_edge_requires_one_direct_shared_source_endpoint(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
@@ -93,7 +206,7 @@ class LoweringTests(unittest.TestCase):
"message": "swept edge source endpoint provenance is unsupported",
}])
def test_direct_full_revolve_swept_circle_edges_bind_for_dressups(self):
def test_full_revolve_swept_circle_edges_remain_deferred_with_prefixes(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
for sample_id, dressup_id, selector_count in (
("00048326", "f_F3", 2),
@@ -109,14 +222,17 @@ class LoweringTests(unittest.TestCase):
selector["geometry"].get("curve_type") == "circle"
and len(selector["geometry"].get("circle_center_mm") or []) == 3
and selector["geometry"].get("radius_mm", 0) > 0
and selector["selector_intent"]["query_family"] == "SWEPT_EDGE"
and selector["selector_intent"]["evidence"] == "feature_script_query"
and selector["selector_intent"]["derivation_policy"]["multiplicity"] == "none"
for selector in dressup["selectors"]
))
with tempfile.TemporaryDirectory() as directory:
rebuilt = Path(directory) / "rebuild.step"
outcome = rebuild_candidate(result.cdsl, rebuilt)
self.assertEqual(outcome["status"], "rebuilt")
comparison = compare_steps(root / "step_abc" / sample_id[:4] / f"{sample_id}.step", rebuilt)
self.assertTrue(comparison["strict"]["passed"])
self.assertEqual(outcome["status"], "rebuild_failed")
self.assertEqual(outcome["error"]["message"], f"{dressup_id}: selector_query_unsupported during incremental replay")
self.assertEqual(outcome["last_executable_prefix"]["failed_feature_id"], dressup_id)
def test_full_revolve_swept_circle_requires_one_direct_shared_source_endpoint(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
@@ -135,7 +251,7 @@ class LoweringTests(unittest.TestCase):
"message": "swept edge source endpoint provenance is unsupported",
}, result.diagnostics)
def test_equivalent_imprint_profile_retains_direct_full_revolve_swept_edges(self):
def test_equivalent_imprint_profile_preserves_but_does_not_bind_full_revolve_queries(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
source = root / "featurescript_rp/0011/00112257.txt"
result = lower_model(parse_featurescript(source.read_text(), "00112257"), {})
@@ -144,17 +260,16 @@ class LoweringTests(unittest.TestCase):
self.assertEqual(len(fillet["selectors"]), 5)
self.assertTrue(all(
selector["geometry"].get("curve_type") == "circle"
and selector["selector_intent"]["query_family"] == "SWEPT_EDGE"
and selector["selector_intent"]["derivation_policy"]["multiplicity"] == "none"
for selector in fillet["selectors"]
))
from cadfs_to_cdsl.selector_binding import bind_candidate_selectors
prefix = dict(result.cdsl)
prefix["features"] = result.cdsl["features"][:2]
bound, evidence = bind_candidate_selectors(prefix)
selectors = next(item for item in bound["features"] if item["id"] == "f_F2")["selectors"]
self.assertEqual(len(evidence), 1)
self.assertEqual(len(selectors), 5)
self.assertTrue(all(selector.get("snapshot_id") for selector in selectors))
with self.assertRaisesRegex(ValueError, "f_F2: selector_query_unsupported after prefix rebuild"):
bind_candidate_selectors(prefix)
def test_changed_imprint_profile_does_not_inherit_full_revolve_source_edges(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
@@ -689,6 +804,30 @@ class LoweringTests(unittest.TestCase):
self.assertEqual(end["offset_mm"], 3.0)
self.assertEqual(end["reference"]["kind"], "body")
self.assertEqual(end["reference"]["owner_feature_id"], "f_F1")
self.assertEqual(end["reference"]["source"], "runtime_snapshot")
self.assertNotIn("stable_id", end["reference"])
self.assertEqual(end["reference"]["selector_intent"]["query_family"], "SWEPT_BODY")
self.assertEqual(end["reference"]["selector_intent"]["evidence"], "active_body_member")
self.assertEqual(
end["reference"]["selector_intent"]["derivation_policy"],
{"allowed": ["boundary"], "multiplicity": "one"},
)
fillet = next(item for item in result.cdsl["features"] if item["id"] == "f_F4")
self.assertEqual(len(fillet["selectors"]), 1)
self.assertEqual(fillet["selectors"][0]["selector_intent"]["query_family"], "CAP_EDGE")
self.assertEqual(
fillet["selectors"][0]["selector_intent"]["derivation_policy"],
{"allowed": ["boundary", "continuation"], "multiplicity": "one"},
)
from engine.cdsl_engine.semantic_validation import validate_semantic_cdsl
mixed_evidence = deepcopy(result.cdsl)
mixed_reference = next(
item for item in mixed_evidence["features"] if item["id"] == "f_F3"
)["params"]["end_condition"]["reference"]
mixed_reference["stable_id"] = "body:f_F1"
with self.assertRaisesRegex(ValueError, "invalid SWEPT_BODY member contract"):
validate_semantic_cdsl(mixed_evidence)
def test_up_to_surface_extrude_captures_the_cap_face(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
@@ -700,9 +839,24 @@ class LoweringTests(unittest.TestCase):
self.assertEqual(end["type"], "up_to_surface")
self.assertEqual(end["reference"]["kind"], "face")
self.assertEqual(end["reference"]["owner_feature_id"], "f_F1")
self.assertEqual(end["reference"]["geometry"]["plane_offset_mm"], 0.0)
self.assertEqual(end["reference"]["output_role"], "extrude.start")
self.assertEqual(end["reference"]["source"], "runtime_snapshot")
self.assertNotIn("geometry", end["reference"])
self.assertEqual(end["reference"]["selector_intent"]["query_family"], "CAP_FACE")
self.assertEqual(end["reference"]["selector_intent"]["evidence"], "operation_role")
swept = next(item for item in result.cdsl["features"] if item["id"] == "f_F6")
self.assertEqual(swept["params"]["end_condition"]["reference"]["geometry"]["radius_mm"], 24.3)
swept_reference = swept["params"]["end_condition"]["reference"]
self.assertEqual(swept_reference["selector_intent"]["query_family"], "SWEPT_FACE")
self.assertEqual(
swept_reference["selector_intent"]["derivation_policy"],
{"allowed": ["boundary", "continuation"], "multiplicity": "one"},
)
self.assertEqual(swept_reference["selector_intent"]["evidence"], "kernel_history")
self.assertEqual(swept_reference["selector_intent"]["source_entity"], {"sketch_id": "F0", "entity_id": "E0"})
self.assertEqual(swept_reference["source"], "runtime_snapshot")
self.assertNotIn("stable_id", swept_reference)
self.assertNotIn("binding_feature_id", swept_reference)
self.assertNotIn("geometry", swept_reference)
mirror = next(item for item in result.cdsl["features"] if item["id"] == "f_F18")
self.assertTrue(mirror["params"]["mirror_current_body"])
@@ -756,6 +910,160 @@ class LoweringTests(unittest.TestCase):
self.assertAlmostEqual(plane["origin_mm"][0], -12.15)
self.assertAlmostEqual(plane["origin_mm"][1], -24.3 * math.sqrt(3) / 2)
def test_line_angle_direct_circle_uses_the_featurescript_circle_axis(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
feature = root / "featurescript_rp/0003/00030209.txt"
if not feature.exists(): self.skipTest("CADFS sample is not installed")
result = lower_model(parse_featurescript(feature.read_text(), "00030209"), {})
self.assertEqual(result.status, "converted_partial")
planes = {
item["id"]: item["params"]["plane"]
for item in result.cdsl["features"]
if item["atomic_id"] == "reference_plane"
}
expected_centers = {
"f_F1": [45.78, 0.0, 26.2],
"f_F2": [13.9, 0.0, 26.5],
"f_F4": [-17.08, 0.0, 23.46],
"f_F5": [-44.4, 0.0, 19.8],
}
self.assertEqual(set(planes), set(expected_centers))
for feature_id, center in expected_centers.items():
with self.subTest(feature_id=feature_id):
plane = planes[feature_id]
self.assertEqual(plane["origin_mm"], center)
self.assertEqual(plane["x_dir"], [0.0, -1.0, 0.0])
self.assertAlmostEqual(plane["normal"][0], 1.0)
self.assertAlmostEqual(plane["normal"][1], 0.0)
self.assertAlmostEqual(plane["normal"][2], 0.0)
self.assertFalse(any(
diagnostic.get("operation") == "cPlane"
for diagnostic in result.diagnostics
))
opposite_source = feature.read_text().replace(
'"angle" : 90 * degree, "width"',
'"angle" : 90 * degree, "oppositeDirection" : true, "width"',
1,
)
opposite = lower_model(parse_featurescript(opposite_source, "line-angle-circle-opposite"), {})
opposite_plane = next(
item for item in opposite.cdsl["features"] if item["id"] == "f_F1"
)["params"]["plane"]
self.assertAlmostEqual(opposite_plane["normal"][0], -1.0)
self.assertAlmostEqual(opposite_plane["normal"][1], 0.0)
self.assertAlmostEqual(opposite_plane["normal"][2], 0.0)
def test_line_angle_two_entities_follow_axis_plane_order_and_swap_rules(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
cases = {
# F8 selects Right plane first, then the direct F7 line. The
# standard library must swap them before calculating the frame.
"00298026": ("f_F8", [0.0, -1.0, 0.0], [0.43837114678907757, 0.0, -0.898794046299167]),
# F1 supplies the direct line first and the default plane second.
"00586156": ("f_F1", [0.0, 0.0, 1.0], [-math.sqrt(0.5), -math.sqrt(0.5), 0.0]),
}
for sample_id, (feature_id, expected_x, expected_normal) in cases.items():
with self.subTest(sample_id=sample_id):
source = root / "featurescript_rp" / sample_id[:4] / f"{sample_id}.txt"
result = lower_model(parse_featurescript(source.read_text(), sample_id), {})
plane = next(item for item in result.cdsl["features"] if item["id"] == feature_id)["params"]["plane"]
self.assertEqual(plane["x_dir"], expected_x)
for actual, expected in zip(plane["normal"], expected_normal):
self.assertAlmostEqual(actual, expected)
self.assertFalse(any(
diagnostic.get("operation") == "cPlane"
and diagnostic.get("feature_id") == feature_id.removeprefix("f_")
for diagnostic in result.diagnostics
))
def test_line_angle_two_direct_entities_cover_axis_point_and_degenerate_contracts(self):
result = lower_model(parse_featurescript(LINE_ANGLE_TWO_ENTITY_SOURCE, "line-angle-two-entities"), {})
self.assertEqual(result.status, "converted_partial")
planes = {
item["id"]: item["params"]["plane"]
for item in result.cdsl["features"]
if item["atomic_id"] == "reference_plane"
}
# Nonparallel axes, parallel offset axes, and an axis plus a direct
# point each use the exact source direction, rather than a sketch
# workplane frame.
for feature_id in ("f_F1", "f_F2", "f_F3"):
self.assertEqual(planes[feature_id]["x_dir"], [1.0, 0.0, 0.0])
self.assertEqual(planes[feature_id]["normal"], [0.0, 0.0, 1.0])
# A point selected before its axis triggers FeatureScript's swap. The
# signed angle also follows oppositeDirection.
self.assertEqual(planes["f_F4"]["x_dir"], [1.0, 0.0, 0.0])
self.assertAlmostEqual(planes["f_F4"]["normal"][0], 0.0)
self.assertAlmostEqual(planes["f_F4"]["normal"][1], 1.0)
self.assertAlmostEqual(planes["f_F4"]["normal"][2], 0.0)
# A cPlane output is represented as qCreatedBy(...planeOp, FACE),
# not a sketch source. It is accepted only because F1 is an existing
# reference-plane frame, and it supplies the true zero-angle side.
self.assertEqual(planes["f_F6"]["x_dir"], [1.0, 0.0, 0.0])
self.assertEqual(planes["f_F6"]["normal"], [0.0, 0.0, -1.0])
self.assertIn({
"code": "feature_deferred",
"feature_id": "F5",
"operation": "cPlane",
"message": "line-angle reference selection is degenerate",
}, result.diagnostics)
def test_line_angle_direct_axis_refuses_a_topology_derived_query(self):
direct = Call("sQuery", ["F0.wireOp", "EDGE", "E0"], 1)
derived = Call("makeQuery", [
"F1.opExtrude", "CAP_EDGE", "EDGE",
{"derivedFrom": direct, "isStart": False},
], 2)
sketch_by_source = {"F0": {"workplane": {
"origin_mm": [0.0, 0.0, 0.0], "x_dir": [1.0, 0.0, 0.0], "normal": [0.0, 0.0, 1.0],
}}}
entity_by_sketch = {"F0": {"E0": {
"type": "line", "start": [0.0, 0.0], "end": [10.0, 0.0], "source_entity_id": "E0",
}}}
self.assertIsNotNone(_direct_line_angle_axis(direct, sketch_by_source, entity_by_sketch))
self.assertIsNone(_direct_line_angle_axis(derived, sketch_by_source, entity_by_sketch))
def test_line_angle_direct_axis_refuses_a_query_wrapper_and_keeps_the_prefix(self):
direct = Call("sQuery", ["F0.wireOp", "EDGE", "E0"], 1)
wrapped = Call("qAdjacent", [direct, "VERTEX", "EDGE"], 2)
sketch_by_source = {"F0": {"workplane": {
"origin_mm": [0.0, 0.0, 0.0], "x_dir": [1.0, 0.0, 0.0], "normal": [0.0, 0.0, 1.0],
}}}
entity_by_sketch = {"F0": {"E0": {
"type": "line", "start": [0.0, 0.0], "end": [10.0, 0.0], "source_entity_id": "E0",
}}}
self.assertIsNone(_direct_line_angle_axis(wrapped, sketch_by_source, entity_by_sketch))
model = parse_featurescript(LINE_ANGLE_TWO_ENTITY_SOURCE, "line-angle-wrapped-axis")
plane = next(item for item in model.features if item.feature_id == "F1")
plane.params["entities"].args[0][0] = wrapped
result = lower_model(model, {})
self.assertIn({
"code": "feature_deferred",
"feature_id": "F1",
"operation": "cPlane",
"message": "line-angle reference selection is unsupported",
}, result.diagnostics)
self.assertIn("f_F2", {item["id"] for item in result.cdsl["features"]})
def test_line_angle_rejects_a_circle_center_vertex_as_an_axis(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
feature = root / "featurescript_rp/0003/00030209.txt"
if not feature.exists(): self.skipTest("CADFS sample is not installed")
source = feature.read_text().replace(
'Q0=sQuery(id+"F0.wireOp",EDGE,"E0");',
'Q0=sQuery(id+"F0.wireOp",VERTEX,"E0.center");',
1,
)
result = lower_model(parse_featurescript(source, "line-angle-circle-center"), {})
self.assertIn({
"code": "feature_deferred",
"feature_id": "F1",
"operation": "cPlane",
"message": "line-angle reference selection is unsupported",
}, result.diagnostics)
def test_open_nonconstruction_geometry_is_preserved_as_reference(self):
source = SOURCE.replace('skSolve(sketch);', 'skLineSegment(sketch, "open", {"start":v(0, 0) * mm, "end":v(20, 0) * mm}); skSolve(sketch);', 1)
result = lower_model(parse_featurescript(source, "open-profile"), {})
@@ -931,7 +1239,7 @@ class LoweringTests(unittest.TestCase):
[("outer", 25.5), ("inner", 19.0)],
)
def test_mixed_extrude_rebuilds_surface_limited_chamfer_history(self):
def test_mixed_extrude_preserves_prefix_when_cap_edge_chamfer_query_is_deferred(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
feature = root / "featurescript_rp/0071/00710855.txt"
if not feature.exists(): self.skipTest("CADFS sample is not installed")
@@ -941,10 +1249,10 @@ class LoweringTests(unittest.TestCase):
from cadfs_to_cdsl.rebuild import rebuild_candidate
rebuilt = rebuild_candidate(result.cdsl, Path(tmp) / "rebuilt.step")
self.assertEqual(rebuilt["status"], "rebuilt")
chamfer = next(item for item in rebuilt["result"]["feature_results"] if item["feature_id"] == "f_F10")
self.assertIn("chamfer_surface_limited", [item["code"] for item in chamfer["diagnostics"]])
self.assertAlmostEqual(rebuilt["result"]["volume_mm3"], 72577.33528261917, places=6)
self.assertEqual(rebuilt["status"], "rebuild_failed")
self.assertEqual(rebuilt["error"]["message"], "f_F10: selector_query_unsupported during incremental replay")
self.assertEqual(rebuilt["last_executable_prefix"]["failed_feature_id"], "f_F10")
self.assertEqual(rebuilt["last_executable_prefix"]["last_feature_id"], "f_F9")
def test_fit_spline_loft_lowers_to_executable_loft_add(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
@@ -1048,7 +1356,7 @@ class LoweringTests(unittest.TestCase):
"kind": "face", "owner_feature_id": "f_F3", "output_role": "extrude.start",
"source": "runtime_snapshot", "confidence": 1.0,
})
self.assertEqual(selector["selector_intent_version"], "1.0")
self.assertNotIn("selector_intent_version", selector)
self.assertEqual(selector["selector_intent"]["query_family"], "CAP_FACE")
from engine.cdsl_engine.runtime import rebuild_cdsl
@@ -1130,21 +1438,19 @@ class LoweringTests(unittest.TestCase):
})
# F9 rotates F4 only after F5/F7 have consumed it. It cannot be baked
# into the earlier F4 sketch; the unsupported body continuation stays
# explicit and F1--F7 remains an executable prefix.
# into the earlier F4 sketch. The CAP_EDGE geometry is preserved for
# diagnostics, but no source-qualified cap-edge lineage exists for
# this two-sided profile contract.
prefix = deepcopy(result.cdsl)
prefix["features"] = [
item for item in prefix["features"]
if item["id"] in {"f_F1", "f_F2", "f_F4", "f_F5", "f_F7"}
]
from engine.cdsl_engine.runtime import rebuild_cdsl
with tempfile.TemporaryDirectory() as directory:
rebuilt = rebuild_cdsl(prefix, Path(directory) / "two-sided-cap-prefix.step")
self.assertFalse(rebuilt["runtime_diagnostics"])
self.assertEqual(
[item["feature_id"] for item in rebuilt["feature_results"]],
["f_F1", "f_F2", "f_F4", "f_F5", "f_F7"],
)
rebuilt = rebuild_candidate(prefix, Path(directory) / "two-sided-cap-prefix.step")
self.assertEqual(rebuilt["status"], "rebuild_failed")
self.assertEqual(rebuilt["error"]["message"], "f_F7: selector_query_unsupported during incremental replay")
self.assertEqual(rebuilt["last_executable_prefix"]["last_feature_id"], "f_F5")
from engine.cdsl_engine.runtime import analyze_cdsl
f9 = next(item for item in analyze_cdsl(result.cdsl).as_dict()["feature_results"] if item["feature_id"] == "f_F9")
@@ -1338,6 +1644,41 @@ class LoweringTests(unittest.TestCase):
["E14", "E14"],
)
def test_direct_imprint_profile_preserves_selector_intent_and_f1_checkpoint(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
feature = root / "featurescript_rp/0035/00354246.txt"
if not feature.exists(): self.skipTest("CADFS sample is not installed")
result = lower_model(parse_featurescript(feature.read_text(), "00354246"), {})
self.assertEqual(result.status, "converted_complete")
features = {item["id"]: item for item in result.cdsl["features"]}
sketches = {item["id"]: item for item in result.cdsl["geometry"]["sketches"]}
profile = sketches[features["f_F1"]["sketch_id"]]["profile"]
self.assertEqual(profile["type"], "planar_imprint")
self.assertEqual(profile["selections"], [
{"source_entity_id": "E6.MirrorCS", "face_side": -1.0},
{"source_entity_id": "E0.MirrorCS", "face_side": 1.0},
])
selectors = features["f_F2"]["selectors"]
self.assertEqual(len(selectors), 7)
self.assertTrue(all(
selector["selector_intent"]["query_family"] == "SWEPT_FACE"
and selector["selector_intent"]["derivation_policy"] == {
"allowed": ["continuation"], "multiplicity": "none",
}
for selector in selectors
))
self.assertTrue(any("geometry" not in selector for selector in selectors))
with tempfile.TemporaryDirectory() as directory:
rebuilt = Path(directory) / "rebuild.step"
outcome = rebuild_candidate(result.cdsl, rebuilt)
self.assertEqual(outcome["status"], "rebuild_failed")
self.assertEqual(outcome["error"]["message"], "f_F2: selector_query_unsupported during incremental replay")
self.assertTrue(rebuilt.exists())
self.assertEqual(outcome["last_executable_prefix"]["last_feature_id"], "f_F1")
def test_direct_translation_transform_updates_the_source_revolve(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
feature = root / "featurescript_rp/0011/00111611.txt"
@@ -1512,6 +1853,121 @@ class LoweringTests(unittest.TestCase):
diagnostics = [item for item in result.diagnostics if item.get("operation") == "cPlane"]
self.assertFalse(diagnostics)
def test_curve_point_plane_uses_an_explicit_bspline_endpoint_tangent(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
feature = root / "featurescript_rp/0082/00827798.txt"
if not feature.exists(): self.skipTest("CADFS sample is not installed")
result = lower_model(parse_featurescript(feature.read_text(), "00827798"), {})
plane = next(item for item in result.cdsl["features"] if item["id"] == "f_F1")
self.assertEqual(plane["atomic_id"], "reference_plane")
self.assertFalse(any(
item.get("operation") == "cPlane" and item.get("feature_id") == "F1"
for item in result.diagnostics
))
expected = [107.86, 0.0, -176.04]
magnitude = math.sqrt(sum(value * value for value in expected))
for actual, value in zip(plane["params"]["plane"]["normal"], expected):
self.assertAlmostEqual(actual, value / magnitude)
second_feature = root / "featurescript_rp/0084/00845891.txt"
if second_feature.exists():
second = lower_model(parse_featurescript(second_feature.read_text(), "00845891"), {})
self.assertEqual(
next(item for item in second.cdsl["features"] if item["id"] == "f_F3")["atomic_id"],
"reference_plane",
)
self.assertFalse(any(
item.get("operation") == "cPlane" and item.get("feature_id") == "F3"
for item in second.diagnostics
))
without_end_derivative = feature.read_text().replace(
', "endDerivative": vector(107.86, -176.04) * mm', "", 1,
)
rejected = lower_model(parse_featurescript(without_end_derivative, "00827798-no-end-derivative"), {})
self.assertIn({
"code": "unsupported_engine_capability",
"capability": "reference_plane:curve_point",
"feature_id": "F1",
"operation": "cPlane",
"message": "CURVE_POINT B-spline endpoint has no exported tangent",
}, rejected.diagnostics)
def test_curve_endpoint_tangent_uses_arc_orientation_without_a_chord_fallback(self):
arc = {
"type": "arc", "start": [1.0, 0.0], "end": [0.0, 1.0],
"center": [0.0, 0.0], "clockwise": False,
}
self.assertEqual(_curve_endpoint_tangent(arc, "start"), [0.0, 1.0])
self.assertEqual(_curve_endpoint_tangent(arc, "end"), [-1.0, 0.0])
with self.assertRaisesRegex(UnsupportedCapability, "direct source-curve endpoint"):
_curve_endpoint_tangent(arc, "internal")
def test_curve_point_plane_uses_exact_bspline_interpolation_vertex_tangents(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
cases = {
"00456498": ("f_F1", "F1", [141.23, 0.0, -13.99]),
"00812557": ("f_F2", "F2", [0.0, 191.15, -139.74]),
}
for sample_id, (cdsl_feature_id, source_feature_id, expected) in cases.items():
with self.subTest(sample_id=sample_id):
source = root / "featurescript_rp" / sample_id[:4] / f"{sample_id}.txt"
if not source.exists():
self.skipTest("CADFS sample is not installed")
result = lower_model(parse_featurescript(source.read_text(), sample_id), {})
plane = next(item for item in result.cdsl["features"] if item["id"] == cdsl_feature_id)
self.assertEqual(plane["atomic_id"], "reference_plane")
magnitude = math.sqrt(sum(value * value for value in expected))
for actual, value in zip(plane["params"]["plane"]["normal"], expected):
self.assertAlmostEqual(actual, value / magnitude, places=8)
self.assertFalse(any(
item.get("operation") == "cPlane" and item.get("feature_id") == source_feature_id
for item in result.diagnostics
))
no_derivatives = (root / "featurescript_rp/0045/00456498.txt").read_text().replace(
', "startDerivative": vector(0, 64.52) * mm, "endDerivative": vector(141.23, -13.99) * mm', "", 1,
)
rejected = lower_model(parse_featurescript(no_derivatives, "00456498-no-derivatives"), {})
self.assertIn({
"code": "unsupported_engine_capability",
"capability": "reference_plane:curve_point",
"feature_id": "F1",
"operation": "cPlane",
"message": "CURVE_POINT B-spline interpolation point has no complete source interpolation data",
}, rejected.diagnostics)
derived = (root / "featurescript_rp/0045/00456498.txt").read_text().replace(
'"E0.2.internal"', '"E0.2.endSnap0"', 1,
)
rejected_derived = lower_model(parse_featurescript(derived, "00456498-derived-vertex"), {})
self.assertIn({
"code": "unsupported_engine_capability",
"capability": "reference_plane:curve_point",
"feature_id": "F1",
"operation": "cPlane",
"message": "CURVE_POINT point must name a direct source-curve endpoint or B-spline interpolation vertex",
}, rejected_derived.diagnostics)
def test_curve_point_plane_uses_a_direct_arc_endpoint_tangent(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
feature = root / "featurescript_rp/0005/00057273.txt"
if not feature.exists(): self.skipTest("CADFS sample is not installed")
source = feature.read_text().replace(
'EDGE,"E1"', 'EDGE,"E0"', 1,
).replace(
'VERTEX,"E1.end"', 'VERTEX,"E0.end"', 1,
)
result = lower_model(parse_featurescript(source, "00057273-arc-curve-point"), {})
plane = next(item for item in result.cdsl["features"] if item["id"] == "f_F1")
self.assertEqual(plane["atomic_id"], "reference_plane")
self.assertEqual(plane["params"]["plane"]["origin_mm"], [-24.6, 0.0, 0.0])
self.assertEqual(plane["params"]["plane"]["normal"], [0.0, -1.0, 0.0])
self.assertFalse(any(
item.get("operation") == "cPlane" and item.get("feature_id") == "F1"
for item in result.diagnostics
))
def test_offset_reference_plane_honors_opposite_direction_without_reversing_its_frame(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
feature = root / "featurescript_rp/0077/00777619.txt"
@@ -1571,7 +2027,7 @@ class LoweringTests(unittest.TestCase):
))
self.assertNotIn("F8", {item.get("feature_id") for item in result.diagnostics})
def test_shell_lowers_and_executes_direct_linear_extrude_swept_faces(self):
def test_shell_resolves_direct_prism_swept_faces_through_kernel_lineage(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
feature = root / "featurescript_rp/0059/00594348.txt"
if not feature.exists(): self.skipTest("CADFS sample is not installed")
@@ -1582,18 +2038,105 @@ class LoweringTests(unittest.TestCase):
shell = result.cdsl["features"][shell_index]
self.assertEqual(shell["atomic_id"], "shell")
self.assertEqual([item["owner_feature_id"] for item in shell["selectors"]], ["f_F1", "f_F1"])
self.assertTrue(all("normal" in item["geometry"] for item in shell["selectors"]))
prefix = deepcopy(result.cdsl)
prefix["features"] = prefix["features"][:shell_index + 1]
self.assertTrue(all("geometry" not in item and "stable_id" not in item for item in shell["selectors"]))
self.assertTrue(all(item["selector_intent"]["query_family"] == "SWEPT_FACE" for item in shell["selectors"]))
self.assertTrue(all(item["selector_intent"]["evidence"] == "kernel_history" for item in shell["selectors"]))
self.assertTrue(all(
item["selector_intent"]["derivation_policy"] == {"allowed": ["boundary"], "multiplicity": "one"}
for item in shell["selectors"]
))
with tempfile.TemporaryDirectory() as directory:
outcome = rebuild_candidate(prefix, Path(directory) / "swept-face-shell.step")
outcome = rebuild_candidate(result.cdsl, Path(directory) / "swept-face-shell.step")
self.assertEqual(outcome["status"], "rebuilt")
self.assertEqual(
[item["status"] for item in outcome["result"]["feature_results"]],
["executed", "executed"],
resolutions = [
item for item in outcome["result"]["selector_resolution"]
if item["feature_id"] == "f_F2"
]
self.assertEqual(len(resolutions), 2)
self.assertTrue(all(item["resolution_mode"] == "kernel_lineage" for item in resolutions))
self.assertTrue(all(
item["evidence"]["relations"][0]["source_kind"] == "edge"
and item["evidence"]["relations"][0]["result_kind"] == "face"
for item in resolutions
))
def test_shell_resolves_immediate_direct_prism_cap_faces_through_output_roles(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
cases = (("00212904", "extrude.start"), ("00789939", "extrude.end"))
for sample_id, output_role in cases:
with self.subTest(sample_id=sample_id):
feature = root / "featurescript_rp" / sample_id[:4] / f"{sample_id}.txt"
if not feature.exists(): self.skipTest("CADFS sample is not installed")
result = lower_model(parse_featurescript(feature.read_text(encoding="utf-8"), sample_id), {})
self.assertEqual(result.status, "converted_complete")
shell_index = next(index for index, item in enumerate(result.cdsl["features"]) if item["id"] == "f_F2")
shell = result.cdsl["features"][shell_index]
self.assertEqual(shell["atomic_id"], "shell")
self.assertEqual(len(shell["selectors"]), 1)
selector = shell["selectors"][0]
self.assertEqual(selector["owner_feature_id"], "f_F1")
self.assertEqual(selector["output_role"], output_role)
self.assertEqual(selector["selector_intent"]["query_family"], "CAP_FACE")
self.assertEqual(selector["selector_intent"]["evidence"], "operation_role")
self.assertNotIn("stable_id", selector)
self.assertNotIn("binding_feature_id", selector)
self.assertNotIn("geometry", selector)
prefix = deepcopy(result.cdsl)
prefix["features"] = prefix["features"][:shell_index + 1]
with tempfile.TemporaryDirectory() as directory:
outcome = rebuild_candidate(prefix, Path(directory) / f"{sample_id}-cap-face-shell.step")
self.assertEqual(outcome["status"], "rebuilt")
resolution = next(
item for item in outcome["result"]["selector_resolution"]
if item["feature_id"] == "f_F2"
)
self.assertEqual(resolution["status"], "resolved")
self.assertEqual(resolution["resolution_mode"], "operation_role")
self.assertEqual(resolution["selected"]["output_roles"], [output_role])
def test_shell_direct_prism_swept_face_rejects_unregistered_source_version(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
feature = root / "featurescript_rp/0059/00594348.txt"
if not feature.exists(): self.skipTest("CADFS sample is not installed")
source = feature.read_text(encoding="utf-8").replace("FeatureScript 1511;", "FeatureScript 1793;", 1)
result = lower_model(parse_featurescript(source, "00594348-version-1793"), {})
shell = next(item for item in result.cdsl["features"] if item["id"] == "f_F2")
self.assertTrue(all(
item["selector_intent"]["evidence"] == "feature_script_query"
and item["selector_intent"]["derivation_policy"]["multiplicity"] == "none"
for item in shell["selectors"]
))
with tempfile.TemporaryDirectory() as directory:
outcome = rebuild_candidate(result.cdsl, Path(directory) / "swept-face-shell-1793.step")
self.assertEqual(outcome["status"], "rebuild_failed")
self.assertEqual(outcome["error"]["message"], "f_F2: selector_query_unsupported during incremental replay")
self.assertEqual(outcome["last_executable_prefix"]["last_feature_id"], "f_F1")
def test_shell_direct_prism_swept_face_rejects_unregistered_standard_library_revision(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
feature = root / "featurescript_rp/0059/00594348.txt"
if not feature.exists(): self.skipTest("CADFS sample is not installed")
source = feature.read_text(encoding="utf-8").replace(
'import(path : "onshape/std/geometry.fs", version : "1511.0");',
'import(path : "onshape/std/geometry.fs", version : "1512.0");',
1,
)
self.assertFalse(outcome["result"].get("diagnostics", []))
result = lower_model(parse_featurescript(source, "00594348-library-1512"), {})
shell = next(item for item in result.cdsl["features"] if item["id"] == "f_F2")
self.assertTrue(all(
item["selector_intent"]["source_query"]["featurescript_version"] == "1511"
and item["selector_intent"]["source_query"]["standard_library"] == "onshape/std/geometry.fs"
and item["selector_intent"]["source_query"]["standard_library_version"] == "1512.0"
for item in shell["selectors"]
))
with tempfile.TemporaryDirectory() as directory:
outcome = rebuild_candidate(result.cdsl, Path(directory) / "swept-face-shell-library-1512.step")
self.assertEqual(outcome["status"], "rebuild_failed")
self.assertEqual(outcome["error"]["message"], "f_F2: selector_query_unsupported during incremental replay")
self.assertEqual(outcome["last_executable_prefix"]["last_feature_id"], "f_F1")
def test_shell_offset_face_requires_true_dependency_cap_evidence(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
@@ -1623,13 +2166,14 @@ class LoweringTests(unittest.TestCase):
outcome = rebuild_candidate(result.cdsl, rebuilt)
self.assertTrue(rebuilt.exists())
self.assertEqual(outcome["status"], "rebuild_failed")
self.assertEqual(outcome["error"]["type"], "RuntimeExecutionError")
self.assertEqual(outcome["error"]["type"], "ValueError")
self.assertEqual(outcome["error"]["message"], "f_F5: selector_query_unsupported during incremental replay")
self.assertIn("bound_cdsl", outcome)
self.assertEqual(outcome["last_executable_prefix"]["failed_feature_id"], "f_F5")
self.assertEqual(outcome["last_executable_prefix"]["last_feature_id"], "f_F4")
self.assertEqual(outcome["last_executable_prefix"]["result"]["solid_count"], 1)
def test_pattern_copy_selector_binding_rebuilds_the_full_history(self):
def test_pattern_copy_intersection_query_preserves_its_prefix_without_instance_geometry_binding(self):
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")
@@ -1639,14 +2183,15 @@ class LoweringTests(unittest.TestCase):
rebuilt = Path(directory) / "selector-prefix.step"
outcome = rebuild_candidate(result.cdsl, rebuilt)
self.assertTrue(rebuilt.exists())
self.assertEqual(outcome["status"], "rebuilt")
resolved = [
item for item in outcome["result"]["selector_resolution"]
if item["feature_id"] == "f_F7"
and item["selector"].get("owner_feature_id") == "f_F4.c4.f_F1"
]
self.assertEqual(len(resolved), 2)
self.assertTrue(all(item["status"] == "resolved" for item in resolved))
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")
reference = next(item for item in result.cdsl["features"] if item["id"] == "f_F7")["params"]["end_condition"]["reference"]
self.assertEqual(reference["selector_intent"]["query_family"], "INTERSECT")
self.assertEqual(
[item["selector_intent"]["query_family"] for item in reference["intersection_of"][:2]],
["COPY", "COPY"],
)
def test_shell_rejects_swept_faces_without_direct_linear_extrude_ownership(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
@@ -1698,6 +2243,10 @@ class LoweringTests(unittest.TestCase):
def test_cap_face_selector_retains_versioned_provenance_intent(self):
source = SOURCE.replace(
"FeatureScript 1511;",
'FeatureScript 1511;\nimport(path : "onshape/std/geometry.fs", version : "1511.0");',
1,
).replace(
'\n});\n',
'\n extrude(context, id + "F2", {"entities":makeQuery(id + "F1.opExtrude", "CAP_FACE", FACE, {"isStart":false}), "depth":10 * mm});\n});\n',
)
@@ -1705,12 +2254,127 @@ class LoweringTests(unittest.TestCase):
self.assertEqual(result.status, "converted_complete")
selector = next(item for item in result.cdsl["features"] if item["id"] == "f_F2")["selectors"][0]
self.assertEqual(selector["selector_intent"]["query_family"], "CAP_FACE")
self.assertEqual(selector["selector_intent_version"], "1.0")
self.assertNotIn("selector_intent_version", selector)
self.assertEqual(selector["selector_intent"]["source_query"]["featurescript_version"], "1511")
self.assertEqual(selector["selector_intent"]["source_query"]["standard_library"], "onshape/std/geometry.fs")
self.assertEqual(selector["selector_intent"]["source_query"]["standard_library_version"], "1511.0")
self.assertEqual(selector["selector_intent"]["evidence"], "operation_role")
from engine.cdsl_engine.semantic_validation import validate_semantic_cdsl
self.assertTrue(validate_semantic_cdsl(result.cdsl)["future_rebuild_ready"])
def test_direct_boolean_intersection_lowering_keeps_two_source_contract(self):
base = lower_model(parse_featurescript(SOURCE, "intersection-intent"), {}).cdsl
sketch = base["geometry"]["sketches"][0]
direct = {**base["features"][0], "id": "f_F2", "name": "F2"}
feature_by_id = {
"f_F1": base["features"][0],
"f_F2": direct,
"f_F3": {
"id": "f_F3", "atomic_id": "boolean_bodies",
"params": {
"operation": "subtract", "target_feature_ids": ["f_F1"],
"tool_feature_ids": ["f_F2"], "keep_tools": False,
},
},
}
cap = Call("makeQuery", ["F1.opExtrude", "CAP_FACE", "EntityType.FACE", {"isStart": False}])
source_edge = Call("sQuery", ["F0.wireOp", "EntityType.EDGE", "E0"])
swept = Call("makeQuery", [
"F2.opExtrude", "SWEPT_FACE", "EntityType.FACE",
{"disambiguationData": [Call("OSD", [[source_edge]])]},
])
query = Call("makeQuery", [
"F3.boolean.opBoolean", "INTERSECT", "EntityType.EDGE", {"derivedFrom": [cap, swept]},
])
selector = _direct_boolean_intersection_selector(
query,
feature_by_id=feature_by_id,
feature_frames={"F2": {"profile_source": "F0"}},
sketch_by_source={"F0": sketch},
sketches_by_id={sketch["id"]: sketch},
entity_by_sketch={"F0": {"E0": {"type": "circle", "center": [0, 0], "radius_mm": 9.53}}},
featurescript_version="1511",
)
self.assertIsNotNone(selector)
self.assertNotIn("stable_id", selector)
intent = selector["selector_intent"]
self.assertEqual(intent["derivation_policy"], {"allowed": ["intersection"], "multiplicity": "one"})
self.assertEqual(intent["intersection_sources"], [
{"query_family": "CAP_FACE", "owner_feature_id": "f_F1", "output_role": "extrude.end"},
{"query_family": "SWEPT_FACE", "owner_feature_id": "f_F2", "source_entity": {"sketch_id": "F0", "entity_id": "E0"}},
])
def test_direct_primary_cut_intersection_requires_no_unverified_disambiguation(self):
base = lower_model(parse_featurescript(SOURCE, "primary-intersection-intent"), {}).cdsl
target = base["features"][0]
target_sketch = base["geometry"]["sketches"][0]
tool_sketch = deepcopy(target_sketch)
tool_sketch.update({"id": "sketch_F2", "source_sketch_id": "F2"})
if tool_sketch["profile"].get("type") == "circle":
tool_sketch["profile"]["source_entity_id"] = "E1"
else:
for contour in tool_sketch["profile"]["contours"]:
for segment in contour["segments"]:
segment["source_entity_id"] = "E1"
tool = {
"id": "f_F3", "atomic_id": "extrude_cut_blind", "sketch_id": "sketch_F2",
"params": {"distance_mm": 10, "end_condition": {"type": "blind"}},
}
cap = Call("makeQuery", ["F1.opExtrude", "CAP_FACE", "EntityType.FACE", {"isStart": False}])
tool_edge = Call("sQuery", ["F2.wireOp", "EntityType.EDGE", "E1"])
swept = Call("makeQuery", [
"F3.opExtrude", "SWEPT_FACE", "EntityType.FACE",
{"disambiguationData": [Call("OSD", [[tool_edge]])]},
])
query = Call("makeQuery", [
"F3.boolean.opBoolean", "INTERSECT", "EntityType.EDGE", {"derivedFrom": [cap, swept]},
])
kwargs = {
"feature_by_id": {"f_F1": target, "f_F3": tool},
"feature_frames": {"F3": {"profile_source": "F2"}},
"sketch_by_source": {"F2": tool_sketch},
"sketches_by_id": {tool_sketch["id"]: tool_sketch, target_sketch["id"]: target_sketch},
"entity_by_sketch": {"F2": {"E1": {"type": "circle", "center": [0, 0], "radius_mm": 9.53}}},
"previous": ["f_F1", "f_F3"],
"featurescript_version": "1511",
}
selector = _direct_primary_cut_intersection_selector(query, **kwargs)
self.assertIsNotNone(selector)
self.assertEqual(selector["owner_feature_id"], "f_F3")
self.assertEqual(selector["selector_intent"]["intersection_sources"][1], {
"query_family": "SWEPT_FACE", "owner_feature_id": "f_F3",
"source_entity": {"sketch_id": "F2", "entity_id": "E1"},
})
deferred = Call("makeQuery", [
"F3.boolean.opBoolean", "INTERSECT", "EntityType.EDGE",
{"derivedFrom": [cap, swept], "disambiguationData": [Call("OD", [0.0])]},
])
self.assertIsNone(_direct_primary_cut_intersection_selector(deferred, **kwargs))
def test_deferred_primary_intersect_keeps_the_convertible_f3_checkpoint(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
feature = root / "featurescript_rp/0002/00020311.txt"
if not feature.exists(): self.skipTest("CADFS sample is not installed")
result = lower_model(parse_featurescript(feature.read_text(), "00020311"), {})
f4 = next(item for item in result.cdsl["features"] if item["id"] == "f_F4")
self.assertTrue(all(
selector["kind"] == "edge"
and selector["selector_intent"]["query_family"] == "INTERSECT"
and selector["selector_intent"]["derivation_policy"]["multiplicity"] == "none"
for selector in f4["selectors"]
))
from engine.cdsl_engine.semantic_validation import validate_semantic_cdsl
self.assertTrue(validate_semantic_cdsl(result.cdsl)["future_rebuild_ready"])
with tempfile.TemporaryDirectory() as directory:
outcome = rebuild_candidate(result.cdsl, Path(directory) / "00020311.step")
self.assertTrue((Path(directory) / "00020311.step").exists())
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_conversion_writes_status_and_sidecars(self):
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp); source = root / "00000173.txt"; source.write_text(SOURCE)