5786 lines
345 KiB
Python
5786 lines
345 KiB
Python
from __future__ import annotations
|
|
|
|
import json, math, re, tempfile, unittest
|
|
from copy import deepcopy
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
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 (
|
|
UnsupportedCapability, _arc, _contours, _curve_endpoint_tangent,
|
|
_direct_hole_location, _direct_line_angle_axis, _direct_segmented_sketch_wire_path,
|
|
_is_direct_hole_location_query,
|
|
_direct_line_angle_wire_axis, _direct_sketch_wire_path, _direct_spatial_segmented_sketch_wire_path,
|
|
_direct_boolean_intersection_selector, _direct_primary_cut_intersection_selector, _global, _number,
|
|
_multi_source_sketch_region_profile, _record_single_body_successor, _surface_profile_selection_sketch, 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
|
|
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}); }
|
|
}'''
|
|
|
|
|
|
CAP_VERTEX_UP_TO_VERTEX_SOURCE = '''FeatureScript 1511;
|
|
import(path : "onshape/std/geometry.fs", version : "1511.0");
|
|
const mm = millimeter;
|
|
const EDGE = EntityType.EDGE;
|
|
const VERTEX = EntityType.VERTEX;
|
|
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(10,0)*mm,"end":v(10,5)*mm}); skLineSegment(sketch,"E2",{"start":v(10,5)*mm,"end":v(0,5)*mm}); skLineSegment(sketch,"E3",{"start":v(0,5)*mm,"end":v(0,0)*mm}); skSolve(sketch); }
|
|
{ var Q0; Q0=qSketchRegion(id+"F0",true); extrude(context,id+"F1",{"entities":qUnion([Q0]),"depth":10*mm}); }
|
|
{ var Q0; Q0=qSketchRegion(id+"F0",true); var Q1; Q1=makeQuery(id+"F1.opExtrude","CAP_VERTEX",VERTEX,{"disambiguationData":[OSD([sQuery(id+"F0.wireOp",EDGE,"E0"),sQuery(id+"F0.wireOp",EDGE,"E1")])],"isStart":false}); extrude(context,id+"F2",{"entities":qUnion([Q0]),"endBound":BoundingType.UP_TO_VERTEX,"endBoundEntityVertex":qUnion([Q1]),"depth":8*mm}); }
|
|
}'''
|
|
|
|
|
|
class LoweringTests(unittest.TestCase):
|
|
def test_multi_source_sketch_region_union_preserves_all_direct_profiles(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test/featurescript_rp"
|
|
source = root / "0001/00011635.txt"
|
|
if not source.exists(): self.skipTest("CADFS sample is not installed")
|
|
|
|
result = lower_model(parse_featurescript(source.read_text(encoding="utf-8"), "00011635"), {})
|
|
self.assertEqual({item["id"] for item in result.cdsl["features"]}, {"f_F1", "f_F3", "f_F5", "f_F7", "f_F10"})
|
|
profile = next(
|
|
sketch["profile"] for sketch in result.cdsl["geometry"]["sketches"]
|
|
if sketch["id"] == "sketch_regions_f_F7"
|
|
)
|
|
self.assertEqual(profile["type"], "multi_source_regions")
|
|
self.assertEqual(profile["source_sketch_ids"], ["F6", "F4"])
|
|
self.assertEqual(len(profile["profiles"]), 2)
|
|
|
|
# A composed source query outside the strict direct-region contract
|
|
# must defer rather than select the last source sketch as older
|
|
# lowering did.
|
|
text = source.read_text(encoding="utf-8")
|
|
needle = 'qSketchRegion(id + "F4", true)'
|
|
offset = text.rfind(needle)
|
|
invalid = lower_model(parse_featurescript(
|
|
text[:offset] + text[offset:].replace(needle, 'qSketchRegion(id + "F4", false)', 1),
|
|
"00011635-invalid-regions",
|
|
), {})
|
|
self.assertNotIn("f_F7", {item["id"] for item in invalid.cdsl["features"]})
|
|
self.assertIn(("F7", "unsupported_engine_capability", "extrude qSketchRegion union requires direct executable source profiles on one identical unattached frame"), {
|
|
(item.get("feature_id"), item.get("code"), item.get("message")) for item in invalid.diagnostics
|
|
})
|
|
|
|
def test_multi_source_sketch_region_requires_identical_unattached_frames(self):
|
|
def sketch(radius, plane, attachment=None):
|
|
result = {"workplane": plane, "profile": {"type": "circle", "radius_mm": radius}}
|
|
if attachment is not None: result["attachment"] = attachment
|
|
return result
|
|
|
|
query = Call("qUnion", [[
|
|
Call("qSketchRegion", ["F0", "true"]), Call("qSketchRegion", ["F1", "true"]),
|
|
]])
|
|
plane = {"origin_mm": [0, 0, 0], "x_dir": [1, 0, 0], "normal": [0, 0, 1]}
|
|
self.assertIsNotNone(_multi_source_sketch_region_profile(query, {
|
|
"F0": sketch(2, plane), "F1": sketch(3, deepcopy(plane)),
|
|
}, "f_regions"))
|
|
self.assertIsNone(_multi_source_sketch_region_profile(query, {
|
|
"F0": sketch(2, plane), "F1": sketch(3, {**plane, "origin_mm": [0, 0, 1]}),
|
|
}, "f_regions"))
|
|
self.assertIsNone(_multi_source_sketch_region_profile(query, {
|
|
"F0": sketch(2, plane), "F1": sketch(3, plane, {"kind": "face"}),
|
|
}, "f_regions"))
|
|
|
|
def test_multi_source_sketch_region_union_flattens_only_nested_union_wrappers(self):
|
|
plane = {"origin_mm": [0, 0, 0], "x_dir": [1, 0, 0], "normal": [0, 0, 1]}
|
|
sources = {
|
|
identifier: {"workplane": deepcopy(plane), "profile": {"type": "circle", "radius_mm": radius}}
|
|
for identifier, radius in (("F0", 2), ("F1", 3), ("F2", 4))
|
|
}
|
|
region = lambda identifier: Call("qSketchRegion", [identifier, "true"])
|
|
nested = Call("qUnion", [[region("F0"), Call("qUnion", [[region("F1"), region("F2")]])]])
|
|
profile = _multi_source_sketch_region_profile(nested, sources, "f_regions")
|
|
self.assertEqual(profile["profile"]["source_sketch_ids"], ["F0", "F1", "F2"])
|
|
|
|
# qIntersection has different set semantics and cannot be normalized
|
|
# as a profile union merely because it contains direct region leaves.
|
|
mixed = Call("qUnion", [[region("F0"), Call("qIntersection", [region("F1"), region("F2")])]])
|
|
self.assertIsNone(_multi_source_sketch_region_profile(mixed, sources, "f_regions"))
|
|
def test_cpoint_direct_source_line_is_an_explicit_datum_only(self):
|
|
"""A direct source line cPoint feeds cPlane without topology fallback."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test/featurescript_rp"
|
|
source = root / "0024/00240949.txt"
|
|
if not source.exists(): self.skipTest("CADFS sample is not installed")
|
|
|
|
result = lower_model(parse_featurescript(source.read_text(), "00240949"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
self.assertEqual(features["f_F3"], {
|
|
"id": "f_F3", "name": "F3", "atomic_id": "reference_point",
|
|
"depends_on": ["f_F1"], "params": {"point_mm": [15.05, 0.0, 22.985]},
|
|
"execution_status": "supported",
|
|
})
|
|
self.assertEqual(features["f_F4"]["params"]["plane"]["origin_mm"], [15.05, 0.0, 22.985])
|
|
|
|
from engine.cdsl_engine.runtime import prepare_cdsl_execution
|
|
from engine.cdsl_engine.semantic_validation import validate_semantic_cdsl
|
|
validate_semantic_cdsl(result.cdsl)
|
|
execution = prepare_cdsl_execution(result.cdsl)
|
|
for _ in range(6):
|
|
execution.execute_next()
|
|
self.assertEqual(list(execution.session.results), ["f_F1", "f_F3", "f_F4", "f_F6", "f_F7", "f_F9"])
|
|
self.assertIsNone(execution.session.results["f_F3"].body_id)
|
|
|
|
def test_cpoint_direct_line_boundary_and_rejection_contract(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test/featurescript_rp"
|
|
direct = root / "0050/00503730.txt"
|
|
if not direct.exists(): self.skipTest("CADFS sample is not installed")
|
|
|
|
result = lower_model(parse_featurescript(direct.read_text(), "00503730"), {})
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
self.assertEqual(features["f_F1"]["atomic_id"], "reference_point")
|
|
self.assertEqual(features["f_F1"]["params"]["point_mm"], [0.0, 4.5, 0.0])
|
|
self.assertEqual(features["f_F2"]["params"]["plane"]["origin_mm"], [0.0, 4.5, 0.0])
|
|
self.assertEqual(features["f_F4"]["params"]["end_condition"]["reference"], {
|
|
"kind": "source_vertex", "source_sketch_id": "F0",
|
|
"source_entity_id": "E4.bottom.end", "point_mm": [-1.3, -4.5, 0.0],
|
|
})
|
|
self.assertIn(("F6", "feature_deferred", "swept edge source endpoint provenance is unsupported"), {
|
|
(item.get("feature_id"), item.get("code"), item.get("message")) for item in result.diagnostics
|
|
})
|
|
|
|
invalid = lower_model(parse_featurescript(
|
|
direct.read_text().replace('"parameter" : 0.5', '"parameter" : 1.5', 1), "00503730"), {})
|
|
self.assertIn(("F1", "unsupported_engine_capability", "cPoint parameter must be a finite value in [0, 1]"), {
|
|
(item.get("feature_id"), item.get("code"), item.get("message")) for item in invalid.diagnostics
|
|
})
|
|
|
|
rejected = root / "0023/00238956.txt"
|
|
if not rejected.exists(): self.skipTest("CADFS sample is not installed")
|
|
outcome = lower_model(parse_featurescript(rejected.read_text(), "00238956"), {})
|
|
self.assertTrue(any(
|
|
item.get("code") == "unsupported_engine_capability"
|
|
and item.get("message") == "cPoint datum source is unsupported"
|
|
for item in outcome.diagnostics
|
|
))
|
|
|
|
def test_cpoint_direct_prism_swept_and_cap_edges_are_source_datums(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test/featurescript_rp"
|
|
source = root / "0028/00289068.txt"
|
|
if not source.exists(): self.skipTest("CADFS sample is not installed")
|
|
|
|
text = source.read_text(encoding="utf-8")
|
|
result = lower_model(parse_featurescript(text, "00289068"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
self.assertEqual(features["f_F3"]["params"]["point_mm"], [25.85, -42.6, 38.1])
|
|
self.assertEqual(features["f_F4"]["params"]["point_mm"], [25.85, -4.5, 76.2])
|
|
self.assertEqual(features["f_F5"]["params"]["plane"]["origin_mm"], [-12.25, -42.6, 76.2])
|
|
self.assertTrue(all("selectors" not in features[feature_id] for feature_id in ("f_F3", "f_F4")))
|
|
|
|
from engine.cdsl_engine.runtime import prepare_cdsl_execution
|
|
execution = prepare_cdsl_execution(result.cdsl)
|
|
execution.execute_all()
|
|
self.assertEqual(list(execution.session.results), ["f_F1", "f_F3", "f_F4", "f_F5", "f_F7"])
|
|
|
|
# A source pair without a unique profile vertex cannot describe the
|
|
# physical swept span. A through-all extrusion has no blind cap role.
|
|
mutations = (
|
|
text.replace('sQuery(id+"F0.wireOp",EDGE,"E0.bottom"),sQuery(id+"F0.wireOp",EDGE,"E0.right")',
|
|
'sQuery(id+"F0.wireOp",EDGE,"E0.bottom"),sQuery(id+"F0.wireOp",EDGE,"E0.top")', 1),
|
|
text.replace('"entities" : qUnion([Q0]), "depth" : 76.2 * mm',
|
|
'"entities" : qUnion([Q0]), "endBound" : BoundingType.THROUGH_ALL, "depth" : 76.2 * mm', 1),
|
|
)
|
|
for index, mutated in enumerate(mutations):
|
|
with self.subTest(mutation=index):
|
|
candidate = lower_model(parse_featurescript(mutated, f"cpoint-prism-mutation-{index}"), {})
|
|
rejected_feature = "F3" if index == 0 else "F4"
|
|
self.assertIn((rejected_feature, "unsupported_engine_capability", "cPoint datum source is unsupported"), {
|
|
(item.get("feature_id"), item.get("code"), item.get("message"))
|
|
for item in candidate.diagnostics
|
|
})
|
|
|
|
def test_direct_prism_cap_vertex_up_to_vertex_uses_kernel_history(self):
|
|
result = lower_model(parse_featurescript(CAP_VERTEX_UP_TO_VERTEX_SOURCE), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
end = {item["id"]: item for item in result.cdsl["features"]}["f_F2"]["params"]["end_condition"]
|
|
self.assertEqual(end["type"], "up_to_vertex")
|
|
reference = end["reference"]
|
|
self.assertEqual(reference["kind"], "vertex")
|
|
self.assertEqual(reference["owner_feature_id"], "f_F1")
|
|
self.assertEqual(reference["selector_intent"]["query_family"], "CAP_VERTEX")
|
|
self.assertEqual(reference["selector_intent"]["lineage_role"], "extrude.end")
|
|
self.assertEqual(reference["selector_intent"]["derivation_policy"], {
|
|
"allowed": ["boundary"], "multiplicity": "one",
|
|
})
|
|
self.assertEqual(reference["selector_intent"]["source_entities"], [
|
|
{"sketch_id": "F0", "entity_id": "E0"},
|
|
{"sketch_id": "F0", "entity_id": "E1"},
|
|
])
|
|
from engine.cdsl_engine.runtime import rebuild_cdsl
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_cdsl(result.cdsl, Path(directory) / "cap-vertex.step")
|
|
self.assertEqual([item["status"] for item in rebuilt["feature_results"]], ["executed", "executed"])
|
|
resolution = rebuilt["selector_resolution"][-1]
|
|
self.assertEqual(resolution["status"], "resolved")
|
|
self.assertEqual(resolution["resolution_mode"], "kernel_lineage")
|
|
self.assertEqual(resolution["evidence"]["relations"][0]["result_kind"], "vertex")
|
|
|
|
def test_direct_source_vertex_up_to_vertex_is_a_datum_not_a_runtime_selector(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
source = root / "featurescript_rp/0044/00444951.txt"
|
|
if not source.exists(): self.skipTest("CADFS sample is not installed")
|
|
|
|
result = lower_model(parse_featurescript(source.read_text(encoding="utf-8"), "00444951"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
for feature_id, source_entity_id in (("f_F5", "E0.left.end"), ("f_F7", "E6.end"), ("f_F9", "E5.end")):
|
|
reference = features[feature_id]["params"]["end_condition"]["reference"]
|
|
self.assertEqual(reference["kind"], "source_vertex")
|
|
self.assertEqual(reference["source_sketch_id"], "F0")
|
|
self.assertEqual(reference["source_entity_id"], source_entity_id)
|
|
self.assertNotIn("selector_intent", reference)
|
|
self.assertNotIn("stable_id", reference)
|
|
|
|
from engine.cdsl_engine.runtime import rebuild_cdsl
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_cdsl(result.cdsl, Path(directory) / "source-vertex-extent.step")
|
|
self.assertEqual([item["status"] for item in rebuilt["feature_results"]], ["executed"] * 6)
|
|
self.assertEqual(rebuilt["selector_resolution"], [])
|
|
|
|
# The CADFS pipeline uses incremental selector binding before runtime
|
|
# execution. A source datum must not be handed to that binder.
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
outcome = rebuild_candidate(result.cdsl, Path(directory) / "source-vertex-bound.step")
|
|
self.assertEqual(outcome["status"], "rebuilt")
|
|
self.assertTrue(all(not item["resolved"] for item in outcome["selector_binding"]))
|
|
|
|
from engine.cdsl_engine.semantic_validation import validate_semantic_cdsl
|
|
invalid = deepcopy(result.cdsl)
|
|
invalid["features"][3]["params"]["end_condition"]["reference"]["source_sketch_id"] = "missing"
|
|
with self.assertRaisesRegex(ValueError, "invalid source_vertex extent datum"):
|
|
validate_semantic_cdsl(invalid)
|
|
|
|
wrapped = source.read_text(encoding="utf-8").replace(
|
|
'Q1=sQuery(id+"F0.wireOp",VERTEX,"E0.left.end");',
|
|
'Q1=qAdjacent(sQuery(id+"F0.wireOp",VERTEX,"E0.left.end"));',
|
|
1,
|
|
)
|
|
rejected = lower_model(parse_featurescript(wrapped, "00444951-wrapped-vertex"), {})
|
|
self.assertIn(("F5", "feature_deferred", "topology query is unresolved"), {
|
|
(item.get("feature_id"), item.get("code"), item.get("message"))
|
|
for item in rejected.diagnostics
|
|
})
|
|
|
|
def test_direct_source_vertex_up_to_vertex_real_corpus_matrix(self):
|
|
"""Direct source vertices remain datums across one- and two-sided extents."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test/featurescript_rp"
|
|
cases = {
|
|
"00383982": ("0038/00383982.txt", "converted_complete", 1),
|
|
"00503730": ("0050/00503730.txt", "converted_partial", 1),
|
|
"00510558": ("0051/00510558.txt", "converted_complete", 4),
|
|
"00753006": ("0075/00753006.txt", "converted_partial", 2),
|
|
"00894150": ("0089/00894150.txt", "converted_complete", 3),
|
|
"00975649": ("0097/00975649.txt", "converted_complete", 1),
|
|
}
|
|
for sample_id, (relative_path, expected_status, expected_count) in cases.items():
|
|
with self.subTest(sample_id=sample_id):
|
|
source = root / relative_path
|
|
if not source.exists():
|
|
self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(source.read_text(encoding="utf-8"), sample_id), {})
|
|
references = [
|
|
condition["reference"]
|
|
for feature in result.cdsl["features"]
|
|
for condition in (
|
|
(feature.get("params") or {}).get("end_condition"),
|
|
(feature.get("params") or {}).get("reverse_end_condition"),
|
|
)
|
|
if isinstance(condition, dict)
|
|
and condition.get("type") == "up_to_vertex"
|
|
and isinstance(condition.get("reference"), dict)
|
|
and condition["reference"].get("kind") == "source_vertex"
|
|
]
|
|
self.assertEqual(result.status, expected_status)
|
|
self.assertEqual(len(references), expected_count)
|
|
self.assertTrue(all("selector_intent" not in reference for reference in references))
|
|
|
|
def test_cap_vertex_survives_an_exact_independent_body_member_preservation(self):
|
|
"""An added independent body may retain an older member only by IsSame."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0033/00330726.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00330726"), {})
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
self.assertTrue({"f_F1", "f_F3", "f_F5"}.issubset(features))
|
|
reference = features["f_F5"]["params"]["end_condition"]["reference"]
|
|
self.assertEqual(reference["selector_intent"]["query_family"], "CAP_VERTEX")
|
|
self.assertEqual(reference["selector_intent"]["derivation_policy"], {
|
|
"allowed": ["boundary", "continuation"], "multiplicity": "one",
|
|
})
|
|
|
|
from engine.cdsl_engine.runtime import prepare_cdsl_execution
|
|
execution = prepare_cdsl_execution(result.cdsl)
|
|
for _index in range(3): execution.execute_next()
|
|
resolution = execution.session.selector_resolutions[-1]
|
|
self.assertEqual(resolution["status"], "resolved")
|
|
self.assertEqual(resolution["resolution_mode"], "kernel_lineage")
|
|
self.assertEqual(
|
|
[item["operation"] for item in resolution["evidence"]["relations"]],
|
|
["extrude", "body_member_preserve"],
|
|
)
|
|
|
|
def test_three_point_plane_accepts_only_direct_prism_cap_vertex_source_data(self):
|
|
"""CAP vertices used as datum points retain source, not snapshot, semantics."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
from engine.cdsl_engine.runtime import prepare_cdsl_execution
|
|
|
|
for sample_id in ("00243142", "00245768"):
|
|
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(), sample_id), {})
|
|
plane = next(item for item in result.cdsl["features"] if item["id"] == "f_F2")
|
|
self.assertEqual(plane["atomic_id"], "reference_plane")
|
|
self.assertNotIn("selectors", plane)
|
|
|
|
execution = prepare_cdsl_execution(result.cdsl)
|
|
execution.execute_next()
|
|
execution.execute_next()
|
|
self.assertEqual(execution.session.results["f_F2"].atomic_id, "reference_plane")
|
|
execution.execute_all()
|
|
self.assertTrue(all(
|
|
feature_result.status == "executed"
|
|
for feature_result in execution.session.results.values()
|
|
))
|
|
|
|
# This otherwise direct CAP_VERTEX is separated from its prism by a
|
|
# hole. Its source-datum frame must not pretend the pre-hole cap is a
|
|
# currently valid direct-prism point.
|
|
mutated = root / "featurescript_rp/0005/00053942.txt"
|
|
if not mutated.exists(): self.skipTest("CADFS sample is not installed")
|
|
rejected = lower_model(parse_featurescript(mutated.read_text(), "00053942"), {})
|
|
self.assertNotIn("f_F4", {item["id"] for item in rejected.cdsl["features"]})
|
|
self.assertIn(
|
|
("F4", "feature_deferred", "CAP_VERTEX datum source is unsupported"),
|
|
{(item.get("feature_id"), item.get("code"), item.get("message")) for item in rejected.diagnostics},
|
|
)
|
|
|
|
def test_line_angle_source_wire_axis_accepts_one_line_including_construction(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]}
|
|
query = Call("qBodyType", [
|
|
Call("qCreatedBy", [Call("__binary__", ["id", "+", "F0"]), "EDGE"]),
|
|
"BodyType.WIRE",
|
|
])
|
|
source = {"F0": {"workplane": plane}}
|
|
construction_line = {
|
|
"type": "line", "start": [2.0, 3.0], "end": [2.0, 8.0], "construction": True,
|
|
}
|
|
kwargs = {
|
|
"featurescript_version": "1511",
|
|
"standard_library": "onshape/std/geometry.fs",
|
|
"standard_library_version": "1511.0",
|
|
}
|
|
|
|
self.assertEqual(
|
|
_direct_line_angle_wire_axis(query, source, {"F0": {"axis": construction_line}}, **kwargs),
|
|
([2.0, 3.0, 0.0], [0.0, 1.0, 0.0]),
|
|
)
|
|
self.assertIsNone(_direct_line_angle_wire_axis(
|
|
query, source, {"F0": {"axis": construction_line, "extra": {**construction_line, "start": [0.0, 0.0]}}}, **kwargs,
|
|
))
|
|
self.assertIsNone(_direct_line_angle_wire_axis(
|
|
query, source, {"F0": {"arc": {"type": "arc", "start": [0.0, 0.0], "end": [1.0, 1.0]}}}, **kwargs,
|
|
))
|
|
self.assertIsNone(_direct_line_angle_wire_axis(
|
|
query, source, {"F0": {"axis": construction_line}},
|
|
**{**kwargs, "featurescript_version": "1512"},
|
|
))
|
|
filtered = Call("qConstructionFilter", [query, "ConstructionObject.NO"])
|
|
self.assertIsNone(_direct_line_angle_wire_axis(
|
|
filtered, source, {"F0": {"axis": construction_line}}, **kwargs,
|
|
))
|
|
self.assertEqual(
|
|
_direct_line_angle_wire_axis(
|
|
filtered,
|
|
source,
|
|
{"F0": {"axis": construction_line, "visible": {"type": "line", "start": [5.0, 1.0], "end": [5.0, 4.0]}}},
|
|
**kwargs,
|
|
),
|
|
([5.0, 1.0, 0.0], [0.0, 1.0, 0.0]),
|
|
)
|
|
|
|
def test_surface_loft_accepts_two_direct_closed_source_wires_without_changing_body_lifecycle(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0017/00174697.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00174697"), {})
|
|
surface_loft = next(item for item in result.cdsl["features"] if item["id"] == "f_F3")
|
|
self.assertEqual(result.status, "converted_complete")
|
|
self.assertEqual(surface_loft["atomic_id"], "loft_surface")
|
|
self.assertEqual(len(surface_loft["params"]["profile_sketch_ids"]), 2)
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(result.cdsl, Path(directory) / "surface-loft.step")
|
|
self.assertEqual(rebuilt["status"], "rebuilt")
|
|
self.assertEqual(rebuilt["result"]["solid_count"], 0)
|
|
self.assertEqual(rebuilt["result"]["surface_count"], 1)
|
|
|
|
def test_surface_loft_defers_when_source_wire_form_has_an_unmodeled_derivative(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0017/00174697.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
source = feature.read_text().replace(
|
|
'"wireProfilesArray" : [{ "wireProfileEntities" : qUnion([Q0]) }, { "wireProfileEntities" : qUnion([Q1]) }]',
|
|
'"startCondition" : LoftEndDerivativeType.NORMAL_TO_PROFILE, "wireProfilesArray" : [{ "wireProfileEntities" : qUnion([Q0]) }, { "wireProfileEntities" : qUnion([Q1]) }]',
|
|
)
|
|
result = lower_model(parse_featurescript(source, "00174697-surface-loft-derivative"), {})
|
|
self.assertIn({
|
|
"code": "unsupported_engine_capability",
|
|
"capability": "loft_surface_wire_profiles",
|
|
"feature_id": "F3",
|
|
"operation": "loft",
|
|
"message": "current CDSL surface loft requires two direct closed source-wire profiles",
|
|
}, result.diagnostics)
|
|
|
|
def test_direct_sketch_wire_path_accepts_only_one_nonconstruction_open_curve(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",
|
|
))
|
|
arc = {
|
|
"type": "arc", "start": [0.0, 0.0], "end": [10.0, 0.0],
|
|
"center": [5.0, 5.0], "radius_mm": math.sqrt(50.0), "clockwise": False,
|
|
}
|
|
self.assertEqual(
|
|
_direct_sketch_wire_path(
|
|
query, sketches, {"F0": {"E0": arc}}, featurescript_version="1511",
|
|
standard_library="onshape/std/geometry.fs", standard_library_version="1511.0",
|
|
),
|
|
("F0", "E0", arc, sketches["F0"]),
|
|
)
|
|
self.assertIsNone(_direct_sketch_wire_path(
|
|
query, sketches, {"F0": {"E0": {"type": "circle", "center": [0.0, 0.0], "radius_mm": 5.0}}},
|
|
featurescript_version="1511", standard_library="onshape/std/geometry.fs", standard_library_version="1511.0",
|
|
))
|
|
|
|
def test_direct_source_arc_sweep_preserves_cap_source_contract(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
source = root / "featurescript_rp/0000/00007135.txt"
|
|
if not source.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(source.read_text(encoding="utf-8"), "00007135"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
sweep = next(item for item in result.cdsl["features"] if item["id"] == "f_F2")
|
|
self.assertEqual(sweep["params"]["path"]["segment"]["type"], "arc")
|
|
self.assertEqual(sweep["params"]["cap_output_contract"], {
|
|
"profile_source": "F1", "profile_entity": "E1",
|
|
"path_source": "F0", "path_entity": "E0", "path_reversed": False,
|
|
})
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(result.cdsl, Path(directory) / "arc-sweep-shell.step")
|
|
self.assertEqual(rebuilt["status"], "rebuild_failed")
|
|
self.assertEqual(rebuilt["last_executable_prefix"]["last_feature_id"], "f_F2")
|
|
|
|
standalone = root / "featurescript_rp/0017/00175627.txt"
|
|
if not standalone.exists(): self.skipTest("CADFS sample is not installed")
|
|
standalone_result = lower_model(parse_featurescript(standalone.read_text(encoding="utf-8"), "00175627"), {})
|
|
self.assertEqual(standalone_result.status, "converted_complete")
|
|
standalone_sweep = next(item for item in standalone_result.cdsl["features"] if item["id"] == "f_F2")
|
|
self.assertEqual(standalone_sweep["params"]["path"]["segment"]["type"], "arc")
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
standalone_rebuilt = rebuild_candidate(standalone_result.cdsl, Path(directory) / "arc-sweep.step")
|
|
self.assertEqual(standalone_rebuilt["status"], "rebuilt")
|
|
self.assertEqual(standalone_rebuilt["result"]["feature_results"][-1]["feature_id"], "f_F2")
|
|
|
|
def test_single_arc_source_wire_sweep_uses_the_complete_wire_selection(self):
|
|
"""A one-curve qBodyType(WIRE) path is not a parser representative leaf."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
for sample_id in ("00071885", "00109840", "00627942"):
|
|
source = root / f"featurescript_rp/{sample_id[:4]}/{sample_id}.txt"
|
|
if not source.exists():
|
|
self.skipTest("CADFS sample is not installed")
|
|
with self.subTest(sample_id=sample_id):
|
|
lowered = lower_model(parse_featurescript(source.read_text(encoding="utf-8"), sample_id), {})
|
|
self.assertNotIn(
|
|
"sweep_path",
|
|
[item.get("capability") for item in lowered.diagnostics],
|
|
)
|
|
# This source's profile attachment is independently
|
|
# degenerate. The path is nevertheless no longer classified
|
|
# as an unsupported query, and remains outside CDSL when no
|
|
# executable feature can be retained.
|
|
if lowered.cdsl is None:
|
|
self.assertIn("reference plane x direction is degenerate", [
|
|
item.get("message") for item in lowered.diagnostics
|
|
])
|
|
continue
|
|
sweep = next(item for item in lowered.cdsl["features"] if item["id"] == "f_F2")
|
|
self.assertEqual(sweep["params"]["path"]["segment"]["type"], "arc")
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(lowered.cdsl, Path(directory) / f"{sample_id}-arc-wire.step")
|
|
self.assertIn(rebuilt["status"], {"rebuilt", "rebuild_failed"})
|
|
self.assertNotEqual(
|
|
rebuilt.get("error", {}).get("message"),
|
|
"Feature f_F2 is not runtime eligible: sweep_path",
|
|
)
|
|
|
|
def test_direct_source_circle_sweep_is_closed_and_has_no_endpoint_roles(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test/featurescript_rp"
|
|
for sample_id, relative_path in (
|
|
("00232443", "0023/00232443.txt"),
|
|
("00310959", "0031/00310959.txt"),
|
|
):
|
|
source = root / relative_path
|
|
if not source.exists():
|
|
self.skipTest("CADFS sample is not installed")
|
|
lowered = lower_model(parse_featurescript(source.read_text(encoding="utf-8"), sample_id), {})
|
|
self.assertEqual(lowered.status, "converted_complete")
|
|
sweep = next(item for item in lowered.cdsl["features"] if item["id"] == "f_F2")
|
|
self.assertEqual(sweep["params"]["path"]["segment"]["type"], "circle")
|
|
self.assertNotIn("initial_output_roles", sweep["params"])
|
|
self.assertNotIn("cap_output_contract", sweep["params"])
|
|
self.assertNotIn("swept_face_contract", sweep["params"])
|
|
self.assertNotIn("swept_edge_contract", sweep["params"])
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(lowered.cdsl, Path(directory) / f"{sample_id}-circle-sweep.step")
|
|
self.assertEqual(rebuilt["status"], "rebuilt")
|
|
self.assertEqual(rebuilt["result"]["solid_count"], 1)
|
|
|
|
multi_query_source = (root / "0023/00232443.txt").read_text(encoding="utf-8")
|
|
multi_query_source = multi_query_source.replace(
|
|
'Q1=sQuery(id+"F1.wireOp",EDGE,"E1");',
|
|
'Q1=qUnion([sQuery(id+"F1.wireOp",EDGE,"E1"),sQuery(id+"F1.wireOp",EDGE,"E1")]);',
|
|
)
|
|
rejected = lower_model(parse_featurescript(multi_query_source, "circle-multi-query"), {})
|
|
self.assertEqual(rejected.status, "deferred_no_executable_feature")
|
|
self.assertIsNone(rejected.cdsl)
|
|
|
|
def test_single_circle_source_wire_sweep_uses_complete_qbodytype_selection(self):
|
|
"""A qBodyType circle needs complete-set proof, not a parser leaf."""
|
|
source = (
|
|
Path(__file__).parents[2]
|
|
/ "data/cadfs-sample/CADFS_test/featurescript_rp/0049/00498974.txt"
|
|
)
|
|
if not source.exists():
|
|
self.skipTest("CADFS sample is not installed")
|
|
lowered = lower_model(parse_featurescript(source.read_text(encoding="utf-8"), "00498974"), {})
|
|
self.assertNotIn("sweep_path_query", [item.get("capability") for item in lowered.diagnostics])
|
|
self.assertIsNotNone(lowered.cdsl)
|
|
assert lowered.cdsl is not None
|
|
sweep = next(item for item in lowered.cdsl["features"] if item["id"] == "f_F2")
|
|
self.assertEqual(sweep["params"]["path"]["segment"]["type"], "circle")
|
|
self.assertNotIn("initial_output_roles", sweep["params"])
|
|
self.assertNotIn("cap_output_contract", sweep["params"])
|
|
self.assertNotIn("swept_face_contract", sweep["params"])
|
|
self.assertNotIn("swept_edge_contract", sweep["params"])
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(lowered.cdsl, Path(directory) / "qbodytype-circle-sweep.step")
|
|
self.assertNotEqual(
|
|
rebuilt.get("error", {}).get("message"),
|
|
"Feature f_F2 is not runtime eligible: sweep_path_query",
|
|
)
|
|
|
|
def test_direct_segmented_sketch_wire_path_requires_one_open_source_wire(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]}
|
|
query = Call("qUnion", [[
|
|
Call("qBodyType", [
|
|
Call("qCreatedBy", [Call("__binary__", ["id", "+", "F0"]), "EDGE"]),
|
|
"BodyType.WIRE",
|
|
]),
|
|
]])
|
|
segments = {
|
|
# Creation order is intentionally not wire order.
|
|
"E0": {"type": "line", "start": [0.0, 0.0], "end": [0.0, 4.0]},
|
|
"E1": {"type": "line", "start": [1.0, 6.0], "end": [3.0, 10.0]},
|
|
"E2": {
|
|
"type": "arc", "start": [0.0, 4.0], "end": [1.0, 6.0],
|
|
"center": [-5.0, 4.0], "radius_mm": 5.0, "clockwise": False,
|
|
},
|
|
# qCreatedBy(..., EDGE) does not select this source point.
|
|
"visualSharp": {"type": "point", "point": [0.0, 5.0]},
|
|
}
|
|
result = _direct_segmented_sketch_wire_path(
|
|
query, {"F0": {"workplane": plane}}, {"F0": segments},
|
|
featurescript_version="1511", standard_library="onshape/std/geometry.fs", standard_library_version="1511.0",
|
|
)
|
|
self.assertIsNotNone(result)
|
|
assert result is not None
|
|
self.assertEqual(result[0], "F0")
|
|
self.assertEqual([item["source_entity_id"] for item in result[1]], ["E0", "E2", "E1"])
|
|
self.assertEqual([item["type"] for item in result[1]], ["line", "arc", "line"])
|
|
|
|
construction = {**segments, "axis": {"type": "line", "start": [0.0, 0.0], "end": [10.0, 0.0], "construction": True}}
|
|
self.assertIsNone(_direct_segmented_sketch_wire_path(
|
|
query, {"F0": {"workplane": plane}}, {"F0": construction},
|
|
featurescript_version="1511", standard_library="onshape/std/geometry.fs", standard_library_version="1511.0",
|
|
))
|
|
filtered_query = Call("qUnion", [[Call("qConstructionFilter", [query.args[0][0], "ConstructionObject.NO"])]] )
|
|
self.assertIsNotNone(_direct_segmented_sketch_wire_path(
|
|
filtered_query, {"F0": {"workplane": plane}}, {"F0": construction},
|
|
featurescript_version="1511", standard_library="onshape/std/geometry.fs", standard_library_version="1511.0",
|
|
))
|
|
|
|
branched = {**segments, "branch": {"type": "line", "start": [0.0, 4.0], "end": [-4.0, 5.0]}}
|
|
self.assertIsNone(_direct_segmented_sketch_wire_path(
|
|
query, {"F0": {"workplane": plane}}, {"F0": branched},
|
|
featurescript_version="1511", standard_library="onshape/std/geometry.fs", standard_library_version="1511.0",
|
|
))
|
|
|
|
direct_leaf_union = Call("qUnion", [[
|
|
Call("sQuery", ["F0.wireOp", "EDGE", "E0"]),
|
|
Call("sQuery", ["F0.wireOp", "EDGE", "E2"]),
|
|
Call("sQuery", ["F0.wireOp", "EDGE", "E1"]),
|
|
]])
|
|
direct_result = _direct_segmented_sketch_wire_path(
|
|
direct_leaf_union, {"F0": {"workplane": plane}}, {"F0": segments},
|
|
featurescript_version="1511", standard_library="onshape/std/geometry.fs", standard_library_version="1511.0",
|
|
)
|
|
self.assertIsNotNone(direct_result)
|
|
assert direct_result is not None
|
|
self.assertEqual([item["source_entity_id"] for item in direct_result[1]], ["E0", "E2", "E1"])
|
|
|
|
cross_sketch_union = Call("qUnion", [[
|
|
Call("sQuery", ["F0.wireOp", "EDGE", "E0"]),
|
|
Call("sQuery", ["F1.wireOp", "EDGE", "E1"]),
|
|
]])
|
|
self.assertIsNone(_direct_segmented_sketch_wire_path(
|
|
cross_sketch_union,
|
|
{"F0": {"workplane": plane}, "F1": {"workplane": plane}},
|
|
{"F0": segments, "F1": {"E1": segments["E1"]}},
|
|
featurescript_version="1511", standard_library="onshape/std/geometry.fs", standard_library_version="1511.0",
|
|
))
|
|
duplicate_leaf_union = Call("qUnion", [[
|
|
Call("sQuery", ["F0.wireOp", "EDGE", "E0"]),
|
|
Call("sQuery", ["F0.wireOp", "EDGE", "E0"]),
|
|
]])
|
|
self.assertIsNone(_direct_segmented_sketch_wire_path(
|
|
duplicate_leaf_union, {"F0": {"workplane": plane}}, {"F0": segments},
|
|
featurescript_version="1511", standard_library="onshape/std/geometry.fs", standard_library_version="1511.0",
|
|
))
|
|
|
|
def test_direct_spatial_segmented_sketch_wire_path_requires_connected_source_frames(self):
|
|
top = {"origin_mm": [0.0, 0.0, 0.0], "x_dir": [1.0, 0.0, 0.0], "normal": [0.0, 0.0, 1.0]}
|
|
vertical = {"origin_mm": [10.0, 0.0, 0.0], "x_dir": [0.0, 0.0, 1.0], "normal": [0.0, 1.0, 0.0]}
|
|
|
|
def wire(sketch_id):
|
|
return Call("qConstructionFilter", [
|
|
Call("qBodyType", [
|
|
Call("qCreatedBy", [Call("__binary__", ["id", "+", sketch_id]), "EDGE"]),
|
|
"BodyType.WIRE",
|
|
]),
|
|
"ConstructionObject.NO",
|
|
])
|
|
|
|
query = Call("qUnion", [[wire("F0"), wire("F1")]])
|
|
sketches = {"F0": {"workplane": top}, "F1": {"workplane": vertical}}
|
|
entities = {
|
|
"F0": {"E0": {"type": "line", "start": [0.0, 0.0], "end": [10.0, 0.0]}},
|
|
"F1": {"E1": {"type": "line", "start": [0.0, 0.0], "end": [10.0, 0.0]}},
|
|
}
|
|
result = _direct_spatial_segmented_sketch_wire_path(
|
|
query, sketches, entities,
|
|
featurescript_version="1511", standard_library="onshape/std/geometry.fs", standard_library_version="1511.0",
|
|
)
|
|
self.assertIsNotNone(result)
|
|
assert result is not None
|
|
self.assertEqual([item["source_sketch_id"] for item in result], ["F0", "F1"])
|
|
self.assertEqual([item["source_entity_id"] for item in result], ["E0", "E1"])
|
|
self.assertEqual(result[0]["start_mm"], [0.0, 0.0, 0.0])
|
|
self.assertEqual(result[0]["end_mm"], [10.0, 0.0, 0.0])
|
|
self.assertEqual(result[1]["end_mm"], [10.0, 0.0, 10.0])
|
|
|
|
disconnected = deepcopy(sketches)
|
|
disconnected["F1"] = {**vertical, "origin_mm": [11.0, 0.0, 0.0]}
|
|
self.assertIsNone(_direct_spatial_segmented_sketch_wire_path(
|
|
query, disconnected, entities,
|
|
featurescript_version="1511", standard_library="onshape/std/geometry.fs", standard_library_version="1511.0",
|
|
))
|
|
repeated = Call("qUnion", [[wire("F0"), wire("F0")]])
|
|
self.assertIsNone(_direct_spatial_segmented_sketch_wire_path(
|
|
repeated, sketches, entities,
|
|
featurescript_version="1511", standard_library="onshape/std/geometry.fs", standard_library_version="1511.0",
|
|
))
|
|
|
|
def test_unfiltered_source_wire_sweep_rebuilds_its_last_executable_feature(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
source = root / "featurescript_rp/0088/00885126.txt"
|
|
if not source.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(source.read_text(encoding="utf-8"), "00885126"), {})
|
|
self.assertEqual(result.status, "converted_partial")
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
self.assertEqual(set(features), {"f_F2"})
|
|
path = features["f_F2"]["params"]["path"]
|
|
self.assertEqual([item["source_entity_id"] for item in path["segments"]], ["E0", "E2.filletArc", "E1"])
|
|
self.assertEqual([item["type"] for item in path["segments"]], ["line", "arc", "line"])
|
|
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) / "rebuild.step")
|
|
self.assertEqual(outcome["status"], "rebuilt")
|
|
self.assertEqual(outcome["result"]["feature_results"][-1]["feature_id"], "f_F2")
|
|
|
|
def test_multi_leaf_arc_wire_sweep_keeps_every_source_segment(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
source = root / "featurescript_rp/0068/00684140.txt"
|
|
if not source.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(source.read_text(encoding="utf-8"), "00684140"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
sweep = next(item for item in result.cdsl["features"] if item["id"] == "f_F2")
|
|
self.assertEqual(
|
|
[segment["source_entity_id"] for segment in sweep["params"]["path"]["segments"]],
|
|
["E0", "E3.filletArc", "E1"],
|
|
)
|
|
self.assertEqual(
|
|
[segment["type"] for segment in sweep["params"]["path"]["segments"]],
|
|
["line", "arc", "arc"],
|
|
)
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
outcome = rebuild_candidate(result.cdsl, Path(directory) / "rebuild.step")
|
|
self.assertEqual(outcome["status"], "rebuilt")
|
|
self.assertEqual(outcome["result"]["feature_results"][-1]["feature_id"], "f_F2")
|
|
|
|
def test_cross_sketch_source_wire_sweep_rebuilds_its_executable_prefix(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
source = root / "featurescript_rp/0061/00610979.txt"
|
|
if not source.exists(): self.skipTest("CADFS sample is not installed")
|
|
|
|
result = lower_model(parse_featurescript(source.read_text(encoding="utf-8"), "00610979"), {})
|
|
self.assertEqual(result.status, "converted_partial")
|
|
sweep = next(item for item in result.cdsl["features"] if item["id"] == "f_F4")
|
|
path = sweep["params"]["path"]
|
|
self.assertNotIn("workplane", path)
|
|
self.assertEqual(
|
|
[(item["source_sketch_id"], item["source_entity_id"]) for item in path["segments"]],
|
|
[("F1", "E1"), ("F1", "E2"), ("F3", "E3"), ("F3", "E4")],
|
|
)
|
|
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) / "rebuild.step")
|
|
self.assertEqual(outcome["status"], "rebuilt")
|
|
self.assertEqual(outcome["result"]["feature_results"][-1]["feature_id"], "f_F4")
|
|
self.assertEqual(outcome["result"]["solid_count"], 1)
|
|
|
|
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_hole_location_wrapper_is_not_a_direct_source_vertex_query(self):
|
|
direct = Call("sQuery", ["F2.wireOp", "VERTEX", "P0"])
|
|
self.assertTrue(_is_direct_hole_location_query(direct))
|
|
self.assertFalse(_is_direct_hole_location_query(Call("qAdjacent", [direct])))
|
|
|
|
def test_hole_location_accepts_one_direct_source_arc_center(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": {"A0": {
|
|
"type": "arc", "start": [1.0, 0.0], "end": [-1.0, 0.0], "center": [0.0, 0.0],
|
|
}}}
|
|
|
|
self.assertEqual(
|
|
_direct_hole_location(Call("sQuery", ["F2.wireOp", "VERTEX", "A0.center"]), sketches, entities),
|
|
([0.0, 0.0, 0.0], plane),
|
|
)
|
|
self.assertIsNone(
|
|
_direct_hole_location(Call("sQuery", ["F2.wireOp", "VERTEX", "A0.trim.center"]), sketches, entities)
|
|
)
|
|
|
|
def test_hole_location_arc_centers_rebuild_multiple_real_histories(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
for sample_id in ("00392565", "00091356", "00486207"):
|
|
source = next(root.glob(f"featurescript_rp/*/{sample_id}.txt"), None)
|
|
if source is None:
|
|
self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(source.read_text(encoding="utf-8"), sample_id), {})
|
|
self.assertFalse(any(
|
|
item.get("capability") == "hole_location_vertex"
|
|
for item in result.diagnostics
|
|
), sample_id)
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
outcome = rebuild_candidate(result.cdsl, Path(directory) / f"{sample_id}.step")
|
|
self.assertEqual(outcome["status"], "rebuilt", sample_id)
|
|
|
|
def test_swept_face_host_uses_closed_contour_orientation_not_vertex_average(self):
|
|
"""A valid asymmetric profile can average onto its selected boundary."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test/featurescript_rp"
|
|
expected = {
|
|
"00139790": ("f_F6", "rebuilt", "f_F7"),
|
|
"00140109": ("f_F5", "rebuilt", "f_F5"),
|
|
# F4 is independently an open, disconnected cut profile. The
|
|
# rebuilt prefix must nevertheless retain the new F3 hole.
|
|
"00140261": ("f_F3", "runtime_ineligible", "f_F3"),
|
|
}
|
|
for sample_id, (hole_id, status, last_feature_id) in expected.items():
|
|
with self.subTest(sample_id=sample_id):
|
|
source = next(root.glob(f"*/{sample_id}.txt"), None)
|
|
if source is None:
|
|
self.skipTest("CADFS sample is not installed")
|
|
lowered = lower_model(parse_featurescript(source.read_text(encoding="utf-8"), sample_id), {})
|
|
self.assertNotIn("swept face interior is degenerate", [
|
|
item.get("message") for item in lowered.diagnostics
|
|
])
|
|
self.assertIn(hole_id, {feature["id"] for feature in lowered.cdsl["features"]})
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
outcome = rebuild_candidate(lowered.cdsl, Path(directory) / "rebuild.step")
|
|
self.assertEqual(outcome["status"], status)
|
|
if status == "rebuilt":
|
|
self.assertEqual(outcome["result"]["feature_results"][-1]["feature_id"], last_feature_id)
|
|
else:
|
|
self.assertEqual(outcome["last_executable_prefix"]["last_feature_id"], last_feature_id)
|
|
|
|
def test_hole_reports_an_unavailable_source_sketch_before_vertex_rejection(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
source = root / "featurescript_rp/0054/00548761.txt"
|
|
if not source.exists(): self.skipTest("CADFS sample is not installed")
|
|
|
|
result = lower_model(parse_featurescript(source.read_text(encoding="utf-8"), "00548761"), {})
|
|
diagnostics = [
|
|
item for item in result.diagnostics
|
|
if item.get("feature_id") == "F5" and item.get("operation") == "hole"
|
|
]
|
|
self.assertEqual(result.status, "converted_partial")
|
|
self.assertEqual(len(diagnostics), 1)
|
|
self.assertEqual(diagnostics[0]["capability"], "hole_location_sketch_unavailable")
|
|
self.assertEqual(diagnostics[0]["message"], "hole location source sketch is not executable")
|
|
|
|
def test_hole_wrapper_with_an_unavailable_nested_sketch_stays_a_vertex_rejection(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
source = root / "featurescript_rp/0054/00548761.txt"
|
|
if not source.exists(): self.skipTest("CADFS sample is not installed")
|
|
|
|
history = source.read_text(encoding="utf-8").replace(
|
|
'Q0=sQuery(id+"F4.wireOp",VERTEX,"E8.center");',
|
|
'Q0=qAdjacent(sQuery(id+"F4.wireOp",VERTEX,"E8.center"));',
|
|
)
|
|
result = lower_model(parse_featurescript(history, "00548761-wrapped-location"), {})
|
|
diagnostics = [
|
|
item for item in result.diagnostics
|
|
if item.get("feature_id") == "F5" and item.get("operation") == "hole"
|
|
]
|
|
self.assertEqual(result.status, "converted_partial")
|
|
self.assertEqual(len(diagnostics), 1)
|
|
self.assertEqual(diagnostics[0]["capability"], "hole_location_vertex")
|
|
self.assertEqual(diagnostics[0]["message"], "hole location requires one direct original sketch vertex")
|
|
|
|
def test_hole_scope_keeps_one_explicit_live_member_among_multiple_bodies(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0021/00219259.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
|
|
result = lower_model(parse_featurescript(feature.read_text(encoding="utf-8"), "00219259"), {})
|
|
|
|
hole = next(item for item in result.cdsl["features"] if item["id"] == "f_F6")
|
|
self.assertEqual(result.status, "converted_complete")
|
|
self.assertEqual(hole["atomic_id"], "hole_wizard")
|
|
self.assertEqual(hole["params"]["scope_feature_id"], "f_F1")
|
|
self.assertFalse(any(
|
|
item.get("feature_id") == "F6" and item.get("capability") == "hole_scope_body_source"
|
|
for item in result.diagnostics
|
|
))
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
outcome = rebuild_candidate(result.cdsl, Path(directory) / "rebuild.step")
|
|
self.assertEqual(outcome["status"], "rebuilt")
|
|
self.assertEqual(
|
|
[item["feature_id"] for item in outcome["result"]["feature_results"]],
|
|
["f_F1", "f_F3", "f_F6"],
|
|
)
|
|
|
|
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)
|
|
sample = Sample("strict-status", {"step": str(directory / "gold.step")}, {}, {}, [])
|
|
(directory / "rebuild.step").touch(); (directory / "gold.step").touch()
|
|
(directory / "status.json").write_text(json.dumps({
|
|
"sample_id": sample.sample_id, "rebuild_status": "rebuilt",
|
|
"comparison_error": {"type": "TimeoutError", "message": "stale"},
|
|
}), encoding="utf-8")
|
|
(directory / "comparison.json").write_text(json.dumps({"decision": "strict_pass", "strict": {"passed": True}, "rp": {"passed": True}}), encoding="utf-8")
|
|
status = compare_one(sample, output, compare_mode="strict")
|
|
self.assertEqual(status["status"], "rebuilt_strict")
|
|
self.assertNotIn("comparison_error", status)
|
|
|
|
def test_missing_dataset_fails_explicitly(self):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
with self.assertRaises(FileNotFoundError): scan_dataset(Path(tmp) / "missing")
|
|
|
|
def test_circle_extrude_is_valid_cdsl_11(self):
|
|
result = lower_model(parse_featurescript(SOURCE, "00000173"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
self.assertEqual(result.cdsl["schema_version"], "1.1.0")
|
|
self.assertEqual(result.cdsl["geometry"]["sketches"][0]["profile"]["radius_mm"], 9.53)
|
|
from engine.cdsl_engine.semantic_validation import validate_semantic_cdsl
|
|
self.assertTrue(validate_semantic_cdsl(result.cdsl)["future_rebuild_ready"])
|
|
|
|
def test_rectilinear_fillet_arc_recovers_its_exact_endpoint_radius(self):
|
|
arc = _arc([38.1, 63.5], [34.38, 72.48], [25.4, 76.2])
|
|
self.assertEqual(arc["center"], [25.4, 63.5])
|
|
self.assertAlmostEqual(arc["radius_mm"], 12.7)
|
|
self.assertFalse(arc["clockwise"])
|
|
|
|
def test_general_three_point_arc_remains_circumcircle_defined(self):
|
|
arc = _arc([0, 0], [2, 3], [5, 0])
|
|
self.assertAlmostEqual(arc["center"][0], 2.5)
|
|
self.assertAlmostEqual(arc["center"][1], 0.5)
|
|
self.assertAlmostEqual(arc["radius_mm"], math.hypot(2.5, 0.5))
|
|
|
|
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),
|
|
"00005267": ("f_F4", 4, False),
|
|
}
|
|
for sample_id, (fillet_id, selector_count, line_type) in cases.items():
|
|
with self.subTest(sample_id=sample_id):
|
|
feature = root / "featurescript_rp" / sample_id[:4] / f"{sample_id}.txt"
|
|
result = lower_model(parse_featurescript(feature.read_text(), sample_id), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
fillet = next(item for item in result.cdsl["features"] if item["id"] == fillet_id)
|
|
if sample_id == "00000715":
|
|
self.assertEqual(len(fillet["selectors"]), 1)
|
|
selector = fillet["selectors"][0]
|
|
self.assertEqual(selector["selector_intent"]["query_family"], "QUERY_SET")
|
|
self.assertEqual(selector["selector_intent"]["query_set_contract"], "proven_operand_union")
|
|
self.assertEqual(len(selector["query_operands"]), selector_count)
|
|
self.assertTrue(all(
|
|
item["selector_intent"]["query_family"] == "SWEPT_EDGE"
|
|
and item["selector_intent"]["derivation_policy"] == {
|
|
"allowed": ["boundary", "continuation"], "multiplicity": "one",
|
|
}
|
|
and len(item["selector_intent"]["source_entities"]) == 2
|
|
and item["owner_feature_id"] == fillet["depends_on"][0]
|
|
and "geometry" not in item
|
|
for item in selector["query_operands"]
|
|
))
|
|
else:
|
|
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)
|
|
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)
|
|
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"
|
|
feature = root / "featurescript_rp/0000/00000715.txt"
|
|
source = feature.read_text(encoding="utf-8").replace(
|
|
'sQuery(id+"F0.wireOp",EDGE,"E8")',
|
|
'sQuery(id+"F0.wireOp",EDGE,"E1")',
|
|
1,
|
|
)
|
|
result = lower_model(parse_featurescript(source, "swept-edge-ambiguous"), {})
|
|
self.assertEqual(result.status, "converted_partial")
|
|
self.assertEqual(result.diagnostics, [{
|
|
"code": "feature_deferred",
|
|
"feature_id": "F2",
|
|
"operation": "fillet",
|
|
"message": "swept edge source endpoint provenance is unsupported",
|
|
}])
|
|
|
|
def test_direct_query_intersection_and_subtraction_lower_as_proven_sets(self):
|
|
"""Only direct leaf provenance may enter the S0 set-algebra bridge."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0000/00000715.txt"
|
|
source = feature.read_text(encoding="utf-8")
|
|
cases = {
|
|
"intersection": source.replace("qUnion([Q0, Q1])", "qIntersection([Q0, Q1])"),
|
|
"subtraction": source.replace("qUnion([Q0, Q1])", "qSubtraction(Q0, Q1)"),
|
|
}
|
|
contracts = {
|
|
"intersection": "proven_operand_intersection",
|
|
"subtraction": "proven_operand_subtraction",
|
|
}
|
|
expected_rebuild_status = {
|
|
"intersection": "rebuild_failed",
|
|
"subtraction": "rebuilt",
|
|
}
|
|
from engine.cdsl_engine.semantic_validation import validate_semantic_cdsl
|
|
|
|
for operator, candidate in cases.items():
|
|
with self.subTest(operator=operator):
|
|
result = lower_model(parse_featurescript(candidate, f"set-{operator}"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
fillet = next(item for item in result.cdsl["features"] if item["id"] == "f_F2")
|
|
self.assertEqual(len(fillet["selectors"]), 1)
|
|
selector = fillet["selectors"][0]
|
|
self.assertEqual(selector["selector_intent"]["query_set_contract"], contracts[operator])
|
|
self.assertEqual(selector["selector_intent"]["query_expr"]["root"]["operator"], operator)
|
|
self.assertEqual(len(selector["query_operands"]), 2)
|
|
self.assertTrue(validate_semantic_cdsl(result.cdsl)["future_rebuild_ready"])
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = Path(directory) / "rebuild.step"
|
|
outcome = rebuild_candidate(result.cdsl, rebuilt)
|
|
self.assertTrue(rebuilt.exists())
|
|
self.assertEqual(outcome["status"], expected_rebuild_status[operator])
|
|
if operator == "intersection":
|
|
self.assertEqual(outcome["error"]["message"], "f_F2: selector_query_empty during incremental replay")
|
|
self.assertEqual(outcome["last_executable_prefix"]["last_feature_id"], "f_F1")
|
|
|
|
def test_nested_query_union_preserves_recursive_set_expression(self):
|
|
from engine.cdsl_engine.semantic_validation import validate_semantic_cdsl
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0000/00000715.txt"
|
|
source = feature.read_text(encoding="utf-8").replace(
|
|
"qUnion([Q0, Q1])", "qUnion([Q0, qUnion([Q1, Q0])])",
|
|
)
|
|
result = lower_model(parse_featurescript(source, "nested-query-union"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
fillet = next(item for item in result.cdsl["features"] if item["id"] == "f_F2")
|
|
self.assertEqual(len(fillet["selectors"]), 1)
|
|
selector = fillet["selectors"][0]
|
|
intent = selector["selector_intent"]
|
|
self.assertEqual(intent["query_set_contract"], "proven_operand_union")
|
|
self.assertEqual(intent["query_expr"]["root"]["operator"], "union")
|
|
self.assertEqual(len(selector["query_operands"]), 2)
|
|
nested = selector["query_operands"][1]
|
|
self.assertEqual(nested["selector_intent"]["query_set_contract"], "proven_operand_union")
|
|
self.assertEqual(nested["selector_intent"]["query_expr"]["root"]["operator"], "union")
|
|
self.assertEqual(len(nested["query_operands"]), 2)
|
|
self.assertTrue(validate_semantic_cdsl(result.cdsl)["future_rebuild_ready"])
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
outcome = rebuild_candidate(result.cdsl, Path(directory) / "rebuild.step")
|
|
self.assertEqual(outcome["status"], "rebuilt")
|
|
|
|
def test_1511_full_revolve_swept_edges_use_exact_vertex_history(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
for sample_id, dressup_id in (
|
|
("00048326", "f_F3"),
|
|
("00025622", "f_F2"),
|
|
):
|
|
with self.subTest(sample_id=sample_id):
|
|
feature = root / "featurescript_rp" / sample_id[:4] / f"{sample_id}.txt"
|
|
result = lower_model(parse_featurescript(feature.read_text(), sample_id), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
dressup = next(item for item in result.cdsl["features"] if item["id"] == dressup_id)
|
|
self.assertEqual(len(dressup["selectors"]), 1)
|
|
root_selector = dressup["selectors"][0]
|
|
leaves = root_selector.get("query_operands") or [root_selector]
|
|
self.assertTrue(all(
|
|
"geometry" not in selector
|
|
and selector["selector_intent"]["query_family"] == "SWEPT_EDGE"
|
|
and selector["selector_intent"]["evidence"] == "kernel_history"
|
|
and selector["selector_intent"]["source_query"]["featurescript_version"] == "1511"
|
|
and selector["selector_intent"]["derivation_policy"] == {
|
|
"allowed": ["boundary", "continuation"], "multiplicity": "one",
|
|
}
|
|
for selector in leaves
|
|
))
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = Path(directory) / "rebuild.step"
|
|
outcome = rebuild_candidate(result.cdsl, rebuilt)
|
|
self.assertEqual(outcome["status"], "rebuilt")
|
|
resolution = next(
|
|
item for item in outcome["result"]["selector_resolution"]
|
|
if item["feature_id"] == dressup_id
|
|
)
|
|
if root_selector.get("query_operands"):
|
|
self.assertEqual(resolution["resolution_mode"], "query_set_union")
|
|
relations = [
|
|
relation
|
|
for operand in resolution["evidence"]["operand_resolutions"]
|
|
for relation in operand["relations"]
|
|
]
|
|
else:
|
|
self.assertEqual(resolution["resolution_mode"], "kernel_lineage")
|
|
relations = resolution["evidence"]["relations"]
|
|
self.assertTrue(relations)
|
|
self.assertTrue(all(relation["operation"] == "revolve" for relation in relations))
|
|
|
|
def test_1511_full_revolve_accepts_direct_initial_dressup_only(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0040/00407186.txt"
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00407186"), {})
|
|
# Unsupported selectors are retained in the candidate as an explicit
|
|
# non-executable source-query contract. This does not hide the
|
|
# earlier F1/F2 prefix or turn the later revolve BLEND_EDGE tuple into
|
|
# a geometry selector at lowering time.
|
|
self.assertEqual(result.status, "converted_complete")
|
|
deferred = next(item for item in result.cdsl["features"] if item["id"] == "f_F5")
|
|
intent = deferred["selectors"][0]["selector_intent"]
|
|
self.assertEqual(intent["query_family"], "BLEND_EDGE")
|
|
self.assertEqual(intent["evidence"], "feature_script_query")
|
|
self.assertEqual(intent["derivation_policy"], {"allowed": ["continuation"], "multiplicity": "none"})
|
|
prefix = deepcopy(result.cdsl)
|
|
prefix["features"] = result.cdsl["features"][:2]
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
outcome = rebuild_candidate(prefix, Path(directory) / "rebuild.step")
|
|
self.assertEqual(outcome["status"], "rebuilt")
|
|
resolution = outcome["result"]["selector_resolution"][0]
|
|
self.assertEqual(resolution["resolution_mode"], "kernel_lineage")
|
|
self.assertEqual(resolution["evidence"]["relations"][0]["operation"], "revolve")
|
|
|
|
def test_1511_direct_prism_blend_edge_requires_complete_immediate_tuple(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0041/00414347.txt"
|
|
source = feature.read_text(encoding="utf-8")
|
|
result = lower_model(parse_featurescript(source, "00414347"), {})
|
|
|
|
self.assertEqual(result.status, "converted_complete")
|
|
fillet = next(item for item in result.cdsl["features"] if item["id"] == "f_F3")
|
|
selector = fillet["selectors"][0]
|
|
intent = selector["selector_intent"]
|
|
self.assertEqual(intent["query_family"], "BLEND_EDGE")
|
|
self.assertEqual(intent["evidence"], "kernel_history")
|
|
self.assertEqual(intent["derivation_policy"], {"allowed": ["boundary"], "multiplicity": "one"})
|
|
self.assertEqual(intent["blend_sources"], {
|
|
"edge": {
|
|
"query_family": "CAP_EDGE", "owner_feature_id": "f_F1",
|
|
"source_entity": {"sketch_id": "F0", "entity_id": "E0.right"},
|
|
"lineage_role": "extrude.end",
|
|
},
|
|
"face": {
|
|
"query_family": "CAP_FACE", "owner_feature_id": "f_F1",
|
|
"output_role": "extrude.end",
|
|
},
|
|
})
|
|
self.assertFalse(any(key in selector for key in (
|
|
"stable_id", "snapshot_id", "geometry", "binding_feature_id", "output_role",
|
|
)))
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
outcome = rebuild_candidate(result.cdsl, Path(directory) / "rebuild.step")
|
|
self.assertEqual(outcome["status"], "rebuilt")
|
|
resolution = next(
|
|
item for item in outcome["result"]["selector_resolution"]
|
|
if item["feature_id"] == "f_F3"
|
|
)
|
|
self.assertEqual(resolution["resolution_mode"], "blend_boundary")
|
|
self.assertEqual(len(resolution["evidence"]["source_records"]), 2)
|
|
self.assertEqual(len(resolution["evidence"]["result_records"]), 1)
|
|
|
|
# One incomplete source cap set, a changed blendedInto role, or an
|
|
# additional blendedFrom member must stay non-executable. Each
|
|
# mutation remains valid FeatureScript; rejection is contract-based.
|
|
mutations = (
|
|
source.replace('sQuery(id+"F0.wireOp",EDGE,"E0.top"),', "", 1),
|
|
source.replace(
|
|
'sQuery(id+"F0.wireOp",EDGE,"E0.bottom"),sQuery(id+"F0.wireOp",EDGE,"E0.top"),sQuery(id+"F0.wireOp",EDGE,"E0.left"),subQ0])],"isStart":false})]});}',
|
|
'sQuery(id+"F0.wireOp",EDGE,"E0.bottom"),sQuery(id+"F0.wireOp",EDGE,"E0.top"),sQuery(id+"F0.wireOp",EDGE,"E0.left"),subQ0])],"isStart":true})]});}',
|
|
1,
|
|
),
|
|
source.replace(
|
|
'"isStart":false})],"blendedInto"',
|
|
'"isStart":false}),makeQuery(id+"F1.opExtrude","CAP_EDGE",EDGE,{"disambiguationData":[OSD([subQ0])],"isStart":false})],"blendedInto"',
|
|
1,
|
|
),
|
|
)
|
|
for index, mutated in enumerate(mutations):
|
|
with self.subTest(mutation=index):
|
|
candidate = lower_model(parse_featurescript(mutated, f"blend-edge-mutation-{index}"), {})
|
|
deferred = next(item for item in candidate.cdsl["features"] if item["id"] == "f_F3")
|
|
deferred_intent = deferred["selectors"][0]["selector_intent"]
|
|
self.assertEqual(deferred_intent["query_family"], "BLEND_EDGE")
|
|
self.assertEqual(deferred_intent["evidence"], "feature_script_query")
|
|
self.assertNotIn("blend_sources", deferred_intent)
|
|
|
|
def test_1511_direct_prism_blend_edge_accepts_exact_swept_face_pair_only(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
cases = (("00596552", "f_F5", 2), ("00456146", "f_F4", 1))
|
|
for sample_id, feature_id, expected_blends in cases:
|
|
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(encoding="utf-8"), sample_id), {})
|
|
feature = next(item for item in result.cdsl["features"] if item["id"] == feature_id)
|
|
leaves = feature["selectors"][0].get("query_operands") or feature["selectors"]
|
|
blends = [item for item in leaves if item["selector_intent"]["query_family"] == "BLEND_EDGE"]
|
|
self.assertEqual(len(blends), expected_blends)
|
|
for selector in blends:
|
|
intent = selector["selector_intent"]
|
|
self.assertEqual(intent["evidence"], "kernel_history")
|
|
self.assertEqual(intent["blend_sources"]["face"]["query_family"], "SWEPT_FACE")
|
|
self.assertEqual(
|
|
intent["blend_sources"]["face"]["source_entity"],
|
|
intent["blend_sources"]["edge"]["source_entity"],
|
|
)
|
|
|
|
invalid = deepcopy(result.cdsl)
|
|
invalid_feature = next(item for item in invalid["features"] if item["id"] == feature_id)
|
|
invalid_leaf = next(item for item in (invalid_feature["selectors"][0].get("query_operands") or invalid_feature["selectors"])
|
|
if item["selector_intent"]["query_family"] == "BLEND_EDGE")
|
|
invalid_leaf["selector_intent"]["blend_sources"]["face"]["source_entity"] = {
|
|
"sketch_id": "F0", "entity_id": "unrelated",
|
|
}
|
|
from engine.cdsl_engine.semantic_validation import validate_semantic_cdsl
|
|
with self.assertRaisesRegex(ValueError, "swept-face source must match"):
|
|
validate_semantic_cdsl(invalid)
|
|
|
|
def test_1511_direct_prism_blend_face_sketch_uses_runtime_patch_attachment(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
cases = (("00313870", {"sketch_F3", "sketch_F4"}),)
|
|
for sample_id, expected_sketches in cases:
|
|
with self.subTest(sample_id=sample_id):
|
|
source = root / f"featurescript_rp/{sample_id[:4]}/{sample_id}.txt"
|
|
result = lower_model(parse_featurescript(source.read_text(encoding="utf-8"), sample_id), {})
|
|
attached = {
|
|
sketch["id"]: sketch["attachment"]
|
|
for sketch in result.cdsl["geometry"]["sketches"]
|
|
if isinstance(sketch.get("attachment"), dict)
|
|
and sketch["attachment"].get("selector_intent", {}).get("query_family") == "BLEND_FACE"
|
|
}
|
|
self.assertEqual(set(attached), expected_sketches)
|
|
for attachment in attached.values():
|
|
intent = attachment["selector_intent"]
|
|
self.assertEqual(intent["evidence"], "kernel_history")
|
|
self.assertEqual(intent["blend_face_source"]["query_family"], "CAP_EDGE")
|
|
self.assertNotIn("geometry", attachment)
|
|
self.assertNotIn("stable_id", attachment)
|
|
from engine.cdsl_engine.semantic_validation import validate_semantic_cdsl
|
|
self.assertTrue(validate_semantic_cdsl(result.cdsl)["future_rebuild_ready"])
|
|
tampered = deepcopy(result.cdsl)
|
|
tampered_attachment = next(
|
|
sketch for sketch in tampered["geometry"]["sketches"]
|
|
if sketch.get("attachment", {}).get("selector_intent", {}).get("query_family") == "BLEND_FACE"
|
|
)["attachment"]
|
|
tampered_attachment["selector_intent"]["blend_face_source"]["source_entity"]["entity_id"] = "unrelated"
|
|
with self.assertRaisesRegex(ValueError, "direct new-body blind prism"):
|
|
validate_semantic_cdsl(tampered)
|
|
|
|
# F2 is explicit ADD/fuse here, so its CAP edge cannot be promoted to
|
|
# the independent direct-prism BLEND_FACE host contract.
|
|
fused = root / "featurescript_rp/0043/00436592.txt"
|
|
fused_result = lower_model(parse_featurescript(fused.read_text(encoding="utf-8"), "00436592"), {})
|
|
self.assertFalse(any(
|
|
sketch.get("attachment", {}).get("selector_intent", {}).get("query_family") == "BLEND_FACE"
|
|
for sketch in fused_result.cdsl["geometry"]["sketches"]
|
|
))
|
|
|
|
# A symmetric prism uses the same outer query spelling, but it is not
|
|
# the direct blind-prism lifecycle represented by this contract.
|
|
rejected = root / "featurescript_rp/0058/00588828.txt"
|
|
result = lower_model(parse_featurescript(rejected.read_text(encoding="utf-8"), "00588828"), {})
|
|
self.assertFalse(any(
|
|
sketch.get("attachment", {}).get("selector_intent", {}).get("query_family") == "BLEND_FACE"
|
|
for sketch in result.cdsl["geometry"]["sketches"]
|
|
))
|
|
|
|
def test_full_revolve_swept_circle_requires_one_direct_shared_source_endpoint(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0000/00001352.txt"
|
|
source = feature.read_text(encoding="utf-8").replace(
|
|
'sQuery(id+"F0.wireOp",EDGE,"E2")',
|
|
'sQuery(id+"F0.wireOp",EDGE,"E1")',
|
|
1,
|
|
)
|
|
result = lower_model(parse_featurescript(source, "revolve-swept-edge-ambiguous"), {})
|
|
self.assertEqual(result.status, "converted_partial")
|
|
self.assertIn({
|
|
"code": "feature_deferred",
|
|
"feature_id": "F2",
|
|
"operation": "fillet",
|
|
"message": "swept edge source endpoint provenance is unsupported",
|
|
}, result.diagnostics)
|
|
|
|
def test_2491_full_revolve_swept_edge_uses_exact_vertex_history(self):
|
|
"""A 2491 full revolve must bind from MakeRevol vertex history only."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0040/00404735.txt"
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00404735"), {})
|
|
|
|
# F2's source pair crosses intervening profile entities and therefore
|
|
# does not identify one direct vertex. F3 independently does, so its
|
|
# executable prefix is retained without weakening that rejection.
|
|
self.assertEqual(result.status, "converted_partial")
|
|
self.assertIn({
|
|
"code": "feature_deferred",
|
|
"feature_id": "F2",
|
|
"operation": "chamfer",
|
|
"message": "swept edge source endpoint provenance is unsupported",
|
|
}, result.diagnostics)
|
|
chamfer = next(item for item in result.cdsl["features"] if item["id"] == "f_F3")
|
|
selector = chamfer["selectors"][0]
|
|
self.assertNotIn("geometry", selector)
|
|
intent = selector["selector_intent"]
|
|
self.assertEqual(intent["query_family"], "SWEPT_EDGE")
|
|
self.assertEqual(intent["evidence"], "kernel_history")
|
|
self.assertEqual(intent["source_query"]["featurescript_version"], "2491")
|
|
self.assertEqual(intent["source_query"]["standard_library_version"], "2491.0")
|
|
self.assertEqual(intent["source_entities"], [
|
|
{"sketch_id": "F0", "entity_id": "E0"},
|
|
{"sketch_id": "F0", "entity_id": "E5"},
|
|
])
|
|
self.assertEqual(intent["derivation_policy"], {
|
|
"allowed": ["boundary", "continuation"], "multiplicity": "one",
|
|
})
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
outcome = rebuild_candidate(result.cdsl, Path(directory) / "rebuild.step")
|
|
self.assertEqual(outcome["status"], "rebuilt")
|
|
resolution = outcome["result"]["selector_resolution"][0]
|
|
self.assertEqual(resolution["resolution_mode"], "kernel_lineage")
|
|
self.assertEqual(resolution["evidence"]["semantic_anchor"], {
|
|
"type": "source_vertex",
|
|
"source_entities": intent["source_entities"],
|
|
})
|
|
self.assertEqual(resolution["evidence"]["relations"][0]["operation"], "revolve")
|
|
self.assertEqual(resolution["evidence"]["relations"][0]["derivation"], "boundary")
|
|
|
|
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"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
fillet = next(item for item in result.cdsl["features"] if item["id"] == "f_F2")
|
|
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]
|
|
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"
|
|
source = root / "featurescript_rp/0059/00590828.txt"
|
|
result = lower_model(parse_featurescript(source.read_text(), "00590828"), {})
|
|
self.assertEqual(result.status, "converted_partial")
|
|
self.assertIn({
|
|
"code": "feature_deferred",
|
|
"feature_id": "F2",
|
|
"operation": "fillet",
|
|
"message": "swept edge source endpoint provenance is unsupported",
|
|
}, result.diagnostics)
|
|
|
|
def test_unsupported_operation_is_audited_not_invented(self):
|
|
source = SOURCE.replace('extrude(context, id + "F1",', 'draft(context, id + "F1",')
|
|
result = lower_model(parse_featurescript(source, "00000173"), {})
|
|
self.assertEqual(result.status, "deferred_no_executable_feature")
|
|
self.assertIsNone(result.cdsl)
|
|
self.assertEqual(result.diagnostics[0]["code"], "unsupported_operation")
|
|
|
|
def test_unknown_direct_context_operation_is_reported_not_silently_dropped(self):
|
|
source = SOURCE.replace(
|
|
'\n});\n',
|
|
'\n unknownOperation(context, id + "F2", {"value":10 * mm});\n});\n',
|
|
)
|
|
result = lower_model(parse_featurescript(source, "assign-variable"), {})
|
|
self.assertEqual(result.status, "converted_partial")
|
|
self.assertIn(
|
|
{"code": "unsupported_operation", "feature_id": "F2", "operation": "unknownOperation"},
|
|
result.diagnostics,
|
|
)
|
|
|
|
def test_assign_variable_resolves_ordered_source_get_variable_calls(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0006/00067276.txt"
|
|
if not feature.exists(): self.skipTest("CADFS assignVariable sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(encoding="utf-8"), "00067276"), {})
|
|
variables = [item for item in result.cdsl["features"] if item["atomic_id"] == "assign_variable"]
|
|
self.assertEqual([item["params"] for item in variables], [
|
|
{"name": "thickness", "value": 4.0, "value_kind": "any"},
|
|
{"name": "nut_height", "value": 3.2, "value_kind": "any"},
|
|
])
|
|
self.assertFalse(any(diagnostic.get("operation") == "assignVariable" for diagnostic in result.diagnostics))
|
|
|
|
def test_assign_variable_rejects_missing_lookup_redeclaration_and_ambiguous_value(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test/featurescript_rp"
|
|
source_path = root / "0006/00067276.txt"
|
|
if not source_path.exists(): self.skipTest("CADFS assignVariable sample is not installed")
|
|
source = source_path.read_text(encoding="utf-8")
|
|
|
|
missing = lower_model(parse_featurescript(
|
|
source.replace("getVariable(context, 'thickness')", "getVariable(context, 'missing')", 1), "00067276"), {})
|
|
self.assertIn(("F3", "feature_deferred", "source variable is unavailable: missing"), {
|
|
(item.get("feature_id"), item.get("code"), item.get("message")) for item in missing.diagnostics
|
|
})
|
|
|
|
redeclared = lower_model(parse_featurescript(
|
|
source.replace('"name" : "nut_height"', '"name" : "thickness"', 1), "00067276"), {})
|
|
self.assertIn(("F1", "unsupported_engine_capability", "assignVariable redeclares source variable thickness"), {
|
|
(item.get("feature_id"), item.get("code"), item.get("message")) for item in redeclared.diagnostics
|
|
})
|
|
|
|
ambiguous = lower_model(parse_featurescript(
|
|
source.replace('"anyValue" : 4', '"anyValue" : 4, "lengthValue" : 4 * mm', 1), "00067276"), {})
|
|
self.assertIn(("F0", "unsupported_engine_capability", "assignVariable requires exactly one anyValue or lengthValue source expression"), {
|
|
(item.get("feature_id"), item.get("code"), item.get("message")) for item in ambiguous.diagnostics
|
|
})
|
|
|
|
def test_assign_variable_does_not_break_an_immediate_cap_edge_consumer(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test/featurescript_rp"
|
|
source_path = root / "0002/00021014.txt"
|
|
if not source_path.exists(): self.skipTest("CADFS CAP_EDGE sample is not installed")
|
|
source = source_path.read_text(encoding="utf-8")
|
|
source = source.replace(
|
|
' {\n var Q0;\n Q0=makeQuery(id+"F1.opExtrude",',
|
|
' { assignVariable(context, id + "F1variable", {"name" : "radius", "anyValue" : 5.08}); }\n'
|
|
' {\n var Q0;\n Q0=makeQuery(id+"F1.opExtrude",',
|
|
1,
|
|
)
|
|
result = lower_model(parse_featurescript(source, "00021014"), {})
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
self.assertEqual(features["f_F1variable"]["atomic_id"], "assign_variable")
|
|
self.assertEqual(features["f_F2"]["depends_on"], ["f_F1"])
|
|
|
|
from engine.cdsl_engine.runtime import prepare_cdsl_execution
|
|
execution = prepare_cdsl_execution(result.cdsl)
|
|
self.assertTrue(execution.analysis.feature_results[-1].executable)
|
|
|
|
def test_signed_parenthesized_transform_translation_lowers_without_a_parser_defer(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test/featurescript_rp"
|
|
source_path = root / "0029/00292242.txt"
|
|
if not source_path.exists(): self.skipTest("CADFS signed transform sample is not installed")
|
|
result = lower_model(parse_featurescript(source_path.read_text(encoding="utf-8"), "00292242"), {})
|
|
# This is a non-copy move of the immediately preceding independent
|
|
# source body, so the existing contract bakes it into its sketch
|
|
# frame instead of creating a second body-graph feature.
|
|
self.assertEqual([item["id"] for item in result.cdsl["features"]], ["f_F1"])
|
|
self.assertEqual(result.cdsl["geometry"]["sketches"][0]["workplane"]["origin_mm"], [-69.3, 0.0, 0.0])
|
|
self.assertFalse(any(
|
|
item.get("feature_id") == "F2" and item.get("operation") == "transform"
|
|
for item in result.diagnostics
|
|
))
|
|
|
|
def test_direct_translation_transform_is_baked_into_the_source_feature(self):
|
|
result = lower_model(parse_featurescript(TRANSFORM_SOURCE, "transform"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
self.assertEqual([item["id"] for item in result.cdsl["features"]], ["f_F1"])
|
|
self.assertEqual(result.history[-1]["feature_id"], "F2")
|
|
self.assertFalse(result.diagnostics)
|
|
self.assertEqual(result.cdsl["geometry"]["sketches"][0]["workplane"]["origin_mm"], [10.0, 0.0, 0.0])
|
|
|
|
def test_copy_transform_lowers_to_an_explicit_body_operation(self):
|
|
source = TRANSFORM_SOURCE.replace('"makeCopy":false', '"makeCopy":true')
|
|
result = lower_model(parse_featurescript(source, "transform-copy"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
self.assertFalse(result.diagnostics)
|
|
transform = result.cdsl["features"][-1]
|
|
self.assertEqual(transform["atomic_id"], "transform_bodies")
|
|
self.assertEqual(transform["params"], {
|
|
"source_feature_ids": ["f_F1"],
|
|
"transform": {"type": "translation", "translation_mm": [10.0, 0.0, 0.0]},
|
|
"make_copy": True,
|
|
})
|
|
|
|
def test_identity_copy_transform_creates_a_distinct_body_member(self):
|
|
source = TRANSFORM_SOURCE.replace(
|
|
'"transformType":TransformType.TRANSLATION_3D, "dx":10 * mm, "dy":0 * mm, "dz":0 * mm, "makeCopy":false',
|
|
'"transformType":TransformType.COPY',
|
|
)
|
|
result = lower_model(parse_featurescript(source, "identity-copy"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
self.assertFalse(result.diagnostics)
|
|
self.assertEqual(result.cdsl["features"][-1]["params"], {
|
|
"source_feature_ids": ["f_F1"],
|
|
"transform": {"type": "translation", "translation_mm": [0.0, 0.0, 0.0]},
|
|
"make_copy": True,
|
|
})
|
|
|
|
from engine.cdsl_engine.runtime import rebuild_cdsl
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_cdsl(result.cdsl, Path(directory) / "identity-copy.step")
|
|
self.assertEqual([item["feature_id"] for item in rebuilt["feature_results"]], ["f_F1", "f_F2"])
|
|
self.assertEqual(rebuilt["solid_count"], 2)
|
|
self.assertTrue(any(item["operation"] == "translation" for item in rebuilt["topology_deltas"]))
|
|
|
|
def test_identity_copy_transform_preserves_direct_source_provenance(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
cases = {
|
|
"00548763": {"f_F2": "f_F1"},
|
|
"00853763": {"f_F6": "f_F5"},
|
|
# F3 is a typed ADD fuse. F4 therefore targets its active F3
|
|
# successor while preserving F1 as an explicit source alias.
|
|
# F11 remains an identity copy of its direct F10 source.
|
|
"00981258": {"f_F4": "f_F1", "f_F11": "f_F10"},
|
|
}
|
|
for sample_id, expected_sources in cases.items():
|
|
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(), sample_id), {})
|
|
transforms = {item["id"]: item for item in result.cdsl["features"] if item["atomic_id"] == "transform_bodies"}
|
|
self.assertFalse(any(
|
|
item.get("feature_id") in {feature_id[2:] for feature_id in expected_sources}
|
|
and str(item.get("capability") or "").startswith("transform")
|
|
for item in result.diagnostics
|
|
))
|
|
for feature_id, source_feature_id in expected_sources.items():
|
|
expected = {
|
|
"source_feature_ids": [source_feature_id],
|
|
"transform": {"type": "translation", "translation_mm": [0.0, 0.0, 0.0]},
|
|
"make_copy": True,
|
|
}
|
|
if sample_id == "00981258" and feature_id == "f_F4":
|
|
expected["source_feature_ids"] = ["f_F3"]
|
|
expected["source_member_aliases"] = [{
|
|
"source_feature_id": "f_F1",
|
|
"active_member_feature_id": "f_F3",
|
|
}]
|
|
self.assertEqual(transforms[feature_id]["params"], expected)
|
|
|
|
def test_transform_lowers_one_proven_mirror_copy_member(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
cases = {
|
|
"00120430": {"f_F5": {"pattern_feature_id": "f_F4", "source_feature_id": "f_F1", "instance_index": 1}},
|
|
"00749755": {"f_F4": {"pattern_feature_id": "f_F3", "source_feature_id": "f_F1", "instance_index": 1}},
|
|
}
|
|
for sample_id, expected in cases.items():
|
|
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(), sample_id), {})
|
|
transforms = {item["id"]: item for item in result.cdsl["features"] if item["atomic_id"] == "transform_bodies"}
|
|
for feature_id, reference in expected.items():
|
|
self.assertEqual(transforms[feature_id]["params"]["pattern_instance_refs"], [reference])
|
|
self.assertFalse(any(
|
|
item.get("feature_id") in {feature_id[2:] for feature_id in expected}
|
|
and item.get("capability") == "transform_pattern_copy"
|
|
for item in result.diagnostics
|
|
))
|
|
|
|
def test_transform_lowers_source_qualified_multi_source_copy_members(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0069/00699847.txt"
|
|
if not feature.exists():
|
|
self.skipTest("CADFS sample is not installed")
|
|
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00699847"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
self.assertFalse(result.diagnostics)
|
|
transforms = {item["id"]: item for item in result.cdsl["features"] if item["atomic_id"] == "transform_bodies"}
|
|
self.assertEqual(transforms["f_F3"]["params"]["source_feature_ids"], ["f_F1", "f_F2"])
|
|
self.assertEqual(transforms["f_F4"]["params"]["transform_copy_refs"], [
|
|
{"transform_feature_id": "f_F3", "source_feature_id": "f_F1"},
|
|
{"transform_feature_id": "f_F3", "source_feature_id": "f_F2"},
|
|
])
|
|
self.assertEqual(transforms["f_F4"]["depends_on"], ["f_F1", "f_F2", "f_F3"])
|
|
|
|
def test_transform_copy_chain_retains_semantic_source_and_active_member(self):
|
|
"""COPY owns the original CADFS body while executing its live member."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
source = root / "featurescript_rp/0018/00184423.txt"
|
|
if not source.exists():
|
|
self.skipTest("CADFS sample is not installed")
|
|
|
|
result = lower_model(parse_featurescript(source.read_text(), "00184423"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
self.assertFalse(result.diagnostics)
|
|
transforms = {item["id"]: item for item in result.cdsl["features"] if item["atomic_id"] == "transform_bodies"}
|
|
self.assertEqual(transforms["f_F3"]["params"]["source_member_aliases"], [{
|
|
"source_feature_id": "f_F1", "active_member_feature_id": "f_F2",
|
|
}])
|
|
self.assertEqual(transforms["f_F4"]["params"]["source_member_aliases"], [{
|
|
"source_feature_id": "f_F1", "active_member_feature_id": "f_F3",
|
|
}])
|
|
self.assertEqual(transforms["f_F5"]["params"]["source_member_aliases"], [{
|
|
"source_feature_id": "f_F1", "active_member_feature_id": "f_F4",
|
|
}])
|
|
# The alias is source provenance. Replacing its selected runtime
|
|
# member cannot silently redirect a transform to another body.
|
|
invalid = deepcopy(result.cdsl)
|
|
invalid_feature = next(item for item in invalid["features"] if item["id"] == "f_F3")
|
|
invalid_feature["params"]["source_member_aliases"][0]["active_member_feature_id"] = "f_F1"
|
|
from engine.cdsl_engine.semantic_validation import validate_semantic_cdsl
|
|
with self.assertRaisesRegex(ValueError, "does not bind one preceding semantic source"):
|
|
validate_semantic_cdsl(invalid)
|
|
|
|
def test_uniform_scale_lowers_to_a_non_rigid_body_transform(self):
|
|
source = TRANSFORM_SOURCE.replace(
|
|
'"transformType":TransformType.TRANSLATION_3D, "dx":10 * mm, "dy":0 * mm, "dz":0 * mm, "makeCopy":false',
|
|
'"transformType":TransformType.SCALE_UNIFORMLY, "scale":0.5, '
|
|
'"scalePoint":qCreatedBy(makeId("Origin.pointOp"), VERTEX), "makeCopy":false',
|
|
)
|
|
result = lower_model(parse_featurescript(source, "uniform-scale"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
self.assertFalse(result.diagnostics)
|
|
transform = result.cdsl["features"][-1]
|
|
self.assertEqual(transform["atomic_id"], "transform_bodies")
|
|
self.assertEqual(transform["params"], {
|
|
"source_feature_ids": ["f_F1"],
|
|
"transform": {"type": "uniform_scale", "center_mm": [0.0, 0.0, 0.0], "scale_factor": 0.5},
|
|
"make_copy": False,
|
|
})
|
|
|
|
from engine.cdsl_engine.runtime import rebuild_cdsl
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_cdsl(result.cdsl, Path(directory) / "uniform-scale.step")
|
|
self.assertEqual(rebuilt["solid_count"], 1)
|
|
self.assertAlmostEqual(rebuilt["volume_mm3"], math.pi * 9.53 ** 2 * 120 / 8)
|
|
self.assertEqual(rebuilt["bbox_mm"], {"min": [-4.765, -4.765, 0.0], "max": [4.765, 4.765, 60.0]})
|
|
self.assertTrue(any(item["operation"] == "uniform_scale" for item in rebuilt["topology_deltas"]))
|
|
|
|
def test_uniform_scale_resolves_direct_circle_center_and_rejects_invalid_centers(self):
|
|
source = TRANSFORM_SOURCE.replace(
|
|
'"center":v(0, 0) * mm, "radius":9.53 * mm',
|
|
'"center":v(10, 0) * mm, "radius":9.53 * mm',
|
|
).replace(
|
|
'"transformType":TransformType.TRANSLATION_3D, "dx":10 * mm, "dy":0 * mm, "dz":0 * mm, "makeCopy":false',
|
|
'"transformType":TransformType.SCALE_UNIFORMLY, "scale":0.5, '
|
|
'"scalePoint":sQuery(id + "F0.wireOp", VERTEX, "E0.center"), "makeCopy":false',
|
|
)
|
|
result = lower_model(parse_featurescript(source, "uniform-scale-circle-center"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
self.assertEqual(result.cdsl["features"][-1]["params"]["transform"], {
|
|
"type": "uniform_scale", "center_mm": [10.0, 0.0, 0.0], "scale_factor": 0.5,
|
|
})
|
|
|
|
zero_factor = lower_model(parse_featurescript(source.replace('"scale":0.5', '"scale":0'), "uniform-scale-zero"), {})
|
|
self.assertEqual(zero_factor.status, "converted_partial")
|
|
self.assertEqual(zero_factor.diagnostics[-1]["capability"], "transform_uniform_scale")
|
|
|
|
unresolved_center = lower_model(parse_featurescript(source.replace(
|
|
'sQuery(id + "F0.wireOp", VERTEX, "E0.center")',
|
|
'qCreatedBy(id + "F1", VERTEX)',
|
|
), "uniform-scale-unresolved-center"), {})
|
|
self.assertEqual(unresolved_center.status, "converted_partial")
|
|
self.assertEqual(unresolved_center.diagnostics[-1]["capability"], "transform_uniform_scale_center")
|
|
|
|
def test_non_copy_uniform_scale_preserves_a_later_source_owner_alias(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0041/00417936.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00417936"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
transforms = [item for item in result.cdsl["features"] if item["atomic_id"] == "transform_bodies"]
|
|
self.assertEqual([item["params"]["source_feature_ids"] for item in transforms], [["f_F1"], ["f_F2"]])
|
|
self.assertEqual([item["params"]["transform"]["scale_factor"] for item in transforms], [10.0, 1.5])
|
|
|
|
from engine.cdsl_engine.runtime import rebuild_cdsl
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_cdsl(result.cdsl, Path(directory) / "sequential-uniform-scale.step")
|
|
self.assertEqual(rebuilt["solid_count"], 1)
|
|
self.assertEqual([item["feature_id"] for item in rebuilt["feature_results"]], ["f_F1", "f_F2", "f_F3"])
|
|
self.assertEqual(
|
|
[item["operation"] for item in rebuilt["topology_deltas"]],
|
|
["extrude", "uniform_scale", "uniform_scale"],
|
|
)
|
|
|
|
def test_single_body_successor_aliases_clear_at_body_graph_boundaries(self):
|
|
for boundary in (
|
|
{"id": "f_copy", "atomic_id": "transform_bodies", "params": {"make_copy": True}},
|
|
{"id": "f_boolean", "atomic_id": "boolean_bodies", "params": {}},
|
|
{"id": "f_pattern", "atomic_id": "pattern_circular", "params": {}},
|
|
{"id": "f_delete", "atomic_id": "delete_bodies", "params": {}},
|
|
):
|
|
with self.subTest(boundary=boundary["atomic_id"]):
|
|
aliases = {}
|
|
state = {"owner": None, "sources": set()}
|
|
_record_single_body_successor(aliases, state, {
|
|
"id": "f_base", "atomic_id": "extrude_add_blind",
|
|
"params": {"result_mode": "new_body"},
|
|
})
|
|
_record_single_body_successor(aliases, state, {
|
|
"id": "f_add", "atomic_id": "extrude_add_blind", "params": {},
|
|
})
|
|
self.assertEqual(aliases, {"f_base": "f_add"})
|
|
|
|
_record_single_body_successor(aliases, state, boundary)
|
|
self.assertEqual(aliases, {})
|
|
self.assertIsNone(state["owner"])
|
|
self.assertEqual(state["sources"], set())
|
|
|
|
def test_shell_parts_lowers_to_a_live_member_or_proven_sole_body_alias(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
|
|
successor = root / "featurescript_rp/0010/00107631.txt"
|
|
result = lower_model(parse_featurescript(successor.read_text(), "00107631"), {})
|
|
shell = next(feature for feature in result.cdsl["features"] if feature["id"] == "f_F3")
|
|
self.assertEqual(shell["params"]["target_feature_id"], "f_F2")
|
|
self.assertFalse(any(
|
|
diagnostic.get("capability") == "shell_parts_body_source"
|
|
for diagnostic in result.diagnostics
|
|
))
|
|
|
|
patterned = root / "featurescript_rp/0054/00542223.txt"
|
|
result = lower_model(parse_featurescript(patterned.read_text(), "00542223"), {})
|
|
self.assertNotIn("f_F7", {feature["id"] for feature in result.cdsl["features"]})
|
|
diagnostic = next(item for item in result.diagnostics if item.get("feature_id") == "F7")
|
|
self.assertEqual(diagnostic["message"], "pattern copy source feature is not replayed by its owner")
|
|
|
|
def test_translation_distance_lowers_from_a_linear_sketch_direction(self):
|
|
source = TRANSFORM_SOURCE.replace(
|
|
'skCircle(sketch, "E0", {"center":v(0, 0) * mm, "radius":9.53 * mm}); skSolve(sketch);',
|
|
'skCircle(sketch, "E0", {"center":v(0, 0) * mm, "radius":9.53 * mm}); '
|
|
'skLineSegment(sketch, "E1", {"start":v(0, 0) * mm, "end":v(1, 0) * mm, "construction":true}); skSolve(sketch);',
|
|
).replace(
|
|
'"transformType":TransformType.TRANSLATION_3D, "dx":10 * mm, "dy":0 * mm, "dz":0 * mm, "makeCopy":false',
|
|
'"transformType":TransformType.TRANSLATION_DISTANCE, "transformDirection":sQuery(id + "F0.wireOp", EDGE, "E1"), '
|
|
'"distance":10 * mm, "oppositeDirection":true, "makeCopy":false',
|
|
)
|
|
moved = lower_model(parse_featurescript(source, "distance-move"), {})
|
|
self.assertEqual(moved.status, "converted_complete")
|
|
self.assertFalse(moved.diagnostics)
|
|
self.assertEqual(moved.cdsl["geometry"]["sketches"][0]["workplane"]["origin_mm"], [-10.0, 0.0, 0.0])
|
|
|
|
datum_plane = TRANSFORM_SOURCE.replace(
|
|
'"transformType":TransformType.TRANSLATION_3D, "dx":10 * mm, "dy":0 * mm, "dz":0 * mm, "makeCopy":false',
|
|
'"transformType":TransformType.TRANSLATION_DISTANCE, '
|
|
'"transformDirection":qCreatedBy(makeId("Front.planeOp"), FACE), '
|
|
'"distance":10 * mm, "makeCopy":true',
|
|
)
|
|
datum_result = lower_model(parse_featurescript(datum_plane, "distance-datum-plane"), {})
|
|
self.assertEqual(datum_result.status, "converted_complete")
|
|
self.assertEqual(datum_result.cdsl["features"][-1]["params"], {
|
|
"source_feature_ids": ["f_F1"],
|
|
"transform": {"type": "translation", "translation_mm": [0.0, -10.0, 0.0]},
|
|
"make_copy": True,
|
|
})
|
|
|
|
square = (
|
|
'skLineSegment(sketch, "E0", {"start":v(0, 0) * mm, "end":v(10, 0) * mm}); '
|
|
'skLineSegment(sketch, "E1", {"start":v(10, 0) * mm, "end":v(10, 10) * mm}); '
|
|
'skLineSegment(sketch, "E2", {"start":v(10, 10) * mm, "end":v(0, 10) * mm}); '
|
|
'skLineSegment(sketch, "E3", {"start":v(0, 10) * mm, "end":v(0, 0) * mm}); skSolve(sketch);'
|
|
)
|
|
swept_face = TRANSFORM_SOURCE.replace(
|
|
'skCircle(sketch, "E0", {"center":v(0, 0) * mm, "radius":9.53 * mm}); skSolve(sketch);', square,
|
|
).replace(
|
|
'"transformType":TransformType.TRANSLATION_3D, "dx":10 * mm, "dy":0 * mm, "dz":0 * mm, "makeCopy":false',
|
|
'"transformType":TransformType.TRANSLATION_DISTANCE, '
|
|
'"transformDirection":makeQuery(id + "F1.opExtrude", "SWEPT_FACE", FACE, '
|
|
'{"derivedFrom":sQuery(id + "F0.wireOp", EDGE, "E0")}), '
|
|
'"distance":10 * mm, "makeCopy":true',
|
|
)
|
|
swept_result = lower_model(parse_featurescript(swept_face, "distance-swept-face"), {})
|
|
self.assertEqual(swept_result.status, "converted_complete")
|
|
self.assertEqual(swept_result.cdsl["features"][-1]["params"], {
|
|
"source_feature_ids": ["f_F1"],
|
|
"transform": {"type": "translation", "translation_mm": [0.0, -10.0, 0.0]},
|
|
"make_copy": True,
|
|
})
|
|
|
|
copied = lower_model(parse_featurescript(source.replace('"makeCopy":false', '"makeCopy":true'), "distance-copy"), {})
|
|
self.assertEqual(copied.status, "converted_complete")
|
|
self.assertFalse(copied.diagnostics)
|
|
self.assertEqual(copied.cdsl["features"][-1]["params"], {
|
|
"source_feature_ids": ["f_F1"],
|
|
"transform": {"type": "translation", "translation_mm": [-10.0, 0.0, 0.0]},
|
|
"make_copy": True,
|
|
})
|
|
|
|
from engine.cdsl_engine.runtime import rebuild_cdsl
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_cdsl(copied.cdsl, Path(directory) / "distance-copy.step")
|
|
self.assertEqual(rebuilt["solid_count"], 2)
|
|
self.assertEqual(rebuilt["bbox_mm"]["min"], [-19.53, -9.53, 0.0])
|
|
self.assertEqual(rebuilt["bbox_mm"]["max"], [9.53, 9.53, 120.0])
|
|
|
|
def test_translation_distance_cap_edge_uses_its_physical_frame(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0066/00665176.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00665176"), {})
|
|
transforms = {item["id"]: item for item in result.cdsl["features"] if item["atomic_id"] == "transform_bodies"}
|
|
self.assertEqual(transforms["f_F2"]["params"]["transform"], {"type": "translation", "translation_mm": [0.0, 0.0, 381.0]})
|
|
self.assertEqual(transforms["f_F3"]["params"]["transform"], {"type": "translation", "translation_mm": [127.0, 0.0, 0.0]})
|
|
self.assertEqual(transforms["f_F4"]["params"]["transform"], {"type": "translation", "translation_mm": [-127.0, 0.0, 0.0]})
|
|
self.assertNotIn("F2", [item.get("feature_id") for item in result.diagnostics])
|
|
self.assertNotIn("F3", [item.get("feature_id") for item in result.diagnostics])
|
|
self.assertNotIn("F4", [item.get("feature_id") for item in result.diagnostics])
|
|
|
|
def test_translation_distance_cap_face_uses_its_physical_normal(self):
|
|
source = TRANSFORM_SOURCE.replace(
|
|
'"transformType":TransformType.TRANSLATION_3D, "dx":10 * mm, "dy":0 * mm, "dz":0 * mm, "makeCopy":false',
|
|
'"transformType":TransformType.TRANSLATION_DISTANCE, '
|
|
'"transformDirection":makeQuery(id + "F1.opExtrude", "CAP_FACE", FACE, '
|
|
'{"derivedFrom":sQuery(id + "F0.wireOp", EDGE, "E0"), "isStart":false}), '
|
|
'"distance":10 * mm, "makeCopy":true',
|
|
)
|
|
result = lower_model(parse_featurescript(source, "distance-cap-face"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
self.assertFalse(result.diagnostics)
|
|
self.assertEqual(result.cdsl["features"][-1]["params"], {
|
|
"source_feature_ids": ["f_F1"],
|
|
"transform": {"type": "translation", "translation_mm": [0.0, 0.0, 10.0]},
|
|
"make_copy": True,
|
|
})
|
|
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0007/00078650.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
real = lower_model(parse_featurescript(feature.read_text(), "00078650"), {})
|
|
self.assertEqual(real.status, "converted_complete")
|
|
transform = next(item for item in real.cdsl["features"] if item["id"] == "f_F4")
|
|
self.assertEqual(transform["params"], {
|
|
"source_feature_ids": ["f_F1", "f_F2", "f_F3"],
|
|
"transform": {"type": "translation", "translation_mm": [0.0, -431.8, 0.0]},
|
|
"make_copy": True,
|
|
})
|
|
|
|
def test_translation_distance_rejects_a_curved_direction(self):
|
|
source = TRANSFORM_SOURCE.replace(
|
|
'"transformType":TransformType.TRANSLATION_3D, "dx":10 * mm, "dy":0 * mm, "dz":0 * mm, "makeCopy":false',
|
|
'"transformType":TransformType.TRANSLATION_DISTANCE, "transformDirection":sQuery(id + "F0.wireOp", EDGE, "E0"), '
|
|
'"distance":10 * mm, "makeCopy":false',
|
|
)
|
|
result = lower_model(parse_featurescript(source, "distance-curve"), {})
|
|
self.assertEqual(result.status, "converted_partial")
|
|
self.assertEqual(result.diagnostics[-1]["capability"], "transform_translation_direction")
|
|
|
|
cap_circle = lower_model(parse_featurescript(source.replace(
|
|
'sQuery(id + "F0.wireOp", EDGE, "E0")',
|
|
'makeQuery(id + "F1.opExtrude", "CAP_EDGE", EDGE, {"derivedFrom":sQuery(id + "F0.wireOp", EDGE, "E0"), "isStart":false})',
|
|
), "distance-cap-circle"), {})
|
|
self.assertEqual(cap_circle.status, "converted_partial")
|
|
self.assertEqual(cap_circle.diagnostics[-1]["capability"], "transform_translation_direction")
|
|
|
|
def test_translation_entity_lowers_direct_line_and_vertex_vectors(self):
|
|
sketch = (
|
|
'skCircle(sketch, "E0", {"center":v(0, 0) * mm, "radius":9.53 * mm}); '
|
|
'skLineSegment(sketch, "E1", {"start":v(0, 0) * mm, "end":v(2, 0) * mm, "construction":true}); '
|
|
'skPoint(sketch, "E2", {"position":v(0, 0) * mm}); '
|
|
'skPoint(sketch, "E3", {"position":v(0, 5) * mm}); skSolve(sketch);'
|
|
)
|
|
line_source = TRANSFORM_SOURCE.replace(
|
|
'skCircle(sketch, "E0", {"center":v(0, 0) * mm, "radius":9.53 * mm}); skSolve(sketch);', sketch,
|
|
).replace(
|
|
'"transformType":TransformType.TRANSLATION_3D, "dx":10 * mm, "dy":0 * mm, "dz":0 * mm, "makeCopy":false',
|
|
'"transformType":TransformType.TRANSLATION_ENTITY, "transformLine":sQuery(id + "F0.wireOp", EDGE, "E1"), '
|
|
'"oppositeDirectionEntity":false, "makeCopy":true',
|
|
)
|
|
copied = lower_model(parse_featurescript(line_source, "entity-line-copy"), {})
|
|
self.assertEqual(copied.status, "converted_complete")
|
|
self.assertEqual(copied.cdsl["features"][-1]["params"], {
|
|
"source_feature_ids": ["f_F1"],
|
|
"transform": {"type": "translation", "translation_mm": [2.0, 0.0, 0.0]},
|
|
"make_copy": True,
|
|
})
|
|
|
|
vertex_source = line_source.replace(
|
|
'"transformLine":sQuery(id + "F0.wireOp", EDGE, "E1"), "oppositeDirectionEntity":false, "makeCopy":true',
|
|
'"transformLine":qUnion([sQuery(id + "F0.wireOp", VERTEX, "E2"), sQuery(id + "F0.wireOp", VERTEX, "E3")]), '
|
|
'"oppositeDirectionEntity":false, "makeCopy":false',
|
|
)
|
|
moved = lower_model(parse_featurescript(vertex_source, "entity-vertices-move"), {})
|
|
self.assertEqual(moved.status, "converted_complete")
|
|
self.assertEqual(moved.cdsl["geometry"]["sketches"][0]["workplane"]["origin_mm"], [0.0, 5.0, 0.0])
|
|
|
|
curved = lower_model(parse_featurescript(line_source.replace('EDGE, "E1"', 'EDGE, "E0"'), "entity-curve"), {})
|
|
self.assertEqual(curved.status, "converted_partial")
|
|
self.assertEqual(curved.diagnostics[-1]["capability"], "transform_translation_entity")
|
|
|
|
cap_circle = lower_model(parse_featurescript(line_source.replace(
|
|
'sQuery(id + "F0.wireOp", EDGE, "E1")',
|
|
'makeQuery(id + "F1.opExtrude", "CAP_EDGE", EDGE, {"derivedFrom":sQuery(id + "F0.wireOp", EDGE, "E0"), "isStart":false})',
|
|
), "entity-cap-circle"), {})
|
|
self.assertEqual(cap_circle.status, "converted_partial")
|
|
self.assertEqual(cap_circle.diagnostics[-1]["capability"], "transform_translation_entity")
|
|
|
|
def test_translation_entity_real_line_cap_and_body_lifecycle_boundaries(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
direct = root / "featurescript_rp/0012/00122249.txt"
|
|
cap_edge = root / "featurescript_rp/0018/00184423.txt"
|
|
absorbed = root / "featurescript_rp/0022/00226751.txt"
|
|
if not all(path.exists() for path in (direct, cap_edge, absorbed)):
|
|
self.skipTest("CADFS samples are not installed")
|
|
|
|
direct_result = lower_model(parse_featurescript(direct.read_text(), "00122249"), {})
|
|
self.assertEqual(direct_result.status, "converted_complete")
|
|
moved_sketch = next(item for item in direct_result.cdsl["geometry"]["sketches"] if item["id"] == "sketch_F1__f_F2")
|
|
self.assertEqual(moved_sketch["workplane"]["origin_mm"], [-7.0, 0.0, 0.0])
|
|
|
|
cap_result = lower_model(parse_featurescript(cap_edge.read_text(), "00184423"), {})
|
|
transform = next(item for item in cap_result.cdsl["features"] if item["id"] == "f_F2")
|
|
self.assertEqual(transform["params"]["transform"], {"type": "translation", "translation_mm": [12.7, 0.0, 0.0]})
|
|
self.assertEqual(cap_result.status, "converted_complete")
|
|
self.assertFalse(cap_result.diagnostics)
|
|
transforms = {item["id"]: item for item in cap_result.cdsl["features"] if item["atomic_id"] == "transform_bodies"}
|
|
self.assertEqual(
|
|
{feature_id: item["params"]["source_feature_ids"] for feature_id, item in transforms.items()},
|
|
{"f_F2": ["f_F1"], "f_F3": ["f_F2"], "f_F4": ["f_F3"], "f_F5": ["f_F4"], "f_F6": ["f_F4"]},
|
|
)
|
|
self.assertEqual(transforms["f_F5"]["params"]["transform"]["type"], "translation")
|
|
for actual, expected in zip(transforms["f_F5"]["params"]["transform"]["translation_mm"], [0.0, -12.7, 0.0]):
|
|
self.assertAlmostEqual(actual, expected)
|
|
from engine.cdsl_engine.runtime import rebuild_cdsl
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_cdsl(cap_result.cdsl, Path(directory) / "transform-copy-chain.step")
|
|
self.assertEqual(rebuilt["solid_count"], 6)
|
|
self.assertEqual([item["feature_id"] for item in rebuilt["feature_results"]], [
|
|
"f_F1", "f_F2", "f_F3", "f_F4", "f_F5", "f_F6",
|
|
])
|
|
self.assertFalse(rebuilt["runtime_diagnostics"])
|
|
|
|
absorbed_result = lower_model(parse_featurescript(absorbed.read_text(), "00226751"), {})
|
|
self.assertEqual(absorbed_result.diagnostics[-1]["capability"], "transform_body_lifecycle")
|
|
|
|
def test_direct_swept_body_delete_lowers_to_a_body_graph_operation(self):
|
|
source = SOURCE.replace(
|
|
'\n});\n',
|
|
'''
|
|
{ var Q0; Q0=makeQuery(id + "F1.opExtrude", "SWEPT_BODY", BODY, {});
|
|
deleteBodies(context, id + "F2", {"entities":qUnion([Q0])}); }
|
|
});
|
|
''',
|
|
)
|
|
result = lower_model(parse_featurescript(source, "delete-body"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
delete = result.cdsl["features"][-1]
|
|
self.assertEqual(delete["atomic_id"], "delete_bodies")
|
|
self.assertEqual(delete["params"], {"target_feature_ids": ["f_F1"]})
|
|
|
|
def test_symmetric_cut_lowers_to_two_sided_cut(self):
|
|
source = SOURCE.replace('"depth":120 * mm', '"operationType":NewBodyOperationType.REMOVE, "depth":120 * mm, "symmetric":true')
|
|
result = lower_model(parse_featurescript(source, "symmetric-cut"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
feature = result.cdsl["features"][0]
|
|
self.assertEqual(feature["atomic_id"], "extrude_cut_two_sided")
|
|
self.assertEqual(feature["params"]["distance_mm"], 60.0)
|
|
self.assertEqual(feature["params"]["reverse_distance_mm"], 60.0)
|
|
|
|
def test_new_body_operation_only_new_emits_new_body_result_mode(self):
|
|
"""FeatureScript ADD uses the CDSL fuse path; only NEW creates a member."""
|
|
new = lower_model(parse_featurescript(
|
|
SOURCE.replace('"depth":120 * mm', '"operationType":NewBodyOperationType.NEW, "depth":120 * mm'),
|
|
"explicit-new",
|
|
), {})
|
|
add = lower_model(parse_featurescript(
|
|
SOURCE.replace('"depth":120 * mm', '"operationType":NewBodyOperationType.ADD, "depth":120 * mm'),
|
|
"explicit-add",
|
|
), {})
|
|
self.assertEqual(new.cdsl["features"][0]["params"].get("result_mode"), "new_body")
|
|
self.assertNotIn("result_mode", add.cdsl["features"][0]["params"])
|
|
|
|
def test_typed_add_sweep_and_revolve_do_not_emit_new_body_result_mode(self):
|
|
"""The shared ADD lifecycle rule applies to every additive producer."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test/featurescript_rp"
|
|
sweep_source = root / "0000/00007135.txt"
|
|
revolve_source = root / "0004/00048326.txt"
|
|
if not sweep_source.exists() or not revolve_source.exists():
|
|
self.skipTest("CADFS sweep/revolve source samples are not installed")
|
|
sweep = lower_model(parse_featurescript(
|
|
sweep_source.read_text(encoding="utf-8").replace(
|
|
'"path" : qUnion([Q1])',
|
|
'"path" : qUnion([Q1]), "operationType" : NewBodyOperationType.ADD',
|
|
1,
|
|
),
|
|
"00007135-typed-add",
|
|
), {})
|
|
revolve = lower_model(parse_featurescript(
|
|
revolve_source.read_text(encoding="utf-8").replace(
|
|
'"revolveType" : RevolveType.FULL',
|
|
'"revolveType" : RevolveType.FULL, "operationType" : NewBodyOperationType.ADD',
|
|
1,
|
|
),
|
|
"00048326-typed-add",
|
|
), {})
|
|
sweep_feature = next(item for item in sweep.cdsl["features"] if item["atomic_id"] == "sweep_add")
|
|
revolve_feature = next(item for item in revolve.cdsl["features"] if item["atomic_id"] == "revolve_add")
|
|
self.assertNotIn("result_mode", sweep_feature["params"])
|
|
self.assertNotIn("result_mode", revolve_feature["params"])
|
|
|
|
def test_through_all_extrudes_reuse_the_engine_extent_contract(self):
|
|
for operation_type, expected_atomic in [
|
|
("NewBodyOperationType.NEW", "extrude_add_blind"),
|
|
("NewBodyOperationType.REMOVE", "extrude_cut_blind"),
|
|
]:
|
|
with self.subTest(operation_type=operation_type):
|
|
source = SOURCE.replace(
|
|
'"depth":120 * mm',
|
|
f'"operationType":{operation_type}, "depth":120 * mm, "endBound":BoundingType.THROUGH_ALL',
|
|
)
|
|
result = lower_model(parse_featurescript(source, f"through-all-{operation_type}"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
feature = result.cdsl["features"][0]
|
|
self.assertEqual(feature["atomic_id"], expected_atomic)
|
|
self.assertEqual(feature["params"]["end_condition"]["type"], "through_all")
|
|
|
|
def test_up_to_next_extrudes_reuse_the_engine_extent_contract(self):
|
|
for operation_type, expected_atomic in [
|
|
("NewBodyOperationType.NEW", "extrude_add_blind"),
|
|
("NewBodyOperationType.REMOVE", "extrude_cut_blind"),
|
|
]:
|
|
with self.subTest(operation_type=operation_type):
|
|
source = SOURCE.replace(
|
|
'"depth":120 * mm',
|
|
f'"operationType":{operation_type}, "depth":120 * mm, "endBound":BoundingType.UP_TO_NEXT',
|
|
)
|
|
result = lower_model(parse_featurescript(source, f"up-to-next-{operation_type}"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
feature = result.cdsl["features"][0]
|
|
self.assertEqual(feature["atomic_id"], expected_atomic)
|
|
self.assertEqual(feature["params"]["end_condition"]["type"], "through_next")
|
|
|
|
def test_implicit_extrude_after_a_body_uses_default_new_body_semantics(self):
|
|
source = SOURCE.replace(
|
|
'\n});\n',
|
|
'''
|
|
{ var Q0; Q0=qSketchRegion(id + "F0", true);
|
|
extrude(context, id + "F2", {"entities":qUnion([Q0]), "depth":20 * mm}); }
|
|
});
|
|
''',
|
|
)
|
|
result = lower_model(parse_featurescript(source, "implicit-add"), {})
|
|
first, second = result.cdsl["features"]
|
|
self.assertEqual(first["params"]["result_mode"], "new_body")
|
|
self.assertEqual(second["params"]["result_mode"], "new_body")
|
|
|
|
def test_up_to_body_extrude_captures_target_and_offset(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0069/00694309.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00694309"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
extrude = next(item for item in result.cdsl["features"] if item["id"] == "f_F3")
|
|
end = extrude["params"]["end_condition"]
|
|
self.assertEqual(end["type"], "up_to_body")
|
|
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_primary_cut_copy_cap_edge_chamfer_uses_exact_copy_lineage(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0025/00252195.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(encoding="utf-8"), "00252195"), {})
|
|
chamfer = next(item for item in result.cdsl["features"] if item["id"] == "f_F4")
|
|
selector = chamfer["selectors"][0]
|
|
self.assertEqual(selector["selector_intent"]["query_family"], "COPY")
|
|
self.assertEqual(selector["selector_intent"]["copy_contract"], "primary_cut_cap_edge")
|
|
self.assertEqual(selector["query_input"]["selector_intent"]["query_family"], "CAP_EDGE")
|
|
self.assertNotIn("stable_id", selector)
|
|
self.assertNotIn("geometry", selector)
|
|
|
|
prefix = deepcopy(result.cdsl)
|
|
prefix["features"] = [
|
|
item for item in prefix["features"] if item["id"] in {"f_F1", "f_F3", "f_F4"}
|
|
]
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(prefix, Path(directory) / "copy-primary-cut-cap-edge.step")
|
|
self.assertEqual(rebuilt["status"], "rebuild_failed")
|
|
self.assertEqual(rebuilt["error"]["type"], "RuntimeExecutionError")
|
|
resolution = rebuilt["error"]["selector_resolutions"][0]
|
|
self.assertEqual(resolution["status"], "resolved")
|
|
self.assertEqual(resolution["resolution_mode"], "copy_lineage")
|
|
self.assertEqual([relation["operation"] for relation in resolution["evidence"]["relations"]], [
|
|
"extrude", "subtract",
|
|
])
|
|
|
|
def test_boolean_copy_face_workplane_does_not_reuse_its_nested_cap_frame(self):
|
|
"""COPY(FACE) needs its own runtime provenance, not a nested CAP frame."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0025/00252794.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
|
|
result = lower_model(parse_featurescript(feature.read_text(encoding="utf-8"), "00252794"), {})
|
|
self.assertEqual(result.status, "converted_partial")
|
|
self.assertTrue(any(
|
|
diagnostic["code"] == "sketch_deferred"
|
|
and diagnostic["feature_id"] == "F8"
|
|
and "COPY(FACE) workplane requires a dedicated complete/proven runtime face relation" in diagnostic["message"]
|
|
for diagnostic in result.diagnostics
|
|
))
|
|
self.assertFalse(any(sketch["id"] == "sketch_F8" for sketch in result.cdsl["geometry"]["sketches"]))
|
|
self.assertFalse(any(feature["id"] == "f_F9" for feature in result.cdsl["features"]))
|
|
|
|
# A second primary-cut CAP-face source proves this is an outer COPY
|
|
# contract, rather than a sample-specific F3/F8 coordinate rule.
|
|
second_feature = root / "featurescript_rp/0025/00252895.txt"
|
|
if not second_feature.exists(): self.skipTest("second CADFS sample is not installed")
|
|
second = lower_model(parse_featurescript(second_feature.read_text(encoding="utf-8"), "00252895"), {})
|
|
self.assertEqual(second.status, "converted_partial")
|
|
self.assertTrue(any(
|
|
diagnostic["code"] == "sketch_deferred"
|
|
and diagnostic["feature_id"] == "F4"
|
|
and "COPY(FACE) workplane requires a dedicated complete/proven runtime face relation" in diagnostic["message"]
|
|
for diagnostic in second.diagnostics
|
|
))
|
|
|
|
def test_immediate_primary_cut_copy_cap_face_workplane_uses_runtime_attachment(self):
|
|
"""COPY(CAP_FACE) attaches only through the exact cut successor."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
cases = (
|
|
("00573124", "f_F5", "hole_wizard"),
|
|
("00951631", "f_F5", "extrude_cut_blind"),
|
|
)
|
|
for sample_id, consumer_id, atomic_id in cases:
|
|
with self.subTest(sample_id=sample_id):
|
|
feature = root / f"featurescript_rp/{sample_id[:4]}/{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), {})
|
|
sketch = next(item for item in result.cdsl["geometry"]["sketches"] if item["id"] == "sketch_F4")
|
|
attachment = sketch["attachment"]
|
|
self.assertEqual(attachment["selector_intent"]["copy_contract"], "primary_cut_cap_face_workplane")
|
|
self.assertEqual(attachment["query_input"]["selector_intent"]["query_family"], "CAP_FACE")
|
|
self.assertNotIn("geometry", attachment)
|
|
from engine.cdsl_engine.semantic_validation import validate_semantic_cdsl
|
|
self.assertTrue(validate_semantic_cdsl(result.cdsl)["future_rebuild_ready"])
|
|
tampered = deepcopy(result.cdsl)
|
|
tampered_attachment = next(
|
|
item for item in tampered["geometry"]["sketches"] if item["id"] == "sketch_F4"
|
|
)["attachment"]
|
|
tampered_attachment["query_input"]["selector_intent"]["source_entities"][0]["entity_id"] = "unrelated"
|
|
with self.assertRaisesRegex(ValueError, "complete direct primary-cut source-profile edge set"):
|
|
validate_semantic_cdsl(tampered)
|
|
consumer = next(item for item in result.cdsl["features"] if item["id"] == consumer_id)
|
|
self.assertEqual(consumer["atomic_id"], atomic_id)
|
|
if atomic_id == "hole_wizard":
|
|
self.assertEqual(consumer["params"]["host_face"], attachment)
|
|
prefix = deepcopy(result.cdsl)
|
|
prefix["features"] = [
|
|
item for item in prefix["features"] if item["id"] in {"f_F1", "f_F3", consumer_id}
|
|
]
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(prefix, Path(directory) / f"{sample_id}.step")
|
|
self.assertEqual(rebuilt["status"], "rebuilt")
|
|
resolution = next(
|
|
item for item in rebuilt["result"]["selector_resolution"]
|
|
if item["feature_id"] == consumer_id
|
|
)
|
|
self.assertEqual(resolution["resolution_mode"], "copy_lineage")
|
|
self.assertEqual([relation["operation"] for relation in resolution["evidence"]["relations"]], [
|
|
"extrude", "subtract",
|
|
])
|
|
|
|
def test_direct_prism_cap_face_workplane_stays_runtime_attached_00126630(self):
|
|
"""A direct CAP host is not lowered into a static F2 workplane."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0012/00126630.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
|
|
result = lower_model(parse_featurescript(feature.read_text(encoding="utf-8"), "00126630"), {})
|
|
attachment = next(item for item in result.cdsl["geometry"]["sketches"] if item["id"] == "sketch_F2")["attachment"]
|
|
self.assertEqual(attachment["kind"], "face")
|
|
self.assertEqual(attachment["owner_feature_id"], "f_F1")
|
|
self.assertEqual(attachment["output_role"], "extrude.start")
|
|
self.assertEqual(attachment["selector_intent"]["consumer_contract"], "direct_prism_cap_face_workplane")
|
|
self.assertNotIn("geometry", attachment)
|
|
from engine.cdsl_engine.semantic_validation import validate_semantic_cdsl
|
|
self.assertTrue(validate_semantic_cdsl(result.cdsl)["future_rebuild_ready"])
|
|
|
|
imprint = next(item for item in result.cdsl["geometry"]["sketches"] if item["id"] == "sketch_F2__f_F3")
|
|
self.assertEqual(imprint["profile"]["type"], "planar_imprint")
|
|
self.assertEqual(imprint["profile"]["selections"][0]["fragment"]["external_anchor_id"], "cap_boundary_0")
|
|
self.assertEqual(imprint["profile"]["external_anchors"][0]["selector"]["selector_intent"]["query_family"], "CAP_EDGE")
|
|
|
|
# F1 -> F3 proves the attached CAP-face and its exact CAP-edge
|
|
# boundary resolve before the later independent F4 dress-up gap.
|
|
executable = deepcopy(result.cdsl)
|
|
executable["features"] = [
|
|
item for item in executable["features"] if item["id"] in {"f_F1", "f_F3"}
|
|
]
|
|
from engine.cdsl_engine.runtime import prepare_cdsl_execution
|
|
execution = prepare_cdsl_execution(executable)
|
|
self.assertTrue(execution.analysis.runtime_eligible)
|
|
execution.execute_all()
|
|
resolutions = [item for item in execution.session.selector_resolutions if item["feature_id"] == "f_F3"]
|
|
self.assertEqual([item["resolution_mode"] for item in resolutions], ["operation_role", "kernel_lineage"])
|
|
self.assertTrue(all(item["status"] == "resolved" for item in resolutions))
|
|
|
|
delayed = deepcopy(executable)
|
|
blocker = deepcopy(delayed["features"][0])
|
|
blocker["id"] = "f_blocker"
|
|
blocker["name"] = "blocker"
|
|
delayed["features"] = [delayed["features"][0], blocker, delayed["features"][1]]
|
|
with self.assertRaisesRegex(ValueError, "CAP_FACE attachment must be consumed immediately"):
|
|
validate_semantic_cdsl(delayed)
|
|
|
|
def test_attached_cap_edge_imprint_requires_the_attachment_owner_00126630(self):
|
|
"""An external CAP edge cannot be substituted from another producer."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0012/00126630.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
|
|
mismatched = feature.read_text(encoding="utf-8").replace(
|
|
'makeQuery(id+"F1.opExtrude","CAP_EDGE"',
|
|
'makeQuery(id+"F99.opExtrude","CAP_EDGE"',
|
|
)
|
|
result = lower_model(parse_featurescript(mismatched, "00126630-owner-mismatch"), {})
|
|
self.assertEqual(result.status, "converted_partial")
|
|
self.assertEqual([item["id"] for item in result.cdsl["features"]], ["f_F1"])
|
|
diagnostic = next(item for item in result.diagnostics if item["feature_id"] == "F3")
|
|
self.assertEqual(diagnostic["capability"], "extrude_profile_topology:cap_edge")
|
|
|
|
def test_attached_cap_edge_imprint_rejects_other_fragment_and_consumer_forms(self):
|
|
"""The singleton circle/new-body contract does not absorb nearby forms."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
cases = ("00612552", "00183004", "00880585")
|
|
for sample_id in cases:
|
|
with self.subTest(sample_id=sample_id):
|
|
feature = root / f"featurescript_rp/{sample_id[:4]}/{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.assertFalse(any(
|
|
isinstance(sketch, dict) and (sketch.get("profile") or {}).get("external_anchors")
|
|
for sketch in result.cdsl["geometry"]["sketches"]
|
|
))
|
|
diagnostic = next(item for item in result.diagnostics if item["feature_id"] == "F3")
|
|
self.assertEqual(diagnostic["capability"], "extrude_profile_topology:cap_edge")
|
|
|
|
def test_immediate_primary_cut_copy_swept_face_workplane_uses_runtime_attachment(self):
|
|
"""COPY(SWEPT_FACE) follows the exact tool side face, never its frame."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
cases = (("00321940", "sketch_F4", "f_F5"), ("00171671", "sketch_F4", "f_F5"))
|
|
for sample_id, sketch_id, consumer_id in cases:
|
|
with self.subTest(sample_id=sample_id):
|
|
feature = root / f"featurescript_rp/{sample_id[:4]}/{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), {})
|
|
attachment = next(item for item in result.cdsl["geometry"]["sketches"] if item["id"] == sketch_id)["attachment"]
|
|
self.assertEqual(attachment["selector_intent"]["copy_contract"], "primary_cut_swept_face_workplane")
|
|
self.assertEqual(attachment["query_input"]["selector_intent"]["query_family"], "SWEPT_FACE")
|
|
self.assertNotIn("geometry", attachment)
|
|
from engine.cdsl_engine.semantic_validation import validate_semantic_cdsl
|
|
self.assertTrue(validate_semantic_cdsl(result.cdsl)["future_rebuild_ready"])
|
|
tampered = deepcopy(result.cdsl)
|
|
tampered_attachment = next(item for item in tampered["geometry"]["sketches"] if item["id"] == sketch_id)["attachment"]
|
|
tampered_attachment["query_input"]["selector_intent"]["source_entity"]["entity_id"] = "unrelated"
|
|
with self.assertRaisesRegex(ValueError, "one direct primary-cut source-profile edge"):
|
|
validate_semantic_cdsl(tampered)
|
|
prefix = deepcopy(result.cdsl)
|
|
prefix["features"] = [
|
|
item for item in prefix["features"] if item["id"] in {"f_F1", "f_F3", consumer_id}
|
|
]
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(prefix, Path(directory) / f"{sample_id}.step")
|
|
if sample_id == "00321940":
|
|
self.assertEqual(rebuilt["status"], "rebuilt")
|
|
resolution = next(item for item in rebuilt["result"]["selector_resolution"] if item["feature_id"] == consumer_id)
|
|
self.assertEqual(resolution["resolution_mode"], "copy_lineage")
|
|
self.assertEqual([relation["operation"] for relation in resolution["evidence"]["relations"]], ["extrude", "subtract"])
|
|
else:
|
|
self.assertEqual(rebuilt["status"], "rebuilt")
|
|
self.assertEqual(
|
|
[item["feature_id"] for item in rebuilt["result"]["feature_results"]],
|
|
["f_F1", "f_F3"],
|
|
)
|
|
self.assertFalse(any(
|
|
item["feature_id"] == consumer_id
|
|
for item in rebuilt["result"]["selector_resolution"]
|
|
))
|
|
|
|
def test_primary_cut_copy_swept_face_requires_a_unique_active_successor(self):
|
|
"""A real direct source still rejects when OCC reports no continuation."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0032/00326645.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(encoding="utf-8"), "00326645"), {})
|
|
attachment = next(item for item in result.cdsl["geometry"]["sketches"] if item["id"] == "sketch_F4")["attachment"]
|
|
self.assertEqual(attachment["selector_intent"]["copy_contract"], "primary_cut_swept_face_workplane")
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(result.cdsl, Path(directory) / "00326645.step")
|
|
self.assertEqual(rebuilt["status"], "rebuild_failed")
|
|
resolution = rebuilt["error"]["selector_resolutions"][0]
|
|
self.assertEqual(resolution["diagnostic"]["code"], "selector_copy_kernel_history_missing")
|
|
|
|
def test_up_to_body_q_owner_body_projects_a_direct_swept_face(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0069/00694309.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
source = feature.read_text().replace(
|
|
'Q1=makeQuery(id+"F1.opExtrude","SWEPT_BODY",BODY,{"disambiguationData":[OSD([sQuery(id+"F0.wireOp",EDGE,"E0")])]});',
|
|
'Q1=qOwnerBody(makeQuery(id+"F1.opExtrude","SWEPT_FACE",FACE,{"disambiguationData":[OSD([sQuery(id+"F0.wireOp",EDGE,"E0")])]}));',
|
|
).replace('"endBoundEntityBody" : qUnion([Q1])', '"endBoundEntityBody" : Q1')
|
|
result = lower_model(parse_featurescript(source, "00694309-owner-body"), {})
|
|
|
|
self.assertEqual(result.status, "converted_complete")
|
|
reference = next(item for item in result.cdsl["features"] if item["id"] == "f_F3")["params"]["end_condition"]["reference"]
|
|
self.assertEqual(reference["kind"], "body")
|
|
self.assertEqual(reference["selector_intent"]["query_family"], "OWNER_BODY")
|
|
self.assertEqual(reference["selector_intent"]["owner_body_contract"], "exact_input_owner")
|
|
self.assertEqual(reference["selector_intent"]["query_expr"]["root"]["filter"], "owner_body")
|
|
self.assertEqual(reference["query_input"]["selector_intent"]["query_family"], "SWEPT_FACE")
|
|
|
|
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:
|
|
rebuilt = rebuild_candidate(result.cdsl, Path(directory) / "owner-body.step")
|
|
self.assertEqual(rebuilt["status"], "rebuilt")
|
|
|
|
def test_up_to_surface_extrude_captures_the_cap_face(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")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00925274"), {})
|
|
extrude = next(item for item in result.cdsl["features"] if item["id"] == "f_F3")
|
|
end = extrude["params"]["end_condition"]
|
|
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"]["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")
|
|
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"])
|
|
|
|
def test_up_to_surface_can_consume_an_immediate_imprint_prism_retained_wall(self):
|
|
"""One unchanged selected source edge may prove its own prism wall."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
source_path = root / "featurescript_rp/0040/00408613.txt"
|
|
if not source_path.exists(): self.skipTest("CADFS sample is not installed")
|
|
source = source_path.read_text(encoding="utf-8")
|
|
result = lower_model(parse_featurescript(source, "00408613"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
feature = next(item for item in result.cdsl["features"] if item["id"] == "f_F3")
|
|
reference = feature["params"]["end_condition"]["reference"]
|
|
self.assertEqual(reference["selector_intent"]["query_family"], "SWEPT_FACE")
|
|
self.assertEqual(
|
|
reference["selector_intent"]["consumer_contract"],
|
|
"immediate_retained_source_prism_swept_face_up_to_surface",
|
|
)
|
|
self.assertEqual(
|
|
reference["selector_intent"]["derivation_policy"],
|
|
{"allowed": ["boundary"], "multiplicity": "one"},
|
|
)
|
|
self.assertNotIn("stable_id", reference)
|
|
self.assertNotIn("geometry", reference)
|
|
|
|
prefix = deepcopy(result.cdsl)
|
|
prefix["features"] = prefix["features"][:2]
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(prefix, Path(directory) / "00408613-retained-wall.step")
|
|
self.assertEqual(rebuilt["status"], "rebuilt")
|
|
resolution = next(item for item in rebuilt["result"]["selector_resolution"] if item["feature_id"] == "f_F3")
|
|
self.assertEqual(resolution["resolution_mode"], "kernel_lineage")
|
|
self.assertEqual(resolution["record"]["kind"], "face")
|
|
self.assertEqual(resolution["evidence"]["semantic_anchor"], {
|
|
"type": "source_entity", "sketch_id": "F0", "entity_id": "E0.bottom",
|
|
})
|
|
|
|
from engine.cdsl_engine.runtime import analyze_cdsl
|
|
from engine.cdsl_engine.semantic_validation import validate_semantic_cdsl
|
|
|
|
self.assertTrue(validate_semantic_cdsl(prefix)["future_rebuild_ready"])
|
|
self.assertTrue(analyze_cdsl(prefix).feature_results[-1].executable)
|
|
|
|
tampered = deepcopy(prefix)
|
|
tampered_reference = tampered["features"][-1]["params"]["end_condition"]["reference"]
|
|
tampered_reference["selector_intent"]["source_entity"]["entity_id"] = "E1"
|
|
with self.assertRaisesRegex(ValueError, "retained source prism SWEPT_FACE"):
|
|
validate_semantic_cdsl(tampered)
|
|
self.assertIn(
|
|
"unsupported_retained_source_prism_swept_face_extent",
|
|
[blocker.code for blocker in analyze_cdsl(tampered).feature_results[-1].blockers],
|
|
)
|
|
|
|
# E1 is an original circle but not part of F1's selected IMPRINT
|
|
# region. A query change must not recover it by geometry or owner.
|
|
rejected = lower_model(parse_featurescript(source.replace(
|
|
'sQuery(id+"F0.wireOp",EDGE,"E0.bottom")])]});\n extrude(context, id + "F3"',
|
|
'sQuery(id+"F0.wireOp",EDGE,"E1")] )]});\n extrude(context, id + "F3"',
|
|
), "00408613-unselected-circle"), {})
|
|
self.assertNotIn("f_F3", {item["id"] for item in rejected.cdsl["features"]})
|
|
self.assertIn(("F3", "extrude_extent_face_selector"), {
|
|
(item.get("feature_id"), item.get("capability")) for item in rejected.diagnostics
|
|
})
|
|
|
|
def test_shell_mutated_swept_body_cannot_be_reused_as_an_up_to_body_target(self):
|
|
"""A historical prism body is not an active-body fallback after shell."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
source_path = root / "featurescript_rp/0041/00415457.txt"
|
|
if not source_path.exists():
|
|
self.skipTest("CADFS sample is not installed")
|
|
|
|
result = lower_model(parse_featurescript(source_path.read_text(encoding="utf-8"), "00415457"), {})
|
|
self.assertEqual(result.status, "converted_partial")
|
|
self.assertNotIn("f_F5", {feature["id"] for feature in result.cdsl["features"]})
|
|
self.assertIn({
|
|
"code": "unsupported_engine_capability",
|
|
"capability": "extrude_extent_body_selector",
|
|
"feature_id": "F5",
|
|
"operation": "extrude",
|
|
"message": "current CDSL body extent requires a complete/proven active selector reference",
|
|
}, result.diagnostics)
|
|
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(result.cdsl, Path(directory) / "00415457-shell-body-boundary.step")
|
|
self.assertEqual(rebuilt["status"], "rebuilt")
|
|
self.assertEqual(
|
|
[item["feature_id"] for item in rebuilt["result"]["feature_results"]],
|
|
["f_F1", "f_F2", "f_F3", "f_F7"],
|
|
)
|
|
|
|
def test_mirrored_copy_cap_face_uses_the_mirrored_loft_frame(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0061/00612529.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00612529"), {})
|
|
sketch = next(item for item in result.cdsl["geometry"]["sketches"] if item["id"] == "sketch_F6")
|
|
plane = sketch["workplane"]
|
|
|
|
self.assertEqual(plane["origin_mm"], [0.0, 50.0, 0.0])
|
|
self.assertEqual(plane["normal"], [0.0, 1.0, 0.0])
|
|
self.assertEqual(plane["x_dir"], [-1.0, 0.0, 0.0])
|
|
|
|
spline = sketch["profile"]["contours"][0]["segments"][0]
|
|
first = spline["points"][0]
|
|
self.assertEqual([
|
|
plane["origin_mm"][index]
|
|
+ plane["x_dir"][index] * first[0]
|
|
+ (plane["normal"][(index + 1) % 3] * plane["x_dir"][(index + 2) % 3]
|
|
- plane["normal"][(index + 2) % 3] * plane["x_dir"][(index + 1) % 3]) * first[1]
|
|
for index in range(3)
|
|
], [-60.68, 50.0, 66.22])
|
|
|
|
def test_line_angle_opposite_direction_preserves_the_sketch_side(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")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00925274"), {})
|
|
sketch = next(item for item in result.cdsl["geometry"]["sketches"] if item["id"] == "sketch_F8")
|
|
plane = sketch["workplane"]
|
|
y_dir = [
|
|
plane["normal"][1] * plane["x_dir"][2] - plane["normal"][2] * plane["x_dir"][1],
|
|
plane["normal"][2] * plane["x_dir"][0] - plane["normal"][0] * plane["x_dir"][2],
|
|
plane["normal"][0] * plane["x_dir"][1] - plane["normal"][1] * plane["x_dir"][0],
|
|
]
|
|
center = sketch["profile"]["center"]
|
|
self.assertAlmostEqual(plane["origin_mm"][2] + y_dir[2] * center[1], 25.0)
|
|
|
|
def test_line_angle_cylinder_reference_uses_the_surface_generatrix(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")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00925274"), {})
|
|
sketch = next(item for item in result.cdsl["geometry"]["sketches"] if item["id"] == "sketch_F5")
|
|
plane = sketch["workplane"]
|
|
self.assertAlmostEqual(plane["normal"][0], -0.5)
|
|
self.assertAlmostEqual(plane["normal"][1], -math.sqrt(3) / 2)
|
|
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_q_body_type_source_wires_keep_one_construction_axis_per_sketch(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0050/00506444.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00506444"), {})
|
|
|
|
self.assertEqual(result.status, "converted_complete")
|
|
self.assertTrue(any(
|
|
item["id"] == "f_F6" and item["atomic_id"] == "reference_plane"
|
|
for item in result.cdsl["features"]
|
|
))
|
|
self.assertTrue(any(
|
|
item["id"] == "f_F8" and item["atomic_id"] == "extrude_add_blind"
|
|
for item in result.cdsl["features"]
|
|
))
|
|
self.assertFalse(any(
|
|
diagnostic.get("feature_id") == "F6" and diagnostic.get("operation") == "cPlane"
|
|
for diagnostic in result.diagnostics
|
|
))
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(result.cdsl, Path(directory) / "wire-axis.step")
|
|
self.assertEqual(rebuilt["status"], "rebuilt")
|
|
|
|
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_prism_swept_edge_is_a_source_datum_axis(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
cases = {
|
|
# One axis-only plane and one axis-plus-default-plane form. Both
|
|
# source OSDs name the unique profile vertex that the immediately
|
|
# preceding unchanged direct prism sweeps along its blind span.
|
|
"00040198": "f_F2",
|
|
"00722278": "f_F2",
|
|
}
|
|
for sample_id, feature_id 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), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
plane_feature = next(item for item in result.cdsl["features"] if item["id"] == feature_id)
|
|
self.assertEqual(plane_feature["atomic_id"], "reference_plane")
|
|
self.assertFalse(any(
|
|
diagnostic.get("feature_id") == "F2" and diagnostic.get("operation") == "cPlane"
|
|
for diagnostic in result.diagnostics
|
|
))
|
|
self.assertNotIn("selectors", plane_feature)
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(result.cdsl, Path(directory) / f"{sample_id}.step")
|
|
self.assertEqual(rebuilt["status"], "rebuilt")
|
|
|
|
# An immediate derived face alongside the SWEPT_EDGE is not an
|
|
# explicit datum reference plane. It must not inherit this axis-only
|
|
# contract merely because the prism itself is otherwise direct.
|
|
source = root / "featurescript_rp/0064/00644299.txt"
|
|
deferred = lower_model(parse_featurescript(source.read_text(), "00644299"), {})
|
|
self.assertIn({
|
|
"code": "feature_deferred",
|
|
"feature_id": "F2",
|
|
"operation": "cPlane",
|
|
"message": "line-angle reference selection is unsupported",
|
|
}, deferred.diagnostics)
|
|
|
|
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"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
profile = result.cdsl["geometry"]["sketches"][0]["profile"]
|
|
self.assertEqual(profile["type"], "analytic_contours")
|
|
self.assertEqual(profile["construction"][0]["type"], "line")
|
|
|
|
def test_open_imprint_profiles_and_mixed_sketch_context_lower_for_00789939(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")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00789939"), {})
|
|
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
sketches = {item["id"]: item for item in result.cdsl["geometry"]["sketches"]}
|
|
self.assertEqual(features["f_F8"]["sketch_id"], "sketch_F7__f_F8")
|
|
self.assertEqual(features["f_F10"]["sketch_id"], "sketch_F9__f_F10")
|
|
self.assertEqual(features["f_F12"]["sketch_id"], "sketch_F11")
|
|
self.assertFalse(sketches["sketch_F7__f_F8"]["profile"]["contours"][0]["closed"])
|
|
self.assertEqual(sketches["sketch_F7__f_F8"]["workplane"]["origin_mm"], [45.7, 0.0, 0.0])
|
|
self.assertEqual(sketches["sketch_F9__f_F10"]["workplane"]["origin_mm"], [-44.3, -0.0, -0.0])
|
|
self.assertEqual(sketches["sketch_F7__f_F8"]["workplane"]["normal"], [1.0, -0.0, -0.0])
|
|
self.assertEqual(sketches["sketch_F9__f_F10"]["workplane"]["normal"], [-1.0, -0.0, -0.0])
|
|
self.assertEqual(sketches["sketch_F9__f_F10"]["workplane"]["x_dir"], [-0.0, -1.0, -0.0])
|
|
|
|
def test_offset_face_profile_is_the_proven_linear_extrusion_wall(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")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00789939"), {})
|
|
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
sketches = {item["id"]: item for item in result.cdsl["geometry"]["sketches"]}
|
|
self.assertEqual(features["f_F15"]["sketch_id"], "sketch_F5__f_F15")
|
|
sketch = sketches["sketch_F5__f_F15"]
|
|
self.assertEqual(sketch["workplane"]["origin_mm"], [-0.0, 52.19, -0.0])
|
|
self.assertEqual(sketch["workplane"]["normal"], [0.0, -1.0, 0.0])
|
|
points = {
|
|
tuple(round(component, 6) for component in endpoint)
|
|
for segment in sketch["profile"]["contours"][0]["segments"]
|
|
for endpoint in (segment["start"], segment["end"])
|
|
}
|
|
self.assertEqual(points, {(-45.0, 2.5), (45.0, 2.5), (45.0, 6.0), (-45.0, 6.0)})
|
|
|
|
def test_offset_face_profile_defers_when_query_has_multiple_source_edges(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")
|
|
source = feature.read_text(encoding="utf-8").replace(
|
|
'Q0=makeQuery(id+"F5.opShell","OFFSET_FACE",FACE,{"disambiguationData":[OSD([sQuery(id+"F3.wireOp",EDGE,"E1.bottom")])]});',
|
|
'Q0=makeQuery(id+"F5.opShell","OFFSET_FACE",FACE,{"disambiguationData":[OSD([sQuery(id+"F3.wireOp",EDGE,"E1.bottom"),sQuery(id+"F3.wireOp",EDGE,"E1.top")])]});',
|
|
1,
|
|
)
|
|
result = lower_model(parse_featurescript(source, "00789939-offset-face-multi-source"), {})
|
|
|
|
self.assertNotIn("f_F15", {item["id"] for item in result.cdsl["features"]})
|
|
diagnostic = next(item for item in result.diagnostics if item.get("feature_id") == "F15")
|
|
self.assertEqual(diagnostic["capability"], "extrude_profile_topology:offset_face")
|
|
|
|
def test_offset_face_profile_uses_the_retained_direct_shell_contract(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0058/00588094.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00588094"), {})
|
|
|
|
profile = next(item for item in result.cdsl["features"] if item["id"] == "f_F14")
|
|
selector = profile["selectors"][0]
|
|
self.assertEqual(profile["atomic_id"], "extrude_from_face")
|
|
self.assertEqual(selector["owner_feature_id"], "f_F13")
|
|
self.assertEqual(selector["output_role"], "shell.offset_face")
|
|
self.assertEqual(selector["output_role_source"], {
|
|
"owner_feature_id": "f_F12", "output_role": "extrude.start",
|
|
})
|
|
self.assertFalse(any(item.get("feature_id") == "F14" for item in result.diagnostics))
|
|
|
|
def test_retained_direct_prism_shell_cap_offset_face_is_a_runtime_profile(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0071/00719927.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00719927"), {})
|
|
|
|
self.assertEqual(result.status, "converted_complete")
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
profile = features["f_F3"]
|
|
self.assertEqual(profile["atomic_id"], "extrude_from_face")
|
|
self.assertEqual(profile["params"]["result_mode"], "new_body")
|
|
self.assertEqual(features["f_F4"]["params"]["source_feature_ids"], ["f_F2"])
|
|
selector = profile["selectors"][0]
|
|
self.assertEqual(selector["output_role"], "shell.offset_face")
|
|
self.assertEqual(selector["output_role_source"], {
|
|
"owner_feature_id": "f_F1", "output_role": "extrude.start",
|
|
})
|
|
self.assertEqual(
|
|
selector["selector_intent"]["consumer_contract"],
|
|
"shell_retained_direct_prism_cap_offset_face_profile",
|
|
)
|
|
|
|
from engine.cdsl_engine.runtime import rebuild_cdsl
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_cdsl(result.cdsl, Path(directory) / "retained-shell-cap.step")
|
|
resolution = next(item for item in rebuilt["selector_resolution"] if item["feature_id"] == "f_F3")
|
|
self.assertEqual(resolution["status"], "resolved")
|
|
self.assertEqual(resolution["selected"]["owner_feature_ids"], ["f_F2"])
|
|
self.assertEqual(resolution["selected"]["output_roles"], ["shell.offset_face"])
|
|
self.assertEqual(
|
|
resolution["selected"]["output_role_sources"],
|
|
[{"output_role": "shell.offset_face", "owner_feature_id": "f_F1", "source_output_role": "extrude.start"}],
|
|
)
|
|
self.assertEqual([item["status"] for item in rebuilt["feature_results"]], ["executed"] * 4)
|
|
|
|
def test_retained_shell_cap_offset_face_rejects_partial_source_profile(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0071/00719927.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
source = feature.read_text(encoding="utf-8").replace(
|
|
'Q0=makeQuery(id+"F2.opShell","OFFSET_FACE",FACE,{"disambiguationData":[OSD([sQuery(id+"F0.wireOp",EDGE,"E0"),sQuery(id+"F0.wireOp",EDGE,"E1"),sQuery(id+"F0.wireOp",EDGE,"E2")])]});',
|
|
'Q0=makeQuery(id+"F2.opShell","OFFSET_FACE",FACE,{"disambiguationData":[OSD([sQuery(id+"F0.wireOp",EDGE,"E0"),sQuery(id+"F0.wireOp",EDGE,"E1")])]} );',
|
|
1,
|
|
)
|
|
result = lower_model(parse_featurescript(source, "00719927-partial-offset-profile"), {})
|
|
|
|
self.assertNotIn("f_F3", {item["id"] for item in result.cdsl["features"]})
|
|
diagnostic = next(item for item in result.diagnostics if item.get("feature_id") == "F3")
|
|
self.assertEqual(diagnostic["capability"], "extrude_profile_topology:offset_face")
|
|
|
|
def test_retained_shell_cap_offset_face_rejects_the_removed_cap_source(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0071/00719927.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00719927"), {})
|
|
invalid = deepcopy(result.cdsl)
|
|
invalid["features"][2]["selectors"][0]["output_role_source"]["output_role"] = "extrude.end"
|
|
|
|
from engine.cdsl_engine.semantic_validation import validate_semantic_cdsl
|
|
with self.assertRaisesRegex(ValueError, "immediate opposite direct-prism cap removal"):
|
|
validate_semantic_cdsl(invalid)
|
|
|
|
def test_offset_edge_tdd_uses_the_retained_direct_prism_cap_continuation(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0065/00650671.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00650671"), {})
|
|
|
|
self.assertEqual(result.status, "converted_complete")
|
|
chamfer = next(item for item in result.cdsl["features"] if item["id"] == "f_F3")
|
|
selector = chamfer["selectors"][0]
|
|
intent = selector["selector_intent"]
|
|
self.assertEqual(selector["owner_feature_id"], "f_F1")
|
|
self.assertEqual(intent["query_family"], "OFFSET_EDGE")
|
|
self.assertEqual(intent["consumer_contract"], "direct_prism_shell_offset_edge_tdd")
|
|
self.assertEqual(intent["lineage_role"], "extrude.start")
|
|
self.assertEqual(intent["derivation_policy"], {
|
|
"allowed": ["boundary", "continuation"], "multiplicity": "one",
|
|
})
|
|
self.assertEqual(intent["disambiguation"], {
|
|
"type": "offset_edge_tdd_cap_continuation",
|
|
"shell_feature_id": "f_F2",
|
|
"outer_owner_feature_id": "f_F2",
|
|
"tdd_cap_owner_feature_id": "f_F1",
|
|
"tdd_cap_role": "extrude.start",
|
|
"source_entity": {"sketch_id": "F0", "entity_id": "E0"},
|
|
})
|
|
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(result.cdsl, Path(directory) / "offset-edge-tdd.step")
|
|
self.assertEqual(rebuilt["status"], "rebuild_failed")
|
|
self.assertEqual(rebuilt["last_executable_prefix"]["last_feature_id"], "f_F3")
|
|
binding = next(item for item in rebuilt["selector_binding"] if item["feature_id"] == "f_F3")
|
|
self.assertEqual(binding["resolved"][0]["record_id"], "body:f_F2:edge:1")
|
|
self.assertEqual(binding["resolved"][0]["output_roles"], ["extrude.start"])
|
|
|
|
def test_offset_edge_tdd_has_a_second_real_shell_consumer_and_rejects_mixed_set(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0076/00768679.txt"
|
|
mixed_feature = root / "featurescript_rp/0079/00791920.txt"
|
|
if not feature.exists() or not mixed_feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00768679"), {})
|
|
chamfer = next(item for item in result.cdsl["features"] if item["id"] == "f_F8")
|
|
selector = chamfer["selectors"][0]
|
|
self.assertEqual(selector["selector_intent"]["consumer_contract"], "direct_prism_shell_offset_edge_tdd")
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(result.cdsl, Path(directory) / "offset-edge-tdd-second.step")
|
|
self.assertEqual(rebuilt["status"], "rebuilt")
|
|
binding = next(item for item in rebuilt["selector_binding"] if item["feature_id"] == "f_F8")
|
|
self.assertEqual(binding["resolved"][0]["record_id"], "body:f_F7:1:edge:1")
|
|
|
|
mixed = lower_model(parse_featurescript(mixed_feature.read_text(), "00791920"), {})
|
|
fillet = next(item for item in mixed.cdsl["features"] if item["id"] == "f_F3")
|
|
contracts = [
|
|
(item.get("selector_intent") or {}).get("consumer_contract")
|
|
for item in fillet["selectors"]
|
|
]
|
|
self.assertIn("direct_prism_shell_offset_edge_tdd", contracts)
|
|
self.assertIn(None, contracts)
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(mixed.cdsl, Path(directory) / "offset-edge-tdd-mixed.step")
|
|
self.assertEqual(rebuilt["status"], "rebuild_failed")
|
|
self.assertIn("selector_query_unsupported", rebuilt["error"]["message"])
|
|
|
|
def test_offset_edge_vertex_uses_the_direct_prism_swept_edge_continuation(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0005/00059593.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00059593"), {})
|
|
|
|
self.assertEqual(result.status, "converted_complete")
|
|
fillet = next(item for item in result.cdsl["features"] if item["id"] == "f_F3")
|
|
operands = fillet["selectors"][0]["query_operands"]
|
|
vertex_selectors = [
|
|
item for item in operands
|
|
if (item.get("selector_intent") or {}).get("consumer_contract")
|
|
== "direct_prism_shell_offset_edge_vertex"
|
|
]
|
|
self.assertEqual(len(vertex_selectors), 4)
|
|
for selector in vertex_selectors:
|
|
intent = selector["selector_intent"]
|
|
self.assertEqual(intent["query_family"], "OFFSET_EDGE")
|
|
self.assertNotIn("source_entity", intent)
|
|
self.assertEqual(len(intent["source_entities"]), 2)
|
|
self.assertEqual(intent["derivation_policy"], {
|
|
"allowed": ["boundary", "continuation"], "multiplicity": "one",
|
|
})
|
|
self.assertEqual(intent["disambiguation"]["type"], "offset_edge_vertex_continuation")
|
|
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(result.cdsl, Path(directory) / "offset-edge-vertex.step")
|
|
self.assertEqual(rebuilt["status"], "rebuilt")
|
|
binding = next(item for item in rebuilt["selector_binding"] if item["feature_id"] == "f_F3")
|
|
self.assertEqual(len(binding["resolved"]), 8)
|
|
self.assertTrue(all(item["record_id"].startswith("body:f_F2:edge:") for item in binding["resolved"]))
|
|
|
|
def test_offset_edge_vertex_rejects_mixed_osd_and_tdd_set(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0079/00791920.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00791920"), {})
|
|
fillet = next(item for item in result.cdsl["features"] if item["id"] == "f_F3")
|
|
contracts = [(item.get("selector_intent") or {}).get("consumer_contract") for item in fillet["selectors"]]
|
|
self.assertIn("direct_prism_shell_offset_edge_tdd", contracts)
|
|
self.assertIn(None, contracts)
|
|
self.assertNotIn("direct_prism_shell_offset_edge_vertex", contracts)
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(result.cdsl, Path(directory) / "offset-edge-vertex-mixed.step")
|
|
self.assertEqual(rebuilt["status"], "rebuild_failed")
|
|
self.assertIn("selector_query_unsupported", rebuilt["error"]["message"])
|
|
|
|
def test_offset_edge_swept_edge_tdd_does_not_reuse_the_direct_prism_contracts(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0023/00239888.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00239888"), {})
|
|
|
|
# Its OFFSET_EDGE uses TDD(SWEPT_EDGE), but the producer is an
|
|
# IMPRINT profile and the shell follows a fillet. It cannot inherit
|
|
# either the retained CAP_EDGE or immediate source-vertex contract.
|
|
self.assertEqual(result.status, "converted_partial")
|
|
self.assertNotIn("f_F6", {item["id"] for item in result.cdsl["features"]})
|
|
diagnostic = next(item for item in result.diagnostics if item.get("feature_id") == "F6")
|
|
# The IMPRINT producer F3 is absent from the executable prefix, so
|
|
# owner availability must reject the downstream selector before its
|
|
# TDD(SWEPT_EDGE) source form could be considered.
|
|
self.assertEqual(diagnostic["capability"], "selector_owner_unavailable")
|
|
self.assertEqual(diagnostic["message"], "selector owner F3 has no executable CDSL producer")
|
|
|
|
def test_offset_edge_vertex_semantic_validation_rejects_a_mismatched_shell(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0005/00059593.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00059593"), {})
|
|
invalid = deepcopy(result.cdsl)
|
|
selector = next(
|
|
item for item in invalid["features"]
|
|
if item["id"] == "f_F3"
|
|
)["selectors"][0]["query_operands"][8]
|
|
selector["selector_intent"]["disambiguation"]["removed_cap_role"] = "extrude.start"
|
|
|
|
from engine.cdsl_engine.semantic_validation import validate_semantic_cdsl
|
|
with self.assertRaisesRegex(ValueError, "OFFSET_EDGE vertex requires"):
|
|
validate_semantic_cdsl(invalid)
|
|
|
|
from engine.cdsl_engine.capabilities import CapabilityAnalyzer
|
|
from engine.cdsl_engine.registry import EXECUTORS
|
|
from engine.cdsl_engine.sketch_solver import CORE_SHAPE_GENERATORS
|
|
analysis = CapabilityAnalyzer(
|
|
atomic_ids=EXECUTORS, profile_types=CORE_SHAPE_GENERATORS,
|
|
).analyze(invalid)
|
|
f3 = next(item for item in analysis.feature_results if item.feature_id == "f_F3")
|
|
self.assertIn(
|
|
"unsupported_offset_edge_vertex_selector",
|
|
[blocker.code for blocker in f3.blockers],
|
|
)
|
|
|
|
def test_offset_edge_tdd_semantic_validation_rejects_a_mismatched_shell(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0065/00650671.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00650671"), {})
|
|
invalid = deepcopy(result.cdsl)
|
|
selector = next(item for item in invalid["features"] if item["id"] == "f_F3")["selectors"][0]
|
|
selector["selector_intent"]["disambiguation"]["shell_feature_id"] = "f_F1"
|
|
|
|
from engine.cdsl_engine.semantic_validation import validate_semantic_cdsl
|
|
with self.assertRaisesRegex(ValueError, "OFFSET_EDGE TDD requires"):
|
|
validate_semantic_cdsl(invalid)
|
|
|
|
def test_circular_pattern_uses_the_active_typed_add_successor(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")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00542223"), {})
|
|
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
# F5 fuses into F1 and becomes the active member. The circular
|
|
# pattern must use that member instead of reviving stale F1.
|
|
self.assertNotIn("result_mode", features["f_F5"]["params"])
|
|
self.assertEqual(features["f_F6"]["params"]["source_feature_ids"], ["f_F5"])
|
|
|
|
def test_circular_copy_boolean_uses_the_proven_fused_body_successor(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0025/00253824.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00253824"), {})
|
|
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
# F7 attaches to an outer MERGE(FACE), not directly to F4's nested
|
|
# CAP_FACE. The selector has no active provenance contract for that
|
|
# merge result, so preserving F1--F6 is preferable to materializing
|
|
# an unproven static workplane for F7/F8.
|
|
self.assertEqual(result.status, "converted_partial")
|
|
# F4 uses typed ADD and therefore fuses into the active member. F5
|
|
# and the targetless union must consume that active successor.
|
|
self.assertNotIn("result_mode", features["f_F4"]["params"])
|
|
self.assertEqual(features["f_F5"]["params"]["source_feature_ids"], ["f_F4"])
|
|
self.assertEqual(features["f_F6"]["params"], {
|
|
"operation": "union",
|
|
"keep_tools": False,
|
|
"targetless_body_set": True,
|
|
"target_feature_ids": ["f_F4"],
|
|
"tool_pattern_instance_refs": [
|
|
{"pattern_feature_id": "f_F5", "source_feature_id": "f_F4", "instance_index": 2},
|
|
{"pattern_feature_id": "f_F5", "source_feature_id": "f_F4", "instance_index": 1},
|
|
],
|
|
})
|
|
self.assertNotIn("f_F7", features)
|
|
self.assertNotIn("f_F8", features)
|
|
self.assertIn({
|
|
"code": "sketch_deferred",
|
|
"feature_id": "F7",
|
|
"message": "MERGE(FACE) workplane requires a dedicated complete/proven runtime face relation",
|
|
}, result.diagnostics)
|
|
|
|
def test_merge_face_workplane_never_reuses_a_nested_swept_face_frame(self):
|
|
"""Outer MERGE faces must not inherit one derived SWEPT_FACE plane."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
cases = {
|
|
"00013930": ("0001", "F7", "f_F7"),
|
|
"00020631": ("0002", "F4", "f_F4"),
|
|
"00036155": ("0003", "F5", "f_F5"),
|
|
}
|
|
for sample_id, (shard, sketch_id, dependent_feature) in cases.items():
|
|
with self.subTest(sample_id=sample_id):
|
|
feature = root / f"featurescript_rp/{shard}/{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), {})
|
|
features = {item["id"] for item in result.cdsl["features"]}
|
|
self.assertEqual(result.status, "converted_partial")
|
|
self.assertNotIn(dependent_feature, features)
|
|
self.assertIn({
|
|
"code": "sketch_deferred",
|
|
"feature_id": sketch_id,
|
|
"message": "MERGE(FACE) workplane requires a dedicated complete/proven runtime face relation",
|
|
}, result.diagnostics)
|
|
|
|
def test_boolean_uses_source_qualified_multi_source_transform_copies(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0069/00699847.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
source = feature.read_text(encoding="utf-8")
|
|
# This is a source-history contract variant, not a source STEP
|
|
# comparison: F3 is the real history's two-source makeCopy output.
|
|
boolean = ''' {
|
|
var Q0;
|
|
Q0=makeQuery(id+"F3.opPattern","COPY",BODY,{"derivedFrom":makeQuery(id+"F1.opExtrude","SWEPT_BODY",BODY,{"disambiguationData":[OSD([sQuery(id+"F0.wireOp",EDGE,"E0.bottom"),sQuery(id+"F0.wireOp",EDGE,"E0.top"),sQuery(id+"F0.wireOp",EDGE,"E0.left"),sQuery(id+"F0.wireOp",EDGE,"E0.right")])]}),"instanceName":"1"});
|
|
var Q1;
|
|
Q1=makeQuery(id+"F3.opPattern","COPY",BODY,{"derivedFrom":makeQuery(id+"F2.opPattern","COPY",BODY,{"derivedFrom":makeQuery(id+"F1.opExtrude","SWEPT_BODY",BODY,{"disambiguationData":[OSD([sQuery(id+"F0.wireOp",EDGE,"E0.bottom"),sQuery(id+"F0.wireOp",EDGE,"E0.top"),sQuery(id+"F0.wireOp",EDGE,"E0.left"),sQuery(id+"F0.wireOp",EDGE,"E0.right")])]}),"instanceName":"1"}),"instanceName":"1"});
|
|
booleanBodies(context, id + "F5", {"operationType" : BooleanOperationType.UNION, "targets" : qUnion([Q0]), "tools" : qUnion([Q1])});
|
|
}
|
|
'''
|
|
source = source.rsplit(" });", 1)[0] + boolean + " });"
|
|
result = lower_model(parse_featurescript(source, "00699847-copy-boolean-contract"), {})
|
|
|
|
self.assertEqual(result.status, "converted_complete")
|
|
feature = next(item for item in result.cdsl["features"] if item["id"] == "f_F5")
|
|
self.assertEqual(feature["params"], {
|
|
"operation": "union", "keep_tools": False,
|
|
"target_transform_copy_refs": [{"transform_feature_id": "f_F3", "source_feature_id": "f_F1"}],
|
|
"tool_transform_copy_refs": [{"transform_feature_id": "f_F3", "source_feature_id": "f_F2"}],
|
|
})
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(result.cdsl, Path(directory) / "copy-boolean-contract.step")
|
|
self.assertEqual(rebuilt["status"], "rebuilt")
|
|
self.assertEqual([item["feature_id"] for item in rebuilt["result"]["feature_results"]], [
|
|
"f_F1", "f_F2", "f_F3", "f_F4", "f_F5",
|
|
])
|
|
|
|
def test_shared_sketch_edge_produces_each_bounded_region(self):
|
|
segments = [
|
|
{"type": "line", "start": [0, 0], "end": [1, 0]},
|
|
{"type": "line", "start": [1, 0], "end": [2, 0]},
|
|
{"type": "line", "start": [2, 0], "end": [2, 1]},
|
|
{"type": "line", "start": [2, 1], "end": [1, 1]},
|
|
{"type": "line", "start": [1, 1], "end": [0, 1]},
|
|
{"type": "line", "start": [0, 1], "end": [0, 0]},
|
|
{"type": "line", "start": [1, 0], "end": [1, 1]},
|
|
]
|
|
contours, construction = _contours(segments)
|
|
self.assertEqual(len(contours), 2)
|
|
self.assertFalse(construction)
|
|
self.assertEqual(sorted(len(item["segments"]) for item in contours), [4, 4])
|
|
|
|
def test_vertex_on_long_line_is_split_before_region_extraction(self):
|
|
segments = [
|
|
{"type": "line", "start": [0, 0], "end": [2, 0]},
|
|
{"type": "line", "start": [2, 0], "end": [2, 2]},
|
|
{"type": "line", "start": [2, 2], "end": [0, 2]},
|
|
{"type": "line", "start": [0, 2], "end": [0, 0]},
|
|
{"type": "line", "start": [2, 0.5], "end": [3, 0.5]},
|
|
{"type": "line", "start": [3, 0.5], "end": [3, 1.5]},
|
|
{"type": "line", "start": [3, 1.5], "end": [2, 1.5]},
|
|
]
|
|
contours, construction = _contours(segments)
|
|
self.assertEqual(len(contours), 2)
|
|
self.assertFalse(construction)
|
|
|
|
def test_surface_operation_on_a_closed_region_lowers_to_a_solid_revolve(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0095/00957738.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00957738"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
feature = next(item for item in result.cdsl["features"] if item["id"] == "f_F2")
|
|
self.assertEqual(feature["atomic_id"], "revolve_add")
|
|
self.assertNotIn("result_mode", feature["params"])
|
|
|
|
def test_explicit_surface_body_lowers_to_the_surface_revolve_contract(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0078/00784880.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00784880"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
feature = next(item for item in result.cdsl["features"] if item["id"] == "f_F3")
|
|
self.assertEqual(feature["atomic_id"], "revolve_surface")
|
|
self.assertNotIn("result_mode", feature["params"])
|
|
|
|
def test_mixed_extrude_lowers_selected_circular_surface_wires(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")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00710855"), {})
|
|
|
|
self.assertEqual(result.status, "converted_complete")
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
self.assertEqual(features["f_F1_surface"]["atomic_id"], "extrude_surface")
|
|
self.assertEqual(features["f_F1_surface"]["params"], {
|
|
"distance_mm": 63.5,
|
|
"reverse": False,
|
|
"reverse_distance_mm": 63.5,
|
|
})
|
|
self.assertEqual(features["f_F5_surface"]["atomic_id"], "extrude_surface")
|
|
sketches = {item["id"]: item for item in result.cdsl["geometry"]["sketches"]}
|
|
radii = [
|
|
contour["segments"][0]["radius_mm"]
|
|
for contour in sketches["sketch_F0__f_F1_surface"]["profile"]["contours"]
|
|
]
|
|
self.assertEqual(radii, [19.0, 16.0])
|
|
profile = sketches[features["f_F5"]["sketch_id"]]["profile"]
|
|
self.assertEqual(profile["type"], "analytic_contours")
|
|
self.assertEqual(
|
|
[(item["role"], item["segments"][0]["radius_mm"]) for item in profile["contours"]],
|
|
[("outer", 25.5), ("inner", 19.0)],
|
|
)
|
|
|
|
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")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00710855"), {})
|
|
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
from cadfs_to_cdsl.rebuild import rebuild_candidate
|
|
rebuilt = rebuild_candidate(result.cdsl, Path(tmp) / "rebuilt.step")
|
|
|
|
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_pure_surface_extrude_lowers_one_direct_open_line_without_creating_a_body(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0067/00677236.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00677236"), {})
|
|
|
|
self.assertEqual(result.status, "converted_complete")
|
|
first = next(item for item in result.cdsl["features"] if item["id"] == "f_F1")
|
|
self.assertEqual(first["atomic_id"], "extrude_surface")
|
|
sketch = next(item for item in result.cdsl["geometry"]["sketches"] if item["id"] == first["sketch_id"])
|
|
self.assertEqual(sketch["profile"]["contours"], [{
|
|
"role": "open", "closed": False, "surface_wire": True,
|
|
"segments": [{
|
|
"type": "line", "start": [-1012.95, 885.86],
|
|
"end": [-1012.95, -974.14], "source_entity_id": "E0.left",
|
|
}],
|
|
}])
|
|
|
|
prefix = deepcopy(result.cdsl)
|
|
prefix["features"] = [first]
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
from cadfs_to_cdsl.rebuild import rebuild_candidate
|
|
rebuilt = rebuild_candidate(prefix, Path(directory) / "pure-surface-line.step")
|
|
self.assertEqual(rebuilt["status"], "rebuilt")
|
|
self.assertEqual(rebuilt["result"]["solid_count"], 0)
|
|
self.assertEqual(rebuilt["result"]["surface_count"], 1)
|
|
|
|
def test_pure_surface_extrude_lowers_multiple_direct_open_line_wires(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0041/00414424.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00414424"), {})
|
|
|
|
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"]}
|
|
self.assertEqual({features[feature_id]["atomic_id"] for feature_id in ("f_F2", "f_F3", "f_F4")}, {"extrude_surface"})
|
|
self.assertEqual(
|
|
[[segment["source_entity_id"] for segment in contour["segments"]]
|
|
for contour in sketches[features["f_F2"]["sketch_id"]]["profile"]["contours"]],
|
|
[["E26", "E25", "E32"]],
|
|
)
|
|
self.assertEqual(
|
|
[[segment["source_entity_id"] for segment in contour["segments"]]
|
|
for contour in sketches[features["f_F3"]["sketch_id"]]["profile"]["contours"]],
|
|
[["E27"], ["E31"]],
|
|
)
|
|
self.assertEqual(
|
|
[[segment["source_entity_id"] for segment in contour["segments"]]
|
|
for contour in sketches[features["f_F4"]["sketch_id"]]["profile"]["contours"]],
|
|
[["E30", "E29", "E28"]],
|
|
)
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(result.cdsl, Path(directory) / "multi-open-surface-wires.step")
|
|
self.assertEqual(rebuilt["status"], "rebuilt")
|
|
self.assertEqual(rebuilt["result"]["solid_count"], 0)
|
|
self.assertEqual(rebuilt["result"]["surface_count"], 3)
|
|
self.assertEqual(rebuilt["result"]["surface_face_count"], 8)
|
|
|
|
def test_pure_surface_extrude_lowers_a_second_real_multiline_open_wire(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0042/00424680.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00424680"), {})
|
|
|
|
self.assertEqual(result.status, "converted_complete")
|
|
surface = next(item for item in result.cdsl["features"] if item["id"] == "f_F1")
|
|
self.assertEqual(surface["atomic_id"], "extrude_surface")
|
|
sketch = next(item for item in result.cdsl["geometry"]["sketches"] if item["id"] == surface["sketch_id"])
|
|
contours = sketch["profile"]["contours"]
|
|
self.assertEqual(len(contours), 1)
|
|
self.assertEqual(len(contours[0]["segments"]), 4)
|
|
self.assertTrue(contours[0]["surface_wire"])
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(result.cdsl, Path(directory) / "second-multi-open-surface-wire.step")
|
|
self.assertEqual(rebuilt["status"], "rebuilt")
|
|
self.assertEqual(rebuilt["result"]["solid_count"], 0)
|
|
self.assertEqual(rebuilt["result"]["surface_count"], 1)
|
|
self.assertEqual(rebuilt["result"]["surface_face_count"], 4)
|
|
|
|
def test_pure_surface_extrude_accepts_independent_add_and_symmetric_extent(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0059/00592110.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00592110"), {})
|
|
|
|
surface = next(item for item in result.cdsl["features"] if item["id"] == "f_F3")
|
|
self.assertEqual(surface["atomic_id"], "extrude_surface")
|
|
self.assertEqual(surface["params"], {
|
|
"distance_mm": 12.7, "reverse": False, "reverse_distance_mm": 12.7,
|
|
})
|
|
self.assertFalse([item for item in result.diagnostics if item.get("feature_id") == "F3"])
|
|
|
|
def test_pure_surface_extrude_preserves_a_symmetric_direct_circle_shell(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0050/00509429.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00509429"), {})
|
|
|
|
self.assertEqual(result.status, "converted_complete")
|
|
surface = result.cdsl["features"][0]
|
|
self.assertEqual(surface["atomic_id"], "extrude_surface")
|
|
self.assertEqual(surface["params"], {
|
|
"distance_mm": 228.6, "reverse": False, "reverse_distance_mm": 228.6,
|
|
})
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
from cadfs_to_cdsl.rebuild import rebuild_candidate
|
|
rebuilt = rebuild_candidate(result.cdsl, Path(directory) / "pure-surface-circle.step")
|
|
self.assertEqual(rebuilt["status"], "rebuilt")
|
|
self.assertEqual(rebuilt["result"]["solid_count"], 0)
|
|
self.assertEqual(rebuilt["result"]["surface_face_count"], 2)
|
|
|
|
def test_pure_surface_extrude_rejects_remove_semantics(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0059/00595418.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00595418"), {})
|
|
|
|
self.assertNotIn("f_F9", {item["id"] for item in result.cdsl["features"]})
|
|
self.assertIn({
|
|
"code": "unsupported_engine_capability",
|
|
"capability": "extrude_surface_operation",
|
|
"feature_id": "F9",
|
|
"operation": "extrude",
|
|
"message": "pure ToolBodyType.SURFACE extrusion supports only an independent ADD surface operation",
|
|
}, result.diagnostics)
|
|
|
|
def test_pure_surface_extrude_rejects_cross_sketch_source_wires(self):
|
|
source_sketch = {
|
|
"id": "sketch_F0", "name": "F0",
|
|
"profile": {"type": "analytic_contours", "contours": []},
|
|
}
|
|
source_line = {"type": "line", "start": [0, 0], "end": [10, 0]}
|
|
query = Call("qUnion", [[
|
|
Call("sQuery", ["F0.wireOp", "EDGE", "E0"]),
|
|
Call("sQuery", ["F1.wireOp", "EDGE", "E0"]),
|
|
]])
|
|
|
|
with self.assertRaisesRegex(
|
|
UnsupportedCapability,
|
|
"surface extrude source wires must come from one source sketch",
|
|
):
|
|
_surface_profile_selection_sketch(
|
|
source_sketch, query, {"E0": source_line}, "f_surface", allow_open_wire=True,
|
|
)
|
|
|
|
def test_pure_surface_extrude_rejects_branched_or_closed_open_line_sets(self):
|
|
source_sketch = {
|
|
"id": "sketch_F0", "name": "F0",
|
|
"profile": {"type": "analytic_contours", "contours": []},
|
|
}
|
|
def query(*entity_ids):
|
|
return Call("qUnion", [[
|
|
Call("sQuery", ["F0.wireOp", "EDGE", entity_id]) for entity_id in entity_ids
|
|
]])
|
|
|
|
branched = {
|
|
"E0": {"type": "line", "start": [0, 0], "end": [10, 0]},
|
|
"E1": {"type": "line", "start": [0, 0], "end": [0, 10]},
|
|
"E2": {"type": "line", "start": [0, 0], "end": [-10, 0]},
|
|
}
|
|
closed = {
|
|
"E0": {"type": "line", "start": [0, 0], "end": [10, 0]},
|
|
"E1": {"type": "line", "start": [10, 0], "end": [5, 10]},
|
|
"E2": {"type": "line", "start": [5, 10], "end": [0, 0]},
|
|
}
|
|
for entities in (branched, closed):
|
|
with self.assertRaisesRegex(UnsupportedCapability, "non-branching open chains"):
|
|
_surface_profile_selection_sketch(
|
|
source_sketch, query(*entities), entities, "f_surface", allow_open_wire=True,
|
|
)
|
|
|
|
def test_fit_spline_loft_lowers_to_executable_loft_add(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0061/00612529.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00612529"), {})
|
|
self.assertIsNotNone(result.cdsl)
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
self.assertEqual(features["f_F3"]["atomic_id"], "loft_add")
|
|
self.assertEqual(features["f_F3"]["params"]["profile_sketch_ids"], ["sketch_F2", "sketch_F0"])
|
|
self.assertEqual(features["f_F4"]["atomic_id"], "pattern_mirror")
|
|
self.assertEqual(features["f_F4"]["params"]["source_feature_ids"], ["f_F3"])
|
|
sketches = {item["id"]: item for item in result.cdsl["geometry"]["sketches"]}
|
|
self.assertEqual(sketches["sketch_F0"]["profile"]["contours"][0]["segments"][0]["type"], "bspline")
|
|
self.assertNotIn("loft", {item.get("operation") for item in result.diagnostics})
|
|
|
|
def test_fit_spline_loft_candidate_rebuilds(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0061/00612529.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00612529"), {})
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
from cadfs_to_cdsl.rebuild import rebuild_candidate
|
|
rebuilt = rebuild_candidate(result.cdsl, Path(tmp) / "rebuilt.step")
|
|
self.assertEqual(rebuilt["status"], "rebuilt")
|
|
self.assertGreater(rebuilt["result"]["volume_mm3"], 0)
|
|
|
|
def test_initial_direct_loft_shell_cap_uses_osd_profile_role_not_is_start(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
source_path = root / "featurescript_rp/0002/00023963.txt"
|
|
if not source_path.exists(): self.skipTest("CADFS sample is not installed")
|
|
source = source_path.read_text(encoding="utf-8")
|
|
result = lower_model(parse_featurescript(source, "00023963"), {})
|
|
|
|
self.assertEqual(result.status, "converted_complete")
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
self.assertEqual(features["f_F3"]["params"], {
|
|
"profile_sketch_ids": ["sketch_F0", "sketch_F2"],
|
|
"initial_output_roles": True,
|
|
"cap_output_profile_sources": ["F0", "F2"],
|
|
})
|
|
selector = features["f_F4"]["selectors"][0]
|
|
# The source query says isStart:true but its OSD names F2, the second
|
|
# loft input. The output role must follow that profile provenance.
|
|
self.assertEqual(selector["output_role"], "loft.end")
|
|
self.assertEqual(selector["selector_intent"]["disambiguation"], {
|
|
"type": "loft_profile_source", "source_sketch_id": "F2",
|
|
})
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(result.cdsl, Path(directory) / "loft-shell.step")
|
|
self.assertEqual(rebuilt["status"], "rebuilt")
|
|
resolution = next(item for item in rebuilt["result"]["selector_resolution"] if item["feature_id"] == "f_F4")
|
|
self.assertEqual(resolution["resolution_mode"], "operation_role")
|
|
self.assertEqual(resolution["selected"]["output_roles"], ["loft.end"])
|
|
|
|
constrained = source.replace(
|
|
'{"sheetProfilesArray"', '{"matchConnections" : true, "sheetProfilesArray"', 1,
|
|
)
|
|
deferred = lower_model(parse_featurescript(constrained, "loft-cap-constrained"), {})
|
|
deferred_selector = next(item for item in deferred.cdsl["features"] if item["id"] == "f_F4")["selectors"][0]
|
|
self.assertNotIn("initial_output_roles", next(item for item in deferred.cdsl["features"] if item["id"] == "f_F3")["params"])
|
|
self.assertNotIn("output_role", deferred_selector)
|
|
self.assertEqual(deferred_selector["selector_intent"]["evidence"], "feature_script_query")
|
|
|
|
unsupported_version = source.replace("FeatureScript 1511;", "FeatureScript 1549;", 1).replace(
|
|
'version : "1511.0"', 'version : "1549.0"', 2,
|
|
)
|
|
version_deferred = lower_model(parse_featurescript(unsupported_version, "loft-cap-1549"), {})
|
|
version_loft = next(item for item in version_deferred.cdsl["features"] if item["id"] == "f_F3")
|
|
version_selector = next(item for item in version_deferred.cdsl["features"] if item["id"] == "f_F4")["selectors"][0]
|
|
self.assertNotIn("initial_output_roles", version_loft["params"])
|
|
self.assertNotIn("output_role", version_selector)
|
|
|
|
def test_initial_direct_sweep_shell_cap_requires_profile_and_path_endpoint_pair(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
source_path = root / "featurescript_rp/0033/00330012.txt"
|
|
if not source_path.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(source_path.read_text(encoding="utf-8"), "00330012"), {})
|
|
|
|
self.assertEqual(result.status, "converted_complete")
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
self.assertEqual(features["f_F2"]["params"]["cap_output_contract"], {
|
|
"profile_source": "F0", "profile_entity": "E0",
|
|
"path_source": "F1", "path_entity": "E1", "path_reversed": False,
|
|
})
|
|
selector = features["f_F3"]["selectors"][0]
|
|
self.assertNotIn("geometry", selector)
|
|
self.assertNotIn("stable_id", selector)
|
|
self.assertEqual(selector["output_role"], "sweep.end")
|
|
self.assertEqual(selector["selector_intent"]["disambiguation"], {
|
|
"type": "sweep_profile_path_endpoint",
|
|
"profile_source": "F0", "profile_entity": "E0",
|
|
"path_source": "F1", "path_entity": "E1", "path_reversed": False,
|
|
"path_endpoint": "end",
|
|
})
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(result.cdsl, Path(directory) / "sweep-shell.step")
|
|
self.assertEqual(rebuilt["status"], "rebuilt")
|
|
resolution = next(item for item in rebuilt["result"]["selector_resolution"] if item["feature_id"] == "f_F3")
|
|
self.assertEqual(resolution["resolution_mode"], "operation_role")
|
|
self.assertEqual(resolution["selected"]["output_roles"], ["sweep.end"])
|
|
self.assertEqual(resolution["evidence"]["relations"][0]["operation"], "sweep")
|
|
|
|
rejected_path = root / "featurescript_rp/0065/00658358.txt"
|
|
if not rejected_path.exists(): self.skipTest("CADFS source-pair rejection sample is not installed")
|
|
rejected = lower_model(parse_featurescript(rejected_path.read_text(encoding="utf-8"), "00658358"), {})
|
|
# F4 is an attached sketch, not the immediate shell consumer allowed
|
|
# by this CAP_FACE tuple. It must not inherit a static sweep cap
|
|
# frame merely because its query contains the producer's endpoint.
|
|
self.assertNotIn("f_F4", {item["id"] for item in rejected.cdsl["features"]})
|
|
self.assertTrue(any(
|
|
item["feature_id"] == "F4"
|
|
and item["code"] == "feature_deferred"
|
|
and item["message"] == "cap face source frame is unresolved"
|
|
for item in rejected.diagnostics
|
|
))
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
outcome = rebuild_candidate(rejected.cdsl, Path(directory) / "sweep-shell-rejected.step")
|
|
self.assertEqual(outcome["status"], "rebuilt")
|
|
self.assertEqual(outcome["result"]["feature_results"][-1]["feature_id"], "f_F3")
|
|
|
|
reversed_path = root / "featurescript_rp/0026/00260523.txt"
|
|
if not reversed_path.exists(): self.skipTest("CADFS path-reversal source sample is not installed")
|
|
reversed_source = reversed_path.read_text(encoding="utf-8").rsplit(" });", 1)[0] + '''
|
|
{
|
|
var Q0;
|
|
Q0=makeQuery(id+"F3.opSweep","CAP_FACE",FACE,{"disambiguationData":[OSD([sQuery(id+"F2.wireOp",EDGE,"E1"),sQuery(id+"F0.wireOp",VERTEX,"E0.end")])],"isStart":false});
|
|
shell(context, id + "F4", {"entities" : qUnion([Q0]), "thickness" : 0.5 * mm});
|
|
}
|
|
});'''
|
|
reversed_result = lower_model(parse_featurescript(reversed_source, "00260523-sweep-cap-reversed"), {})
|
|
reversed_features = {item["id"]: item for item in reversed_result.cdsl["features"]}
|
|
self.assertTrue(reversed_features["f_F3"]["params"]["cap_output_contract"]["path_reversed"])
|
|
reversed_selector = reversed_features["f_F4"]["selectors"][0]
|
|
self.assertEqual(reversed_selector["output_role"], "sweep.start")
|
|
self.assertEqual(reversed_selector["selector_intent"]["disambiguation"]["path_endpoint"], "end")
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
reversed_rebuilt = rebuild_candidate(reversed_result.cdsl, Path(directory) / "sweep-shell-reversed.step")
|
|
self.assertEqual(reversed_rebuilt["status"], "rebuilt")
|
|
|
|
def test_initial_direct_sweep_cap_edge_requires_a_single_profile_edge_and_path_endpoint_pair(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
source_path = root / "featurescript_rp/0033/00330012.txt"
|
|
if not source_path.exists(): self.skipTest("CADFS sample is not installed")
|
|
source = source_path.read_text(encoding="utf-8")
|
|
cap_edge_source = source.replace(
|
|
'makeQuery(id+"F2.opSweep","CAP_FACE",FACE,',
|
|
'makeQuery(id+"F2.opSweep","CAP_EDGE",EDGE,',
|
|
).replace(
|
|
'shell(context, id + "F3", {"entities" : qUnion([Q0]), "thickness" : 1.27 * mm});',
|
|
'fillet(context, id + "F3", {"entities" : qUnion([Q0]), "radius" : 1.27 * mm, "tangentPropagation" : true, "allowEdgeOverflow" : false});',
|
|
)
|
|
result = lower_model(parse_featurescript(cap_edge_source, "00330012-sweep-cap-edge"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
selector = features["f_F3"]["selectors"][0]
|
|
self.assertNotIn("geometry", selector)
|
|
self.assertNotIn("stable_id", selector)
|
|
self.assertEqual(selector["selector_intent"]["query_family"], "CAP_EDGE")
|
|
self.assertEqual(selector["selector_intent"]["lineage_role"], "sweep.end")
|
|
self.assertEqual(selector["selector_intent"]["source_entity"], {"sketch_id": "F0", "entity_id": "E0"})
|
|
self.assertEqual(selector["selector_intent"]["disambiguation"]["path_endpoint"], "end")
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(result.cdsl, Path(directory) / "sweep-cap-edge.step")
|
|
self.assertEqual(rebuilt["status"], "rebuilt")
|
|
resolution = next(item for item in rebuilt["result"]["selector_resolution"] if item["feature_id"] == "f_F3")
|
|
self.assertEqual(resolution["resolution_mode"], "kernel_lineage")
|
|
self.assertEqual(resolution["evidence"]["relations"][0]["output_role"], "sweep.end")
|
|
self.assertEqual(resolution["evidence"]["relations"][0]["source_kind"], "edge")
|
|
self.assertEqual(resolution["evidence"]["relations"][0]["result_kind"], "edge")
|
|
|
|
mismatched_endpoint = cap_edge_source.replace('E1.end")])],"isStart":false', 'E1.start")])],"isStart":false')
|
|
rejected = lower_model(parse_featurescript(mismatched_endpoint, "00330012-sweep-cap-edge-endpoint-mismatch"), {})
|
|
self.assertEqual(rejected.status, "converted_partial")
|
|
self.assertNotIn("f_F3", {item["id"] for item in rejected.cdsl["features"]})
|
|
|
|
def test_initial_direct_sweep_swept_face_requires_profile_and_path_edge_pair(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
source_path = root / "featurescript_rp/0033/00330012.txt"
|
|
if not source_path.exists(): self.skipTest("CADFS sample is not installed")
|
|
source = source_path.read_text(encoding="utf-8")
|
|
swept_face_source = source.replace(
|
|
'makeQuery(id+"F2.opSweep","CAP_FACE",FACE,',
|
|
'makeQuery(id+"F2.opSweep","SWEPT_FACE",FACE,',
|
|
).replace(
|
|
'sQuery(id+"F1.wireOp",VERTEX,"E1.end")',
|
|
'sQuery(id+"F1.wireOp",EDGE,"E1")',
|
|
).replace(
|
|
'])],"isStart":false})', '])]})',
|
|
).replace(
|
|
'shell(context, id + "F3", {"entities" : qUnion([Q0]), "thickness" : 1.27 * mm});',
|
|
'fillet(context, id + "F3", {"entities" : qUnion([Q0]), "radius" : 1.27 * mm, "tangentPropagation" : true, "allowEdgeOverflow" : false});',
|
|
)
|
|
result = lower_model(parse_featurescript(swept_face_source, "00330012-sweep-swept-face"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
selector = next(item for item in result.cdsl["features"] if item["id"] == "f_F3")["selectors"][0]
|
|
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"})
|
|
self.assertEqual(selector["selector_intent"]["disambiguation"], {
|
|
"type": "sweep_profile_path",
|
|
"profile_source": "F0", "profile_entities": ["E0"],
|
|
"path_source": "F1", "path_entity": "E1", "path_reversed": False,
|
|
})
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(result.cdsl, Path(directory) / "sweep-swept-face.step")
|
|
self.assertEqual(rebuilt["status"], "rebuilt")
|
|
resolution = next(item for item in rebuilt["result"]["selector_resolution"] if item["feature_id"] == "f_F3")
|
|
self.assertEqual(resolution["resolution_mode"], "kernel_lineage")
|
|
self.assertEqual(resolution["evidence"]["relations"][0]["source_kind"], "edge")
|
|
self.assertEqual(resolution["evidence"]["relations"][0]["result_kind"], "face")
|
|
self.assertEqual(resolution["evidence"]["relations"][0]["coverage"], "complete")
|
|
|
|
query_prefix, query_suffix = swept_face_source.rsplit('sQuery(id+"F1.wireOp",EDGE,"E1")', 1)
|
|
mismatched_path = query_prefix + 'sQuery(id+"F0.wireOp",EDGE,"E0")' + query_suffix
|
|
rejected = lower_model(parse_featurescript(mismatched_path, "00330012-sweep-swept-face-path-mismatch"), {})
|
|
rejected_selector = next(item for item in rejected.cdsl["features"] if item["id"] == "f_F3")["selectors"][0]
|
|
self.assertNotIn("geometry", rejected_selector)
|
|
self.assertNotIn("stable_id", rejected_selector)
|
|
self.assertEqual(rejected_selector["selector_intent"]["evidence"], "feature_script_query")
|
|
|
|
def test_initial_direct_sweep_swept_face_accepts_complete_direct_multi_edge_profile(self):
|
|
"""Generated(profile_edge) remains exact for every edge of one direct contour."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
source_path = root / "featurescript_rp/0033/00330012.txt"
|
|
if not source_path.exists(): self.skipTest("CADFS sample is not installed")
|
|
source = source_path.read_text(encoding="utf-8")
|
|
source = source.replace(
|
|
'skCircle(sketch, "E0", {"center": v(0, 0) * mm, "radius": 6.35 * mm});',
|
|
'''skLineSegment(sketch, "E0", {"start": v(-4, -4) * mm, "end": v(4, -4) * mm});
|
|
skLineSegment(sketch, "E1", {"start": v(4, -4) * mm, "end": v(4, 4) * mm});
|
|
skLineSegment(sketch, "E2", {"start": v(4, 4) * mm, "end": v(-4, 4) * mm});
|
|
skLineSegment(sketch, "E3", {"start": v(-4, 4) * mm, "end": v(-4, -4) * mm});''',
|
|
)
|
|
source = re.sub(
|
|
r'skFitSpline\(sketch, "E1", \{.*?\}\);',
|
|
'skLineSegment(sketch, "E1", {"start": v(0, 0) * mm, "end": v(100, 0) * mm});',
|
|
source,
|
|
).replace(
|
|
'Q0=makeQuery(id+"F0.imprint","IMPRINT",FACE,{"disambiguationData":[TD([[makeQuery(id+"F0.imprint","IMPRINT",EDGE,{"derivedFrom":sQuery(id+"F0.wireOp",EDGE,"E0")}),1.0]])]});',
|
|
'Q0=qSketchRegion(id+"F0",true);',
|
|
).replace(
|
|
'makeQuery(id+"F2.opSweep","CAP_FACE",FACE,',
|
|
'makeQuery(id+"F2.opSweep","SWEPT_FACE",FACE,',
|
|
).replace(
|
|
'sQuery(id+"F0.wireOp",EDGE,"E0"),sQuery(id+"F1.wireOp",VERTEX,"E1.end")',
|
|
'sQuery(id+"F0.wireOp",EDGE,"E2"),sQuery(id+"F1.wireOp",EDGE,"E1")',
|
|
).replace(
|
|
'])],"isStart":false})', '])]})',
|
|
).replace(
|
|
'shell(context, id + "F3", {"entities" : qUnion([Q0]), "thickness" : 1.27 * mm});',
|
|
'fillet(context, id + "F3", {"entities" : qUnion([Q0]), "radius" : 0.5 * mm, "tangentPropagation" : true, "allowEdgeOverflow" : false});',
|
|
)
|
|
result = lower_model(parse_featurescript(source, "00330012-sweep-swept-face-multi-edge"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
contract = features["f_F2"]["params"]["swept_face_contract"]
|
|
self.assertEqual(contract, {
|
|
"profile_source": "F0", "profile_entities": ["E0", "E1", "E2", "E3"],
|
|
"path_source": "F1", "path_entity": "E1", "path_reversed": False,
|
|
})
|
|
self.assertNotIn("cap_output_contract", features["f_F2"]["params"])
|
|
selector = features["f_F3"]["selectors"][0]
|
|
self.assertEqual(selector["selector_intent"]["source_entity"], {"sketch_id": "F0", "entity_id": "E2"})
|
|
self.assertEqual(selector["selector_intent"]["disambiguation"]["profile_entities"], ["E0", "E1", "E2", "E3"])
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(result.cdsl, Path(directory) / "sweep-swept-face-multi-edge.step")
|
|
self.assertEqual(rebuilt["status"], "rebuilt")
|
|
resolution = next(item for item in rebuilt["result"]["selector_resolution"] if item["feature_id"] == "f_F3")
|
|
self.assertEqual(resolution["resolution_mode"], "kernel_lineage")
|
|
self.assertEqual(resolution["evidence"]["relations"][0]["source_kind"], "edge")
|
|
self.assertEqual(resolution["evidence"]["relations"][0]["result_kind"], "face")
|
|
|
|
query_prefix, query_suffix = source.rsplit('sQuery(id+"F1.wireOp",EDGE,"E1")', 1)
|
|
source_pair_mismatch = query_prefix + 'sQuery(id+"F0.wireOp",EDGE,"E0")' + query_suffix
|
|
rejected = lower_model(parse_featurescript(source_pair_mismatch, "00330012-sweep-swept-face-multi-edge-mismatch"), {})
|
|
rejected_selector = next(item for item in rejected.cdsl["features"] if item["id"] == "f_F3")["selectors"][0]
|
|
self.assertEqual(rejected_selector["selector_intent"]["evidence"], "feature_script_query")
|
|
self.assertNotIn("geometry", rejected_selector)
|
|
self.assertNotIn("stable_id", rejected_selector)
|
|
|
|
def test_initial_direct_sweep_swept_face_rejects_a_multi_segment_path(self):
|
|
"""A qUnion path must not inherit an arbitrary representative source edge."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
source_path = root / "featurescript_rp/0095/00954785.txt"
|
|
if not source_path.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(source_path.read_text(encoding="utf-8"), "00954785"), {})
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
self.assertEqual(result.status, "converted_partial")
|
|
self.assertNotIn("swept_face_contract", features["f_F2"]["params"])
|
|
self.assertNotIn("f_F3", features)
|
|
self.assertTrue(any(
|
|
diagnostic["feature_id"] == "F3" and diagnostic["code"] == "feature_deferred"
|
|
for diagnostic in result.diagnostics
|
|
))
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(result.cdsl, Path(directory) / "sweep-swept-face-segmented-path.step")
|
|
# The complete four-segment source path is preserved instead of
|
|
# falling back to one representative leaf. OCC rejects this actual
|
|
# PipeShell as an invalid solid, which is a modelling/kernel boundary
|
|
# and must remain distinct from selector resolution success.
|
|
self.assertEqual(rebuilt["status"], "rebuild_failed")
|
|
self.assertEqual(rebuilt["error"]["message"], "OCC sweep operation did not produce a valid solid")
|
|
|
|
def test_initial_direct_sweep_swept_edge_requires_an_adjacent_profile_vertex_and_path(self):
|
|
"""PipeShell Generated(vertex) binds one explicit pair of profile edges."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
source_path = root / "featurescript_rp/0033/00330012.txt"
|
|
if not source_path.exists(): self.skipTest("CADFS sample is not installed")
|
|
source = source_path.read_text(encoding="utf-8")
|
|
source = source.replace(
|
|
'skCircle(sketch, "E0", {"center": v(0, 0) * mm, "radius": 6.35 * mm});',
|
|
'''skLineSegment(sketch, "E0", {"start": v(-4, -4) * mm, "end": v(4, -4) * mm});
|
|
skLineSegment(sketch, "E1", {"start": v(4, -4) * mm, "end": v(4, 4) * mm});
|
|
skLineSegment(sketch, "E2", {"start": v(4, 4) * mm, "end": v(-4, 4) * mm});
|
|
skLineSegment(sketch, "E3", {"start": v(-4, 4) * mm, "end": v(-4, -4) * mm});''',
|
|
)
|
|
source = re.sub(
|
|
r'skFitSpline\(sketch, "E1", \{.*?\}\);',
|
|
'skLineSegment(sketch, "E1", {"start": v(0, 0) * mm, "end": v(100, 0) * mm});',
|
|
source,
|
|
).replace(
|
|
'Q0=makeQuery(id+"F0.imprint","IMPRINT",FACE,{"disambiguationData":[TD([[makeQuery(id+"F0.imprint","IMPRINT",EDGE,{"derivedFrom":sQuery(id+"F0.wireOp",EDGE,"E0")}),1.0]])]});',
|
|
'Q0=qSketchRegion(id+"F0",true);',
|
|
).replace(
|
|
'Q0=makeQuery(id+"F2.opSweep","CAP_FACE",FACE,{"disambiguationData":[OSD([sQuery(id+"F0.wireOp",EDGE,"E0"),sQuery(id+"F1.wireOp",VERTEX,"E1.end")])],"isStart":false});',
|
|
'Q0=makeQuery(id+"F2.opSweep","SWEPT_EDGE",EDGE,{"disambiguationData":[OSD([sQuery(id+"F0.wireOp",EDGE,"E1"),sQuery(id+"F0.wireOp",EDGE,"E2"),sQuery(id+"F1.wireOp",EDGE,"E1")])]});',
|
|
).replace(
|
|
'shell(context, id + "F3", {"entities" : qUnion([Q0]), "thickness" : 1.27 * mm});',
|
|
'fillet(context, id + "F3", {"entities" : qUnion([Q0]), "radius" : 0.5 * mm, "tangentPropagation" : true, "allowEdgeOverflow" : false});',
|
|
)
|
|
result = lower_model(parse_featurescript(source, "00330012-sweep-swept-edge"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
self.assertEqual(features["f_F2"]["params"]["swept_edge_contract"], {
|
|
"profile_source": "F0", "profile_entities": ["E0", "E1", "E2", "E3"],
|
|
"path_source": "F1", "path_entity": "E1", "path_reversed": False,
|
|
})
|
|
selector = features["f_F3"]["selectors"][0]
|
|
self.assertNotIn("geometry", selector)
|
|
self.assertNotIn("stable_id", selector)
|
|
self.assertEqual(selector["selector_intent"]["source_entities"], [
|
|
{"sketch_id": "F0", "entity_id": "E1"},
|
|
{"sketch_id": "F0", "entity_id": "E2"},
|
|
])
|
|
self.assertEqual(selector["selector_intent"]["disambiguation"], {
|
|
"type": "sweep_profile_vertex_path",
|
|
"profile_source": "F0", "profile_entities": ["E0", "E1", "E2", "E3"],
|
|
"path_source": "F1", "path_entity": "E1", "path_reversed": False,
|
|
"profile_vertex_entities": ["E1", "E2"],
|
|
})
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(result.cdsl, Path(directory) / "sweep-swept-edge.step")
|
|
self.assertEqual(rebuilt["status"], "rebuilt")
|
|
resolution = next(item for item in rebuilt["result"]["selector_resolution"] if item["feature_id"] == "f_F3")
|
|
self.assertEqual(resolution["resolution_mode"], "kernel_lineage")
|
|
self.assertEqual(resolution["evidence"]["relations"][0]["source_kind"], "vertex")
|
|
self.assertEqual(resolution["evidence"]["relations"][0]["result_kind"], "edge")
|
|
self.assertEqual(resolution["evidence"]["relations"][0]["coverage"], "complete")
|
|
|
|
rejected_source = source.replace(
|
|
'sQuery(id+"F0.wireOp",EDGE,"E1"),sQuery(id+"F0.wireOp",EDGE,"E2")',
|
|
'sQuery(id+"F0.wireOp",EDGE,"E0"),sQuery(id+"F0.wireOp",EDGE,"E2")',
|
|
1,
|
|
)
|
|
rejected = lower_model(parse_featurescript(rejected_source, "00330012-sweep-swept-edge-non-adjacent"), {})
|
|
rejected_selector = next(item for item in rejected.cdsl["features"] if item["id"] == "f_F3")["selectors"][0]
|
|
self.assertEqual(rejected_selector["selector_intent"]["evidence"], "feature_script_query")
|
|
self.assertNotIn("geometry", rejected_selector)
|
|
self.assertNotIn("stable_id", rejected_selector)
|
|
|
|
def test_direct_prism_swept_edge_dressup_allows_proven_continuation_only(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
source_path = root / "featurescript_rp/0009/00094474.txt"
|
|
if not source_path.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(source_path.read_text(encoding="utf-8"), "00094474"), {})
|
|
selector = next(item for item in result.cdsl["features"] if item["id"] == "f_F3")["selectors"][0]
|
|
self.assertEqual(selector["selector_intent"]["query_family"], "SWEPT_EDGE")
|
|
self.assertEqual(selector["selector_intent"]["derivation_policy"], {
|
|
"allowed": ["boundary", "continuation"], "multiplicity": "one",
|
|
})
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(result.cdsl, Path(directory) / "swept-edge-continuation.step")
|
|
self.assertEqual(rebuilt["status"], "rebuilt")
|
|
resolution = next(item for item in rebuilt["result"]["selector_resolution"] if item["feature_id"] == "f_F3")
|
|
self.assertEqual(resolution["resolution_mode"], "kernel_lineage")
|
|
self.assertTrue(resolution["evidence"]["relations"])
|
|
|
|
cut_path = root / "featurescript_rp/0037/00370320.txt"
|
|
if not cut_path.exists(): self.skipTest("CADFS continuation-through-cut sample is not installed")
|
|
cut = lower_model(parse_featurescript(cut_path.read_text(encoding="utf-8"), "00370320"), {})
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
cut_rebuilt = rebuild_candidate(cut.cdsl, Path(directory) / "swept-edge-cut-continuation.step")
|
|
self.assertEqual(cut_rebuilt["status"], "rebuilt")
|
|
cut_resolution = next(
|
|
item for item in cut_rebuilt["result"]["selector_resolution"] if item["feature_id"] == "f_F4"
|
|
)
|
|
self.assertEqual(cut_resolution["resolution_mode"], "kernel_lineage")
|
|
self.assertEqual(
|
|
[relation["derivation"] for relation in cut_resolution["evidence"]["relations"]],
|
|
["boundary", "continuation"],
|
|
)
|
|
|
|
ambiguous_path = root / "featurescript_rp/0002/00027017.txt"
|
|
if not ambiguous_path.exists(): self.skipTest("CADFS continuation boundary sample is not installed")
|
|
ambiguous = lower_model(parse_featurescript(ambiguous_path.read_text(encoding="utf-8"), "00027017"), {})
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rejected = rebuild_candidate(ambiguous.cdsl, Path(directory) / "swept-edge-ambiguous.step")
|
|
self.assertEqual(rejected["status"], "rebuild_failed")
|
|
self.assertEqual(rejected["error"]["message"], "f_F3: selector_relation_non_unique during incremental replay")
|
|
self.assertEqual(rejected["last_executable_prefix"]["last_feature_id"], "f_F2")
|
|
|
|
def test_direct_swept_edge_query_set_uses_its_producer_source_anchors(self):
|
|
"""A later use of the same sketch cannot duplicate an F1 source anchor."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
source_path = root / "featurescript_rp/0040/00407468.txt"
|
|
if not source_path.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(source_path.read_text(encoding="utf-8"), "00407468"), {})
|
|
selector = next(item for item in result.cdsl["features"] if item["id"] == "f_F3")["selectors"][0]
|
|
self.assertEqual(selector["selector_intent"]["query_family"], "QUERY_SET")
|
|
self.assertEqual(len(selector["query_operands"]), 4)
|
|
self.assertTrue(all(item["owner_feature_id"] == "f_F1" for item in selector["query_operands"]))
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(result.cdsl, Path(directory) / "producer-source-anchor.step")
|
|
self.assertEqual(rebuilt["status"], "rebuilt")
|
|
resolution = next(item for item in rebuilt["result"]["selector_resolution"] if item["feature_id"] == "f_F3")
|
|
self.assertEqual(resolution["resolution_mode"], "query_set_union")
|
|
self.assertEqual(
|
|
[item["record_id"] for item in resolution["records"]],
|
|
["body:f_F2:edge:19", "body:f_F2:edge:22", "body:f_F2:edge:25", "body:f_F2:edge:28"],
|
|
)
|
|
|
|
def test_2491_two_sided_prism_cap_edges_use_exact_far_cap_history(self):
|
|
"""The reverse prism's far cap is the FeatureScript start cap."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
source_path = root / "featurescript_rp/0004/00049094.txt"
|
|
if not source_path.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(source_path.read_text(encoding="utf-8"), "00049094"), {})
|
|
feature = next(item for item in result.cdsl["features"] if item["id"] == "f_F2")
|
|
self.assertEqual(feature["selectors"][0]["selector_intent"]["query_family"], "QUERY_SET")
|
|
self.assertEqual(len(feature["selectors"][0]["query_operands"]), 4)
|
|
self.assertTrue(all(
|
|
item["selector_intent"]["query_family"] == "CAP_EDGE"
|
|
and item["selector_intent"]["derivation_policy"] == {
|
|
"allowed": ["boundary"], "multiplicity": "one",
|
|
}
|
|
for item in feature["selectors"][0]["query_operands"]
|
|
))
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(result.cdsl, Path(directory) / "two-sided-cap-edges.step")
|
|
self.assertEqual(rebuilt["status"], "rebuilt")
|
|
resolution = next(item for item in rebuilt["result"]["selector_resolution"] if item["feature_id"] == "f_F2")
|
|
self.assertEqual(resolution["resolution_mode"], "query_set_union")
|
|
self.assertEqual(len(resolution["records"]), 4)
|
|
roles = {
|
|
relation["output_role"]
|
|
for operand in resolution["evidence"]["operand_resolutions"]
|
|
for relation in operand["relations"]
|
|
}
|
|
self.assertEqual(roles, {"extrude.start", "extrude.end"})
|
|
|
|
rejected_path = root / "featurescript_rp/0040/00404726.txt"
|
|
if not rejected_path.exists(): self.skipTest("CADFS negative sample is not installed")
|
|
rejected = lower_model(parse_featurescript(rejected_path.read_text(encoding="utf-8"), "00404726"), {})
|
|
selectors = next(item for item in rejected.cdsl["features"] if item["id"] == "f_F2")["selectors"]
|
|
self.assertTrue(all(
|
|
item["selector_intent"]["evidence"] == "feature_script_query"
|
|
and item["selector_intent"]["derivation_policy"] == {
|
|
"allowed": ["continuation"], "multiplicity": "none",
|
|
}
|
|
for item in selectors
|
|
))
|
|
|
|
def test_two_sided_circle_shell_uses_exact_far_cap_roles(self):
|
|
"""A symmetric circle shell consumes both far prism caps, not their seam."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
for sample_id, producer_id, shell_id, roles in (
|
|
("00019252", "f_F1", "f_F2", ["extrude.end", "extrude.start"]),
|
|
("00000316", "f_F2", "f_F3", ["extrude.start", "extrude.end"]),
|
|
):
|
|
source_path = root / f"featurescript_rp/{sample_id[:4]}/{sample_id}.txt"
|
|
if not source_path.exists():
|
|
self.skipTest("CADFS two-sided circle shell samples are not installed")
|
|
result = lower_model(parse_featurescript(source_path.read_text(encoding="utf-8"), sample_id), {})
|
|
shell = next(item for item in result.cdsl["features"] if item["id"] == shell_id)
|
|
self.assertEqual(
|
|
[item["output_role"] for item in shell["selectors"]],
|
|
roles,
|
|
)
|
|
self.assertTrue(all(
|
|
item["owner_feature_id"] == producer_id
|
|
and item["selector_intent"]["query_family"] == "CAP_FACE"
|
|
and item["selector_intent"]["derivation_policy"] == {
|
|
"allowed": ["boundary"], "multiplicity": "one",
|
|
}
|
|
for item in shell["selectors"]
|
|
))
|
|
shell_index = next(
|
|
index for index, item in enumerate(result.cdsl["features"])
|
|
if item["id"] == shell_id
|
|
)
|
|
prefix = deepcopy(result.cdsl)
|
|
prefix["features"] = prefix["features"][:shell_index + 1]
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(prefix, Path(directory) / f"{sample_id}-two-sided-shell.step")
|
|
self.assertEqual(rebuilt["status"], "rebuilt")
|
|
resolutions = [
|
|
item for item in rebuilt["result"]["selector_resolution"]
|
|
if item["feature_id"] == shell_id
|
|
]
|
|
self.assertEqual(
|
|
[item["record"]["record_id"] for item in resolutions],
|
|
[
|
|
f"body:{producer_id}:face:{2 if role == 'extrude.end' else 3}"
|
|
for role in roles
|
|
],
|
|
)
|
|
|
|
def test_typed_new_body_add_cap_edge_uses_exact_union_continuation(self):
|
|
"""NewBodyOperationType.ADD resolves only through its exact active union successor."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
for sample_id, consumer_id in (("00953397", "f_F7"), ("00957101", "f_F4")):
|
|
with self.subTest(sample_id=sample_id):
|
|
source_path = root / f"featurescript_rp/{sample_id[:4]}/{sample_id}.txt"
|
|
if not source_path.exists(): self.skipTest("CADFS primary-add cap-edge samples are not installed")
|
|
result = lower_model(parse_featurescript(source_path.read_text(encoding="utf-8"), sample_id), {})
|
|
selector = next(item for item in result.cdsl["features"] if item["id"] == consumer_id)["selectors"][0]
|
|
self.assertEqual(selector["selector_intent"]["query_family"], "CAP_EDGE")
|
|
self.assertEqual(selector["selector_intent"]["derivation_policy"], {
|
|
"allowed": ["boundary", "continuation"], "multiplicity": "one",
|
|
})
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
outcome = rebuild_candidate(result.cdsl, Path(directory) / f"{sample_id}-primary-add-cap-edge.step")
|
|
self.assertEqual(outcome["status"], "rebuilt")
|
|
resolution = next(item for item in outcome["result"]["selector_resolution"] if item["feature_id"] == consumer_id)
|
|
self.assertEqual(resolution["status"], "resolved")
|
|
self.assertEqual(resolution["resolution_mode"], "kernel_lineage")
|
|
self.assertEqual(
|
|
[relation["operation"] for relation in resolution["evidence"]["relations"]],
|
|
["extrude", "union"],
|
|
)
|
|
|
|
rejected_path = root / "featurescript_rp/0040/00406939.txt"
|
|
if not rejected_path.exists(): self.skipTest("CADFS multi-solid primary-add sample is not installed")
|
|
rejected = lower_model(parse_featurescript(rejected_path.read_text(encoding="utf-8"), "00406939"), {})
|
|
selectors = next(item for item in rejected.cdsl["features"] if item["id"] == "f_F3")["selectors"]
|
|
self.assertTrue(all(
|
|
item["selector_intent"]["evidence"] == "feature_script_query"
|
|
and item["selector_intent"]["derivation_policy"]["multiplicity"] == "none"
|
|
for item in selectors[:2]
|
|
))
|
|
|
|
def test_typed_new_body_add_cap_face_shell_uses_exact_union_continuation(self):
|
|
"""A typed ADD CAP face needs an exact active union successor for shell use."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
for sample_id, consumer_id in (("00074047", "f_F4"), ("00350698", "f_F7")):
|
|
with self.subTest(sample_id=sample_id):
|
|
source_path = root / f"featurescript_rp/{sample_id[:4]}/{sample_id}.txt"
|
|
if not source_path.exists(): self.skipTest("CADFS primary-add cap-face samples are not installed")
|
|
result = lower_model(parse_featurescript(source_path.read_text(encoding="utf-8"), sample_id), {})
|
|
selector = next(item for item in result.cdsl["features"] if item["id"] == consumer_id)["selectors"][0]
|
|
self.assertEqual(selector["selector_intent"]["query_family"], "CAP_FACE")
|
|
self.assertEqual(selector["selector_intent"]["derivation_policy"], {
|
|
"allowed": ["boundary", "continuation"], "multiplicity": "one",
|
|
})
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
outcome = rebuild_candidate(result.cdsl, Path(directory) / f"{sample_id}-primary-add-cap-face.step")
|
|
self.assertIn(outcome["status"], {"rebuilt", "rebuild_failed"})
|
|
payload = outcome.get("result") or outcome["error"]
|
|
resolution = next(
|
|
item for item in payload["selector_resolution" if "result" in outcome else "selector_resolutions"]
|
|
if item["feature_id"] == consumer_id
|
|
)
|
|
self.assertEqual(resolution["status"], "resolved")
|
|
self.assertEqual(resolution["resolution_mode"], "operation_role")
|
|
self.assertEqual(
|
|
[relation["operation"] for relation in resolution["evidence"]["relations"]],
|
|
["extrude", "union"],
|
|
)
|
|
|
|
rejected_path = root / "featurescript_rp/0029/00293014.txt"
|
|
if not rejected_path.exists(): self.skipTest("CADFS primary-add cap-face ambiguity sample is not installed")
|
|
rejected = lower_model(parse_featurescript(rejected_path.read_text(encoding="utf-8"), "00293014"), {})
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
outcome = rebuild_candidate(rejected.cdsl, Path(directory) / "00293014-primary-add-cap-face.step")
|
|
self.assertEqual(outcome["status"], "rebuild_failed")
|
|
self.assertEqual(outcome["error"]["message"], "f_F4: selector_output_role_ambiguous during incremental replay")
|
|
self.assertEqual(outcome["last_executable_prefix"]["last_feature_id"], "f_F3")
|
|
|
|
multi_solid_path = root / "featurescript_rp/0059/00590599.txt"
|
|
if not multi_solid_path.exists(): self.skipTest("CADFS multi-solid primary-add cap-face sample is not installed")
|
|
multi_solid = lower_model(parse_featurescript(multi_solid_path.read_text(encoding="utf-8"), "00590599"), {})
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
outcome = rebuild_candidate(multi_solid.cdsl, Path(directory) / "00590599-primary-add-cap-face.step")
|
|
self.assertEqual(outcome["status"], "rebuild_failed")
|
|
self.assertEqual(outcome["error"]["message"], "f_F3: selector_query_unsupported during incremental replay")
|
|
self.assertEqual(outcome["last_executable_prefix"]["last_feature_id"], "f_F2")
|
|
|
|
def test_typed_new_body_add_cap_face_up_to_surface_uses_exact_union_continuation(self):
|
|
"""A typed ADD CAP face needs its exact active union successor for an extent."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
source_path = root / "featurescript_rp/0000/00007973.txt"
|
|
if not source_path.exists(): self.skipTest("CADFS primary-add up-to-surface sample is not installed")
|
|
result = lower_model(parse_featurescript(source_path.read_text(encoding="utf-8"), "00007973"), {})
|
|
target = next(item for item in result.cdsl["features"] if item["id"] == "f_F7")
|
|
selector = target["params"]["end_condition"]["reference"]
|
|
self.assertEqual(
|
|
selector["selector_intent"].get("consumer_contract"),
|
|
"primary_add_up_to_surface_union_continuation",
|
|
)
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
outcome = rebuild_candidate(result.cdsl, Path(directory) / "00007973-primary-add-up-to-surface.step")
|
|
self.assertEqual(outcome["status"], "rebuilt")
|
|
resolution = next(item for item in outcome["result"]["selector_resolution"] if item["feature_id"] == "f_F7")
|
|
self.assertEqual(resolution["status"], "resolved")
|
|
self.assertEqual(resolution["resolution_mode"], "operation_role")
|
|
self.assertEqual(
|
|
[relation["operation"] for relation in resolution["evidence"]["relations"]],
|
|
["extrude", "union"],
|
|
)
|
|
|
|
ambiguous_path = root / "featurescript_rp/0063/00638700.txt"
|
|
if not ambiguous_path.exists(): self.skipTest("CADFS ambiguous primary-add up-to-surface sample is not installed")
|
|
ambiguous = lower_model(parse_featurescript(ambiguous_path.read_text(encoding="utf-8"), "00638700"), {})
|
|
cut_index = next(index for index, item in enumerate(ambiguous.cdsl["features"]) if item["id"] == "f_F5")
|
|
prefix = deepcopy(ambiguous.cdsl)
|
|
prefix["features"] = prefix["features"][:cut_index + 1]
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
outcome = rebuild_candidate(prefix, Path(directory) / "00638700-primary-add-up-to-surface.step")
|
|
self.assertEqual(outcome["status"], "rebuild_failed")
|
|
self.assertEqual(outcome["error"]["message"], "f_F5: selector_output_role_ambiguous during incremental replay")
|
|
self.assertEqual(outcome["last_executable_prefix"]["last_feature_id"], "f_F3")
|
|
|
|
def test_two_sided_up_to_surface_requires_one_direct_opposite_cap_pair(self):
|
|
"""Symmetric extent targets are admitted only as the two far CAP roles."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
source_path = root / "featurescript_rp/0021/00215642.txt"
|
|
if not source_path.exists(): self.skipTest("CADFS two-sided CAP-pair sample is not installed")
|
|
result = lower_model(parse_featurescript(source_path.read_text(encoding="utf-8"), "00215642"), {})
|
|
target_index = next(index for index, item in enumerate(result.cdsl["features"]) if item["id"] == "f_F3")
|
|
target = result.cdsl["features"][target_index]
|
|
forward = target["params"]["end_condition"]["reference"]
|
|
reverse = target["params"]["reverse_end_condition"]["reference"]
|
|
self.assertEqual(forward["owner_feature_id"], "f_F1")
|
|
self.assertEqual(reverse["owner_feature_id"], "f_F1")
|
|
self.assertEqual({forward["output_role"], reverse["output_role"]}, {"extrude.start", "extrude.end"})
|
|
self.assertTrue(all(
|
|
item["selector_intent"]["consumer_contract"] == "symmetric_direct_prism_two_sided_up_to_surface_cap_pair"
|
|
and item["selector_intent"]["derivation_policy"] == {"allowed": ["boundary"], "multiplicity": "one"}
|
|
and item["selector_intent"]["disambiguation"]["source_profile_entity_ids"] == ["E0", "E1", "E2"]
|
|
for item in (forward, reverse)
|
|
))
|
|
prefix = deepcopy(result.cdsl)
|
|
prefix["features"] = prefix["features"][:target_index + 1]
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(prefix, Path(directory) / "00215642-two-sided-cap-pair.step")
|
|
self.assertEqual(rebuilt["status"], "rebuilt")
|
|
resolutions = [
|
|
item for item in rebuilt["result"]["selector_resolution"]
|
|
if item["feature_id"] == "f_F3"
|
|
]
|
|
self.assertEqual(len(resolutions), 2)
|
|
self.assertEqual({item["selected"]["output_roles"][0] for item in resolutions}, {"extrude.start", "extrude.end"})
|
|
|
|
rejected_path = root / "featurescript_rp/0093/00935255.txt"
|
|
if not rejected_path.exists(): self.skipTest("CADFS different-owner CAP-pair sample is not installed")
|
|
rejected = lower_model(parse_featurescript(rejected_path.read_text(encoding="utf-8"), "00935255"), {})
|
|
self.assertNotIn("f_F7", {item["id"] for item in rejected.cdsl["features"]})
|
|
self.assertIn(("F7", "extrude_extent_face_selector"), {
|
|
(item.get("feature_id"), item.get("capability")) for item in rejected.diagnostics
|
|
})
|
|
|
|
def test_two_sided_up_to_surface_can_follow_one_shell_with_proven_swept_faces(self):
|
|
"""A paired extent may consume only two retained direct prism walls."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
source_path = root / "featurescript_rp/0018/00180262.txt"
|
|
if not source_path.exists(): self.skipTest("CADFS shell swept-face extent sample is not installed")
|
|
result = lower_model(parse_featurescript(source_path.read_text(encoding="utf-8"), "00180262"), {})
|
|
target_index = next(index for index, item in enumerate(result.cdsl["features"]) if item["id"] == "f_F4")
|
|
target = result.cdsl["features"][target_index]
|
|
forward = target["params"]["end_condition"]["reference"]
|
|
reverse = target["params"]["reverse_end_condition"]["reference"]
|
|
self.assertTrue(all(
|
|
item.get("output_role") is None
|
|
and item["owner_feature_id"] == "f_F1"
|
|
and item["selector_intent"]["consumer_contract"] == "symmetric_direct_prism_shell_swept_face_up_to_surface_pair"
|
|
and item["selector_intent"]["derivation_policy"] == {"allowed": ["boundary", "continuation"], "multiplicity": "one"}
|
|
for item in (forward, reverse)
|
|
))
|
|
self.assertEqual(
|
|
{item["selector_intent"]["source_entity"]["entity_id"] for item in (forward, reverse)},
|
|
{"E0.left", "E0.right"},
|
|
)
|
|
prefix = deepcopy(result.cdsl)
|
|
prefix["features"] = prefix["features"][:target_index + 1]
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(prefix, Path(directory) / "00180262-shell-swept-pair.step")
|
|
self.assertEqual(rebuilt["status"], "rebuilt")
|
|
resolutions = [
|
|
item for item in rebuilt["result"]["selector_resolution"]
|
|
if item["feature_id"] == "f_F4"
|
|
]
|
|
self.assertEqual(len(resolutions), 2)
|
|
self.assertTrue(all(item["resolution_mode"] == "kernel_lineage" for item in resolutions))
|
|
self.assertEqual(
|
|
{item["selected"]["output_roles"][0] for item in resolutions},
|
|
{"shell.offset_face"},
|
|
)
|
|
|
|
# A closing descendant of a removed target side is not an offset
|
|
# face. The source mutation keeps all surrounding feature topology
|
|
# intact while making F2 remove F4's right-side target.
|
|
removed_target_source = source_path.read_text(encoding="utf-8").replace(
|
|
'sQuery(id+"F0.wireOp",EDGE,"E0.bottom")',
|
|
'sQuery(id+"F0.wireOp",EDGE,"E0.right")',
|
|
1,
|
|
)
|
|
rejected = lower_model(parse_featurescript(removed_target_source, "00180262-removed-target"), {})
|
|
self.assertNotIn("f_F4", {item["id"] for item in rejected.cdsl["features"]})
|
|
self.assertIn(("F4", "extrude_extent_face_selector"), {
|
|
(item.get("feature_id"), item.get("capability")) for item in rejected.diagnostics
|
|
})
|
|
|
|
def test_typed_new_body_add_cap_face_dressup_uses_exact_union_contract(self):
|
|
"""The typed ADD producer is selectable only through exact union lineage."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
source_path = root / "featurescript_rp/0005/00051494.txt"
|
|
if not source_path.exists(): self.skipTest("CADFS primary-add CAP_FACE dress-up sample is not installed")
|
|
result = lower_model(parse_featurescript(source_path.read_text(encoding="utf-8"), "00051494"), {})
|
|
target = next(item for item in result.cdsl["features"] if item["id"] == "f_F4")
|
|
selector = target["selectors"][0]
|
|
self.assertEqual(
|
|
selector["selector_intent"].get("consumer_contract"),
|
|
"primary_add_dressup_union_continuation",
|
|
)
|
|
self.assertEqual(
|
|
selector["selector_intent"]["derivation_policy"],
|
|
{"allowed": ["boundary", "continuation"], "multiplicity": "one"},
|
|
)
|
|
|
|
ambiguous_path = root / "featurescript_rp/0066/00660816.txt"
|
|
if not ambiguous_path.exists(): self.skipTest("CADFS ambiguous primary-add CAP_FACE dress-up sample is not installed")
|
|
ambiguous = lower_model(parse_featurescript(ambiguous_path.read_text(encoding="utf-8"), "00660816"), {})
|
|
cut_index = next(index for index, item in enumerate(ambiguous.cdsl["features"]) if item["id"] == "f_F4")
|
|
prefix = deepcopy(ambiguous.cdsl)
|
|
prefix["features"] = prefix["features"][:cut_index + 1]
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
outcome = rebuild_candidate(prefix, Path(directory) / "00660816-primary-add-dressup.step")
|
|
self.assertEqual(outcome["status"], "rebuild_failed")
|
|
self.assertEqual(
|
|
outcome["error"]["message"],
|
|
"f_F4: selector_output_role_ambiguous during incremental replay",
|
|
)
|
|
self.assertEqual(outcome["last_executable_prefix"]["last_feature_id"], "f_F3")
|
|
|
|
def test_closed_fit_splines_lower_to_periodic_contours_and_join_split_imprints(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0061/00612529.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00612529"), {})
|
|
|
|
self.assertEqual(result.status, "converted_complete")
|
|
self.assertNotIn("F8", {item.get("feature_id") for item in result.diagnostics})
|
|
sketches = {item["id"]: item for item in result.cdsl["geometry"]["sketches"]}
|
|
for sketch_id in ("sketch_F0", "sketch_F2", "sketch_F5", "sketch_F6"):
|
|
segment = sketches[sketch_id]["profile"]["contours"][0]["segments"][0]
|
|
self.assertTrue(segment["periodic"])
|
|
self.assertEqual(segment["parameterization"], "centripetal")
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
self.assertEqual(features["f_F8"]["sketch_id"], "sketch_F6")
|
|
|
|
def test_open_fit_spline_reversal_preserves_parameter_and_tangent_semantics(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0028/00287955.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00287955"), {})
|
|
|
|
sketches = {item["id"]: item for item in result.cdsl["geometry"]["sketches"]}
|
|
spline = sketches["sketch_F13__f_F14"]["profile"]["contours"][0]["segments"][1]
|
|
source_points = [[33.77, 39.06], [38.2, 35.02], [36.58, 30.72], [33.77, 28.66]]
|
|
source_parameters = [0.0]
|
|
for start, end in zip(source_points, source_points[1:]):
|
|
source_parameters.append(source_parameters[-1] + math.sqrt(math.dist(start, end)))
|
|
source_parameters = [value / source_parameters[-1] for value in source_parameters]
|
|
|
|
self.assertEqual(spline["points"], list(reversed(source_points)))
|
|
self.assertEqual(spline["start_tangent"], [11.58, -1.2])
|
|
self.assertEqual(spline["end_tangent"], [-14.95, 1.46])
|
|
self.assertEqual(
|
|
spline["parameters"],
|
|
[source_parameters[-1] - value for value in reversed(source_parameters)],
|
|
)
|
|
|
|
def test_draft_and_boolean_bodies_preserve_explicit_source_body_selection(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0083/00835610.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00835610"), {})
|
|
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
self.assertEqual(features["f_F1"]["params"]["draft"], {"angle_deg": 49.0, "pull_direction": True})
|
|
self.assertEqual(features["f_F3"]["sketch_id"], "sketch_F0__f_F3")
|
|
self.assertEqual(features["f_F3"]["params"]["draft"], {"angle_deg": 49.0, "pull_direction": True})
|
|
self.assertEqual(features["f_F4"]["atomic_id"], "boolean_bodies")
|
|
self.assertEqual(features["f_F4"]["params"], {
|
|
"operation": "subtract",
|
|
"target_feature_ids": ["f_F3"],
|
|
"tool_feature_ids": ["f_F2"],
|
|
"keep_tools": False,
|
|
})
|
|
self.assertEqual(features["f_F8"]["atomic_id"], "revolve_add")
|
|
self.assertNotIn("result_mode", features["f_F8"]["params"])
|
|
|
|
def test_drafted_cap_face_profile_executes_from_a_builder_proven_boolean_successor(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0083/00835610.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00835610"), {})
|
|
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
self.assertEqual(result.status, "converted_complete")
|
|
cap_extrude = features["f_F5"]
|
|
self.assertEqual(cap_extrude["atomic_id"], "extrude_from_face")
|
|
self.assertNotIn("sketch_id", cap_extrude)
|
|
selector = cap_extrude["selectors"][0]
|
|
self.assertEqual({key: selector[key] for key in (
|
|
"kind", "owner_feature_id", "output_role", "source", "confidence",
|
|
)}, {
|
|
"kind": "face", "owner_feature_id": "f_F3", "output_role": "extrude.start",
|
|
"source": "runtime_snapshot", "confidence": 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
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_cdsl(result.cdsl, Path(directory) / "drafted-cap-face.step")
|
|
resolution = next(item for item in rebuilt["selector_resolution"] if item["feature_id"] == "f_F5")
|
|
self.assertEqual(resolution["status"], "resolved")
|
|
self.assertEqual(resolution["selected"]["owner_feature_ids"], ["f_F3"])
|
|
self.assertEqual(resolution["selected"]["output_roles"], ["extrude.start"])
|
|
self.assertIn("f_F5", [item["feature_id"] for item in rebuilt["feature_results"]])
|
|
|
|
def test_drafted_cap_face_with_an_inner_loop_remains_an_explicit_capability_gap(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0026/00268467.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00268467"), {})
|
|
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
self.assertNotIn("f_F2", features)
|
|
diagnostics = [item for item in result.diagnostics if item.get("feature_id") == "F2"]
|
|
self.assertEqual(len(diagnostics), 1)
|
|
self.assertEqual(diagnostics[0]["capability"], "extrude_profile_topology:cap_face")
|
|
|
|
def test_nested_cap_edge_union_preserves_outer_profile_and_runtime_prefix(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0095/00950564.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00950564"), {})
|
|
|
|
self.assertEqual(result.status, "converted_complete")
|
|
self.assertFalse(result.diagnostics)
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
self.assertEqual(features["f_F3"]["sketch_id"], "sketch_F2")
|
|
self.assertEqual(features["f_F5"]["sketch_id"], "sketch_F4")
|
|
# Typed ADD operations fuse into the one active member. F12 must use
|
|
# that active F11 successor while retaining F1 as a source alias.
|
|
self.assertEqual(features["f_F12"]["params"]["source_feature_ids"], ["f_F11"])
|
|
self.assertEqual(features["f_F12"]["params"]["source_member_aliases"], [{
|
|
"source_feature_id": "f_F1",
|
|
"active_member_feature_id": "f_F11",
|
|
}])
|
|
self.assertIn("f_F11", features["f_F12"]["depends_on"])
|
|
from engine.cdsl_engine.runtime import analyze_cdsl
|
|
f12_analysis = next(item for item in analyze_cdsl(result.cdsl).as_dict()["feature_results"] if item["feature_id"] == "f_F12")
|
|
self.assertEqual(f12_analysis["resolved_status"], "executable")
|
|
self.assertFalse(f12_analysis["blockers"])
|
|
|
|
# Later chamfers have a separate selector gap. The retained prefix
|
|
# proves the two CAP_EDGE profile unions themselves execute through
|
|
# their dependent extrusions.
|
|
prefix = deepcopy(result.cdsl)
|
|
prefix["features"] = [item for item in prefix["features"] if item["id"] not in {"f_F10", "f_F11", "f_F12"}]
|
|
from engine.cdsl_engine.runtime import rebuild_cdsl
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_cdsl(prefix, Path(directory) / "cap-edge-union-prefix.step")
|
|
self.assertFalse(rebuilt["runtime_diagnostics"])
|
|
self.assertEqual(
|
|
[item["feature_id"] for item in rebuilt["feature_results"]],
|
|
["f_F1", "f_F3", "f_F5", "f_F7", "f_F9"],
|
|
)
|
|
|
|
def test_two_sided_blind_extrude_records_physical_cap_workplanes(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0024/00247322.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00247322"), {})
|
|
|
|
# The CAP_FACE(F4, isStart:true) workplane is the blind cap opposite
|
|
# F4's primary direction, not the source sketch plane. F5 preserves
|
|
# that cap's exact F4 ownership, so its CAP_EDGE can form F7's hole.
|
|
self.assertEqual(result.status, "converted_complete")
|
|
self.assertFalse(result.diagnostics)
|
|
sketches = {item["id"]: item for item in result.cdsl["geometry"]["sketches"]}
|
|
self.assertEqual(sketches["sketch_F3"]["workplane"]["origin_mm"], [0.0, 0.0, 40.0])
|
|
self.assertEqual(sketches["sketch_F6"]["workplane"]["origin_mm"], [0.0, 0.0, 40.3])
|
|
self.assertEqual(sketches["sketch_F6"]["workplane"]["normal"], [0.0, 0.0, 1.0])
|
|
self.assertEqual(sketches["sketch_F8"]["workplane"]["origin_mm"], [0.0, 0.0, 41.8])
|
|
self.assertEqual(sketches["sketch_F10"]["workplane"]["origin_mm"], [0.0, 0.0, 41.8])
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
self.assertEqual(features["f_F7"]["selectors"][0]["geometry"], {
|
|
"normal": [0.0, 0.0, 1.0], "plane_offset_mm": 40.3,
|
|
})
|
|
|
|
# F9 rotates F4 only after F5/F7 have consumed it. It cannot be baked
|
|
# 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"}
|
|
]
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
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")
|
|
self.assertIn("body_source_unavailable", [item["code"] for item in f9["blockers"]])
|
|
|
|
def test_cap_face_profile_executes_from_a_builder_proven_dressup_successor(self):
|
|
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")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00016195"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
feature = next(item for item in result.cdsl["features"] if item["id"] == "f_F3")
|
|
self.assertEqual(feature["atomic_id"], "extrude_from_face")
|
|
self.assertNotIn("sketch_id", feature)
|
|
|
|
from engine.cdsl_engine.runtime import rebuild_cdsl
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_cdsl(result.cdsl, Path(directory) / "cap-face.step")
|
|
resolution = next(item for item in rebuilt["selector_resolution"] if item["feature_id"] == "f_F3")
|
|
self.assertEqual(resolution["status"], "resolved")
|
|
self.assertEqual(resolution["selected"]["owner_feature_ids"], ["f_F1"])
|
|
self.assertEqual(resolution["selected"]["output_roles"], ["extrude.end"])
|
|
|
|
def test_transformed_cap_face_uses_its_attachment_plane_origin(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0083/00835610.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00835610"), {})
|
|
|
|
sketches = {item["id"]: item for item in result.cdsl["geometry"]["sketches"]}
|
|
plane = sketches["sketch_F14"]["workplane"]
|
|
|
|
self.assertAlmostEqual(plane["origin_mm"][0], 0.0)
|
|
self.assertAlmostEqual(plane["origin_mm"][1], 0.0)
|
|
self.assertAlmostEqual(plane["origin_mm"][2], -24.08)
|
|
self.assertAlmostEqual(plane["normal"][0], 0.0)
|
|
self.assertAlmostEqual(plane["normal"][1], 0.0)
|
|
self.assertAlmostEqual(plane["normal"][2], 1.0)
|
|
|
|
def test_chamfer_tangent_propagation_lowers_to_engine_param(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0000/00002243.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00002243"), {})
|
|
chamfer = next(item for item in result.cdsl["features"] if item["id"] == "f_F4")
|
|
self.assertTrue(chamfer["params"]["tangent_propagation"])
|
|
|
|
def test_imprint_profile_query_selects_one_shared_edge_region(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0003/00035682.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00035682"), {})
|
|
revolve = next(item for item in result.cdsl["features"] if item["id"] == "f_F1")
|
|
self.assertEqual(revolve["sketch_id"], "sketch_F0__f_F1")
|
|
selected = next(item for item in result.cdsl["geometry"]["sketches"] if item["id"] == revolve["sketch_id"])
|
|
self.assertEqual(len(selected["profile"]["contours"]), 1)
|
|
x_coordinates = {point[0] for segment in selected["profile"]["contours"][0]["segments"] for point in (segment["start"], segment["end"])}
|
|
self.assertEqual(x_coordinates, {11.68, 12.05})
|
|
|
|
def test_line_point_plane_uses_the_reference_line_as_its_normal(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0003/00035682.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00035682"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
self.assertEqual(features["f_F5"]["atomic_id"], "reference_plane")
|
|
self.assertEqual(features["f_F8"]["atomic_id"], "reference_plane")
|
|
self.assertEqual(features["f_F7"]["atomic_id"], "extrude_cut_blind")
|
|
self.assertEqual(features["f_F10"]["atomic_id"], "extrude_cut_blind")
|
|
sketches = {item["id"]: item for item in result.cdsl["geometry"]["sketches"]}
|
|
length = math.hypot(10.44, 6.03)
|
|
for actual, expected in zip(sketches["sketch_F6"]["workplane"]["normal"], [-10.44 / length, 6.03 / length, 0.0]):
|
|
self.assertAlmostEqual(actual, expected)
|
|
for actual, expected in zip(sketches["sketch_F6"]["workplane"]["x_dir"], [-6.03 / length, -10.44 / length, 0.0]):
|
|
self.assertAlmostEqual(actual, expected)
|
|
for actual, expected in zip(sketches["sketch_F9"]["workplane"]["normal"], [10.44 / length, 6.03 / length, 0.0]):
|
|
self.assertAlmostEqual(actual, expected)
|
|
for actual, expected in zip(sketches["sketch_F9"]["workplane"]["x_dir"], [-6.03 / length, 10.44 / length, 0.0]):
|
|
self.assertAlmostEqual(actual, expected)
|
|
|
|
def test_revolved_swept_face_lowers_to_an_annular_profile(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0005/00054089.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00054089"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
self.assertEqual(features["f_F5"]["atomic_id"], "extrude_add_blind")
|
|
sketches = {item["id"]: item for item in result.cdsl["geometry"]["sketches"]}
|
|
profile = sketches["sketch_F4"]["profile"]
|
|
self.assertEqual(profile["type"], "analytic_contours")
|
|
radii = {contour["role"]: contour["segments"][0]["radius_mm"] for contour in profile["contours"]}
|
|
self.assertEqual(radii, {"outer": 18.0, "inner": 14.0})
|
|
|
|
def test_full_revolve_planar_swept_face_lowers_as_a_mirror_plane(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0000/00000385.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
source = feature.read_text(encoding="utf-8")
|
|
result = lower_model(parse_featurescript(source, "00000385"), {})
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
|
|
self.assertEqual(features["f_F5"]["atomic_id"], "pattern_mirror")
|
|
mirror_plane = features["f_F5_plane"]["params"]["plane"]
|
|
self.assertEqual(mirror_plane["origin_mm"], [0.0, 0.0, 0.0])
|
|
self.assertEqual(mirror_plane["x_dir"], [1.0, 0.0, 0.0])
|
|
self.assertEqual(mirror_plane["normal"], [0.0, 0.0, 1.0])
|
|
self.assertFalse([item for item in result.diagnostics if item.get("feature_id") == "F5"])
|
|
boolean = features["f_F6"]
|
|
self.assertEqual(boolean["atomic_id"], "boolean_bodies")
|
|
self.assertEqual(boolean["params"], {
|
|
"operation": "union",
|
|
"target_feature_ids": ["f_F1"],
|
|
"keep_tools": False,
|
|
"targetless_body_set": True,
|
|
"tool_pattern_instance_refs": [{
|
|
"pattern_feature_id": "f_F5",
|
|
"source_feature_id": "f_F1",
|
|
"instance_index": 1,
|
|
}],
|
|
})
|
|
self.assertFalse([item for item in result.diagnostics if item.get("feature_id") == "F6"])
|
|
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(result.cdsl, Path(directory) / "rebuild.step")
|
|
self.assertEqual(rebuilt["status"], "rebuilt")
|
|
mirror = next(item for item in rebuilt["result"]["feature_results"] if item["feature_id"] == "f_F5")
|
|
self.assertEqual(mirror["status"], "executed")
|
|
boolean_result = next(item for item in rebuilt["result"]["feature_results"] if item["feature_id"] == "f_F6")
|
|
self.assertEqual(boolean_result["status"], "executed")
|
|
|
|
partial = source.replace("RevolveType.FULL", "RevolveType.PARTIAL", 1)
|
|
rejected = lower_model(parse_featurescript(partial, "partial-revolve-mirror-plane"), {})
|
|
self.assertNotIn("f_F5", {item["id"] for item in rejected.cdsl["features"]})
|
|
self.assertEqual(
|
|
[item for item in rejected.diagnostics if item.get("feature_id") == "F5"],
|
|
[{
|
|
"code": "feature_deferred",
|
|
"feature_id": "F5",
|
|
"operation": "mirror",
|
|
"message": "mirror plane is not a default or reference plane",
|
|
}],
|
|
)
|
|
|
|
def test_immediate_direct_prism_cap_and_swept_faces_lower_as_mirror_datums(self):
|
|
"""A mirror may use only its current direct-prism source face as a datum."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test/featurescript_rp"
|
|
cases = {
|
|
"00094392": ("0009/00094392.txt", "CAP_FACE", [0.0, -204.44, 0.0]),
|
|
"00051481": ("0005/00051481.txt", "SWEPT_FACE", [0.0, 0.0, 0.0]),
|
|
}
|
|
for sample_id, (relative_path, family, expected_origin) in cases.items():
|
|
with self.subTest(sample_id=sample_id):
|
|
source = root / relative_path
|
|
if not source.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(source.read_text(encoding="utf-8"), sample_id), {})
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
mirror = features["f_F2"]
|
|
plane = features["f_F2_plane"]
|
|
self.assertEqual(mirror["atomic_id"], "pattern_mirror")
|
|
self.assertEqual(mirror["depends_on"], ["f_F1", "f_F2_plane"])
|
|
self.assertEqual(plane["params"]["plane"]["origin_mm"], expected_origin)
|
|
self.assertEqual(
|
|
mirror["params"]["mirror_plane"]["selector_intent"]["query_family"],
|
|
"GEOMETRIC",
|
|
)
|
|
self.assertFalse(any(item.get("feature_id") == "F2" for item in result.diagnostics))
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(result.cdsl, Path(directory) / "mirror.step")
|
|
feature_result = next(item for item in rebuilt["result"]["feature_results"] if item["feature_id"] == "f_F2")
|
|
self.assertEqual(feature_result["status"], "executed")
|
|
|
|
# A drafted, non-1511 prism's side plane has no admission under this
|
|
# source-datum contract, even though its query spelling is identical.
|
|
rejected_source = root / "0035/00356398.txt"
|
|
if rejected_source.exists():
|
|
rejected = lower_model(parse_featurescript(rejected_source.read_text(encoding="utf-8"), "00356398"), {})
|
|
self.assertIn(("F2", "mirror plane is not a default or reference plane"), {
|
|
(item.get("feature_id"), item.get("message")) for item in rejected.diagnostics
|
|
})
|
|
|
|
def test_targetless_union_partitions_direct_body_queries_in_source_order(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0029/00293508.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(encoding="utf-8"), "00293508"), {})
|
|
self.assertEqual(result.status, "converted_partial")
|
|
boolean = next(item for item in result.cdsl["features"] if item["id"] == "f_F4")
|
|
self.assertEqual(boolean["atomic_id"], "boolean_bodies")
|
|
self.assertEqual(boolean["params"], {
|
|
"operation": "union",
|
|
"target_feature_ids": ["f_F1"],
|
|
"tool_feature_ids": ["f_F3"],
|
|
"keep_tools": False,
|
|
"targetless_body_set": True,
|
|
})
|
|
self.assertEqual(
|
|
[(item["feature_id"], item["message"]) for item in result.diagnostics],
|
|
[
|
|
("F7", "MERGE(FACE) workplane requires a dedicated complete/proven runtime face relation"),
|
|
("F8", "extrude sketch query is unresolved"),
|
|
("F9", "MERGE(FACE) workplane requires a dedicated complete/proven runtime face relation"),
|
|
("F10", "extrude sketch query is unresolved"),
|
|
],
|
|
)
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(result.cdsl, Path(directory) / "rebuild.step")
|
|
self.assertEqual(rebuilt["status"], "rebuilt")
|
|
execution = next(item for item in rebuilt["result"]["feature_results"] if item["feature_id"] == "f_F4")
|
|
self.assertEqual(execution["status"], "executed")
|
|
|
|
def test_targetless_direct_body_intersection_preserves_source_order(self):
|
|
"""A targetless intersection has an explicit left operand, not current body."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0007/00073309.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(encoding="utf-8"), "00073309"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
boolean = next(item for item in result.cdsl["features"] if item["id"] == "f_F7")
|
|
self.assertEqual(boolean["params"], {
|
|
"operation": "intersect",
|
|
"target_feature_ids": ["f_F5"],
|
|
"tool_feature_ids": ["f_F1"],
|
|
"keep_tools": False,
|
|
"targetless_body_set": True,
|
|
})
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(result.cdsl, Path(directory) / "targetless-intersection.step")
|
|
self.assertEqual(rebuilt["status"], "rebuilt")
|
|
self.assertEqual(
|
|
next(item for item in rebuilt["result"]["feature_results"] if item["feature_id"] == "f_F7")["status"],
|
|
"executed",
|
|
)
|
|
|
|
def test_targetless_body_set_keeps_the_chosen_left_member_when_requested(self):
|
|
"""keepTools applies to the complete original targetless input set."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0021/00215642.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(encoding="utf-8"), "00215642"), {})
|
|
boolean = next(item for item in result.cdsl["features"] if item["id"] == "f_F6")
|
|
self.assertEqual(boolean["params"], {
|
|
"operation": "union",
|
|
"target_feature_ids": ["f_F3"],
|
|
"tool_feature_ids": ["f_F1"],
|
|
"keep_tools": True,
|
|
"targetless_body_set": True,
|
|
})
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_candidate(result.cdsl, Path(directory) / "targetless-keep-tools.step")
|
|
self.assertEqual(rebuilt["status"], "runtime_ineligible")
|
|
prefix = rebuilt["last_executable_prefix"]
|
|
self.assertEqual(prefix["last_feature_id"], "f_F12")
|
|
self.assertIn("f_F6", [item["feature_id"] for item in prefix["result"]["feature_results"]])
|
|
|
|
from engine.cdsl_engine.semantic_validation import validate_semantic_cdsl
|
|
invalid = deepcopy(result.cdsl)
|
|
invalid_feature = next(item for item in invalid["features"] if item["id"] == "f_F6")
|
|
invalid_feature["params"]["operation"] = "subtract"
|
|
with self.assertRaisesRegex(ValueError, "targetless_body_set requires one qualified body"):
|
|
validate_semantic_cdsl(invalid)
|
|
|
|
def test_intersect_partition_profile_retains_exact_planar_imprint_evidence(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0083/00835610.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00835610"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
self.assertFalse([item for item in result.diagnostics if item.get("feature_id") == "F5"])
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
self.assertEqual(features["f_F5"]["atomic_id"], "extrude_from_face")
|
|
sketches = {item["id"]: item for item in result.cdsl["geometry"]["sketches"]}
|
|
first = sketches[features["f_F15"]["sketch_id"]]["profile"]
|
|
second = sketches[features["f_F16"]["sketch_id"]]["profile"]
|
|
self.assertEqual(first["type"], "planar_imprint")
|
|
self.assertEqual(second["type"], "planar_imprint")
|
|
self.assertEqual(
|
|
first["selections"],
|
|
[
|
|
{"source_entity_id": "E11", "face_side": 1.0,
|
|
"fragment": {"anchor_entity_id": "E15", "side": -1.0, "intersection_index": 0}},
|
|
{"source_entity_id": "E11", "face_side": -1.0,
|
|
"fragment": {"anchor_entity_id": "E15", "side": -1.0, "intersection_index": 0}},
|
|
],
|
|
)
|
|
self.assertEqual({item["id"] for item in first["source_entities"]}, {"E11", "E13", "E14", "E15"})
|
|
self.assertEqual(
|
|
[selection["fragment"]["anchor_entity_id"] for selection in second["selections"]],
|
|
["E14", "E14"],
|
|
)
|
|
|
|
def test_direct_imprint_profile_reaches_the_dressup_after_exact_selector_resolution(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), 1)
|
|
selector = selectors[0]
|
|
self.assertEqual(selector["selector_intent"]["query_family"], "QUERY_SET")
|
|
self.assertEqual(selector["selector_intent"]["query_set_contract"], "proven_operand_union")
|
|
self.assertEqual(selector["selector_intent"]["query_expr"]["root"]["operator"], "union")
|
|
self.assertEqual(len(selector["query_operands"]), 7)
|
|
self.assertTrue(all(
|
|
item["selector_intent"]["query_family"] == "SWEPT_FACE"
|
|
and item["selector_intent"]["derivation_policy"] == {
|
|
"allowed": ["boundary", "fragment"], "multiplicity": "all_fragments",
|
|
}
|
|
and "geometry" not in item
|
|
for item in selector["query_operands"]
|
|
))
|
|
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = Path(directory) / "rebuild.step"
|
|
outcome = rebuild_candidate(result.cdsl, rebuilt)
|
|
self.assertEqual(outcome["status"], "rebuild_failed")
|
|
self.assertIn("Failed creating a fillet with radius of 5.08", outcome["error"]["message"])
|
|
resolutions = outcome["error"].get("selector_resolutions") or []
|
|
self.assertEqual(len(resolutions), 1)
|
|
self.assertEqual(resolutions[0]["feature_id"], "f_F2")
|
|
self.assertEqual(resolutions[0]["status"], "resolved")
|
|
self.assertEqual(resolutions[0]["resolution_mode"], "query_set_union")
|
|
self.assertEqual(len(resolutions[0]["evidence"]["operand_resolutions"]), 7)
|
|
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"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00111611"), {})
|
|
self.assertNotIn("F10", {item.get("feature_id") for item in result.diagnostics})
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
sketches = {item["id"]: item for item in result.cdsl["geometry"]["sketches"]}
|
|
self.assertEqual(features["f_F9"]["params"]["result_mode"], "new_body")
|
|
self.assertEqual(sketches[features["f_F9"]["sketch_id"]]["workplane"]["origin_mm"], [77.16, -11.67, -63.0])
|
|
for actual, expected in zip(features["f_F9"]["params"]["axis"]["origin_mm"], [0.13, -11.67, 56.11]):
|
|
self.assertAlmostEqual(actual, expected)
|
|
|
|
def test_cap_face_workplane_uses_the_physical_cap_normal(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")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00111611"), {})
|
|
sketches = {item["id"]: item for item in result.cdsl["geometry"]["sketches"]}
|
|
|
|
self.assertEqual(sketches["sketch_F2"]["workplane"]["origin_mm"], [0.0, 25.4, 0.0])
|
|
self.assertEqual(sketches["sketch_F2"]["workplane"]["normal"], [0.0, 1.0, 0.0])
|
|
self.assertEqual(sketches["sketch_F6"]["workplane"]["origin_mm"], [0.0, 0.0, 0.0])
|
|
self.assertEqual(sketches["sketch_F6"]["workplane"]["normal"], [0.0, -1.0, 0.0])
|
|
|
|
def test_nested_circle_imprint_selects_its_bounded_annulus(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")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00111611"), {})
|
|
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_F7"]["sketch_id"]]["profile"]
|
|
|
|
self.assertEqual(profile["type"], "analytic_contours")
|
|
self.assertEqual(
|
|
[(item["role"], item["segments"][0]["radius_mm"]) for item in profile["contours"]],
|
|
[("outer", 4.48), ("inner", 2.36)],
|
|
)
|
|
|
|
def test_ellipse_profiles_unblock_the_00287955_feature_chain(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0028/00287955.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00287955"), {})
|
|
features = {item["id"] for item in result.cdsl["features"]}
|
|
feature_by_id = {item["id"]: item for item in result.cdsl["features"]}
|
|
sketches = {item["id"]: item for item in result.cdsl["geometry"]["sketches"]}
|
|
|
|
self.assertTrue({"f_F3", "f_F5", "f_F6", "f_F11"}.issubset(features))
|
|
self.assertEqual(feature_by_id["f_F9"]["atomic_id"], "extrude_add_blind")
|
|
self.assertNotIn("selectors", feature_by_id["f_F9"])
|
|
mid_plane = feature_by_id["f_F12"]["params"]["plane"]
|
|
self.assertAlmostEqual(mid_plane["origin_mm"][0], -18.67, places=5)
|
|
self.assertAlmostEqual(mid_plane["normal"][0], 1.0, places=5)
|
|
self.assertAlmostEqual(mid_plane["normal"][1], 0.0, places=3)
|
|
axis = feature_by_id["f_F14"]["params"]["axis"]
|
|
self.assertAlmostEqual(axis["origin_mm"][0], -18.67, delta=0.01)
|
|
self.assertAlmostEqual(axis["origin_mm"][1], 33.77, delta=0.01)
|
|
for sketch_id in ("sketch_F2", "sketch_F4", "sketch_F7", "sketch_F8"):
|
|
segment = sketches[sketch_id]["profile"]["contours"][0]["segments"][0]
|
|
self.assertEqual(segment["type"], "ellipse")
|
|
self.assertNotIn("sketch_deferred", {item["code"] for item in result.diagnostics})
|
|
|
|
def test_circular_pattern_lowers_to_existing_engine_contract(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")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00423838"), {})
|
|
self.assertIsNotNone(result.cdsl)
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
pattern = features["f_F4"]
|
|
self.assertEqual(pattern["atomic_id"], "pattern_circular")
|
|
self.assertEqual(pattern["params"]["source_feature_ids"], ["f_F1"])
|
|
self.assertEqual(pattern["params"]["pattern_count"], 6)
|
|
self.assertEqual(pattern["params"]["sweep_angle_deg"], 360.0)
|
|
self.assertEqual(pattern["params"]["axis"]["direction"], [0.0, 0.0, -1.0])
|
|
self.assertNotIn("F2", {item.get("feature_id") for item in result.diagnostics})
|
|
sketches = {item["id"]: item for item in result.cdsl["geometry"]["sketches"]}
|
|
self.assertNotEqual(sketches[features["f_F1"]["sketch_id"]]["workplane"]["normal"], [0.0, 0.0, 1.0])
|
|
self.assertNotIn("circularPattern", {item.get("operation") for item in result.diagnostics})
|
|
|
|
def test_static_integral_round_pattern_count_is_lowered_without_guessing_rounding_mode(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
for sample_id, feature_id, count in (
|
|
("00003011", "f_F5", 8),
|
|
("00039410", "f_F6", 9),
|
|
):
|
|
with self.subTest(sample_id=sample_id):
|
|
feature = root / "featurescript_rp" / sample_id[:4] / f"{sample_id}.txt"
|
|
result = lower_model(parse_featurescript(feature.read_text(), sample_id), {})
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
self.assertEqual(features[feature_id]["atomic_id"], "pattern_circular")
|
|
self.assertEqual(features[feature_id]["params"]["pattern_count"], count)
|
|
self.assertNotIn(feature_id.removeprefix("f_"), {
|
|
item.get("feature_id") for item in result.diagnostics
|
|
})
|
|
|
|
integral_round = Call("round", [8.0])
|
|
fractional_round = Call("round", [8.5])
|
|
self.assertEqual(_number(integral_round), 8.0)
|
|
with self.assertRaisesRegex(ValueError, "not a constant number"):
|
|
_number(fractional_round)
|
|
|
|
def test_two_point_fit_spline_requires_endpoint_derivatives_and_lowers_as_bspline(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
source_path = root / "featurescript_rp/0003/00039410.txt"
|
|
source = source_path.read_text(encoding="utf-8")
|
|
result = lower_model(parse_featurescript(source, "00039410"), {})
|
|
sketches = {item["id"]: item for item in result.cdsl["geometry"]["sketches"]}
|
|
profile = sketches["sketch_F4"]["profile"]
|
|
splines = [
|
|
segment
|
|
for contour in profile["contours"]
|
|
for segment in contour["segments"]
|
|
if segment["type"] == "bspline"
|
|
]
|
|
self.assertEqual(len(splines), 2)
|
|
self.assertTrue(all(len(segment["points"]) == 2 for segment in splines))
|
|
self.assertTrue(all("start_tangent" in segment and "end_tangent" in segment for segment in splines))
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
self.assertEqual(features["f_F6"]["params"]["pattern_count"], 9)
|
|
self.assertNotIn("F4", {item.get("feature_id") for item in result.diagnostics})
|
|
self.assertNotIn("F6", {item.get("feature_id") for item in result.diagnostics})
|
|
|
|
missing_tangent = source.replace(', "endDerivative": vector(4.02, 9.38) * mm', "", 1)
|
|
rejected = lower_model(parse_featurescript(missing_tangent, "two-point-spline-without-tangent"), {})
|
|
self.assertIn({
|
|
"code": "sketch_deferred",
|
|
"feature_id": "F4",
|
|
"message": "two-point fit spline requires both endpoint derivatives",
|
|
}, rejected.diagnostics)
|
|
|
|
coincident = source.replace('v(0, 30) * mm', 'v(-1, 19.97) * mm', 1)
|
|
rejected = lower_model(parse_featurescript(coincident, "two-point-spline-with-coincident-endpoints"), {})
|
|
self.assertIn({
|
|
"code": "sketch_deferred",
|
|
"feature_id": "F4",
|
|
"message": "two-point fit spline endpoints must be distinct",
|
|
}, rejected.diagnostics)
|
|
|
|
def test_opposed_circle_regions_lower_to_their_union_disk(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")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00423838"), {})
|
|
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_F6"]["sketch_id"]]["profile"]
|
|
self.assertEqual(profile["type"], "circle")
|
|
self.assertEqual(profile["radius_mm"], 30.0)
|
|
|
|
def test_reference_plane_variants_lower_to_explicit_frames(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
cases = {
|
|
"00159804": {"F3", "F5"},
|
|
"00192744": {"F3"},
|
|
"00212904": {"F3"},
|
|
"00542223": {"F3"},
|
|
}
|
|
for sample_id, expected in cases.items():
|
|
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(), sample_id), {})
|
|
features = {item["name"]: item for item in result.cdsl["features"]}
|
|
for feature_id in expected:
|
|
plane = features[feature_id]
|
|
self.assertEqual(plane["atomic_id"], "reference_plane")
|
|
self.assertEqual(set(plane["params"]["plane"]), {"origin_mm", "x_dir", "normal"})
|
|
diagnostics = [item for item in result.diagnostics if item.get("operation") == "cPlane"]
|
|
self.assertFalse(diagnostics)
|
|
|
|
def test_cap_vertex_datum_keeps_source_positions_across_a_direct_cap_shell(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0021/00212904.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
source = feature.read_text(encoding="utf-8").replace(
|
|
'"isStart":true});\n shell(context, id + "F2"',
|
|
'"isStart":false});\n shell(context, id + "F2"',
|
|
1,
|
|
)
|
|
result = lower_model(parse_featurescript(source, "00212904-shell-removes-datum-cap"), {})
|
|
self.assertIn("f_F3", {item["id"] for item in result.cdsl["features"]})
|
|
self.assertFalse(any(item.get("feature_id") == "F3" for item in result.diagnostics))
|
|
|
|
def test_plane_point_distinguishes_direct_cap_face_from_source_vertex(self):
|
|
"""PLANE_POINT roles come from query kinds, not qCreatedBy spelling."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0022/00228556.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00228556"), {})
|
|
plane = next(item for item in result.cdsl["features"] if item["id"] == "f_F3")
|
|
self.assertEqual(plane["atomic_id"], "reference_plane")
|
|
self.assertEqual(set(plane["params"]["plane"]), {"origin_mm", "x_dir", "normal"})
|
|
self.assertFalse(any(
|
|
item.get("operation") == "cPlane" and item.get("feature_id") == "F3"
|
|
for item in result.diagnostics
|
|
))
|
|
|
|
ambiguous = feature.read_text().replace(
|
|
'Q1=makeQuery(id+"F1.opExtrude","CAP_FACE",FACE,',
|
|
'Q1=sQuery(id+"F2.wireOp",VERTEX,',
|
|
1,
|
|
)
|
|
rejected = lower_model(parse_featurescript(ambiguous, "00228556-ambiguous-plane-point"), {})
|
|
self.assertIn({
|
|
"code": "feature_deferred",
|
|
"feature_id": "F3",
|
|
"operation": "cPlane",
|
|
"message": "plane-point requires exactly one face and one vertex",
|
|
}, rejected.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"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
source = feature.read_text()
|
|
|
|
opposite = lower_model(parse_featurescript(source, "00777619"), {})
|
|
opposite_plane = next(item for item in opposite.cdsl["features"] if item["id"] == "f_F2")["params"]["plane"]
|
|
self.assertEqual(opposite_plane, {
|
|
"origin_mm": [-76.2, 0.0, 0.0],
|
|
"x_dir": [0.0, 1.0, 0.0],
|
|
"normal": [1.0, 0.0, 0.0],
|
|
})
|
|
|
|
default = lower_model(parse_featurescript(source.replace('"oppositeDirection" : true, ', ""), "00777619-default"), {})
|
|
default_plane = next(item for item in default.cdsl["features"] if item["id"] == "f_F2")["params"]["plane"]
|
|
self.assertEqual(default_plane, {
|
|
"origin_mm": [76.2, 0.0, 0.0],
|
|
"x_dir": [0.0, 1.0, 0.0],
|
|
"normal": [1.0, 0.0, 0.0],
|
|
})
|
|
|
|
def test_shell_defers_pattern_copy_cap_faces_without_an_instance_member(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")
|
|
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00542223"), {})
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
sketches = {item["id"]: item for item in result.cdsl["geometry"]["sketches"]}
|
|
self.assertEqual(sketches["sketch_F4"]["workplane"]["origin_mm"], [0.0, 0.0, -50.0])
|
|
self.assertEqual(features["f_F5"]["params"]["path"]["segment"]["points"][0], [-40.0, -50.0])
|
|
self.assertEqual(features["f_F5"]["params"]["path"]["segment"]["start_tangent"], [27.94, 92.49])
|
|
self.assertNotIn("f_F7", features)
|
|
self.assertEqual(
|
|
next(item for item in result.diagnostics if item.get("feature_id") == "F7")["message"],
|
|
"pattern copy source feature is not replayed by its owner",
|
|
)
|
|
|
|
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")
|
|
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00594348"), {})
|
|
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([item["owner_feature_id"] for item in shell["selectors"]], ["f_F1", "f_F1"])
|
|
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(result.cdsl, Path(directory) / "swept-face-shell.step")
|
|
self.assertEqual(outcome["status"], "rebuilt")
|
|
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_a_direct_prism_swept_face_after_one_primary_cut(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0002/00020670.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00020670"), {})
|
|
self.assertEqual(result.status, "converted_partial")
|
|
self.assertIn(("F10", "extrude_extent_face_selector"), {
|
|
(item.get("feature_id"), item.get("capability")) for item in result.diagnostics
|
|
})
|
|
shell_index = next(index for index, item in enumerate(result.cdsl["features"]) if item["id"] == "f_F4")
|
|
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["selector_intent"]["query_family"], "SWEPT_FACE")
|
|
self.assertEqual(selector["selector_intent"]["derivation_policy"], {
|
|
"allowed": ["boundary", "continuation"], "multiplicity": "one",
|
|
})
|
|
self.assertNotIn("geometry", selector)
|
|
self.assertNotIn("stable_id", selector)
|
|
|
|
prefix = deepcopy(result.cdsl)
|
|
prefix["features"] = prefix["features"][:shell_index + 1]
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
outcome = rebuild_candidate(prefix, Path(directory) / "swept-face-primary-cut-shell.step")
|
|
self.assertEqual(outcome["status"], "rebuilt")
|
|
resolution = next(item for item in outcome["result"]["selector_resolution"] if item["feature_id"] == "f_F4")
|
|
self.assertEqual(resolution["resolution_mode"], "kernel_lineage")
|
|
self.assertEqual(len(resolution["evidence"]["relations"]), 2)
|
|
self.assertEqual(resolution["evidence"]["relations"][1]["operation"], "subtract")
|
|
|
|
def test_shell_does_not_treat_an_add_as_a_primary_cut_swept_face_continuation(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0002/00020670.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
|
|
source = feature.read_text().replace("NewBodyOperationType.REMOVE", "NewBodyOperationType.ADD", 1)
|
|
result = lower_model(parse_featurescript(source, "00020670-no-primary-cut"), {})
|
|
self.assertNotIn("f_F4", {item["id"] for item in result.cdsl["features"]})
|
|
diagnostic = next(item for item in result.diagnostics if item.get("feature_id") == "F4")
|
|
self.assertEqual(diagnostic["capability"], "shell_face_selector")
|
|
|
|
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), {})
|
|
expected_status = "converted_complete" if sample_id == "00212904" else "converted_partial"
|
|
self.assertEqual(result.status, expected_status)
|
|
if sample_id == "00789939":
|
|
self.assertIn(("F14", "extrude_extent_face_selector"), {
|
|
(item.get("feature_id"), item.get("capability")) for item in result.diagnostics
|
|
})
|
|
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,
|
|
)
|
|
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"
|
|
feature = root / "featurescript_rp/0010/00107631.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
# Keep the same nested CAP_FACE outside a true-dependency relation.
|
|
# It is source-profile evidence only and must not select an OFFSET_FACE.
|
|
source = feature.read_text(encoding="utf-8").replace(
|
|
'TDD([makeQuery(id+"F1.opExtrude","CAP_FACE",FACE,{"disambiguationData":[OSD([subQ0])],"isStart":true})])',
|
|
'OSD([subQ0])',
|
|
)
|
|
result = lower_model(parse_featurescript(source, "00107631-without-tdd"), {})
|
|
|
|
diagnostic = next(item for item in result.diagnostics if item.get("feature_id") == "F3")
|
|
self.assertEqual(diagnostic["capability"], "shell_offset_face_selector")
|
|
self.assertIn("true dependency", diagnostic["message"])
|
|
|
|
def test_shell_runtime_failure_exports_its_bound_executable_prefix(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0024/00248377.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00248377"), {})
|
|
self.assertEqual(result.status, "converted_complete")
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = Path(directory) / "swept-face-shell-prefix.step"
|
|
outcome = rebuild_candidate(result.cdsl, rebuilt)
|
|
self.assertTrue(rebuilt.exists())
|
|
self.assertEqual(outcome["status"], "rebuilt")
|
|
self.assertEqual(
|
|
[item["feature_id"] for item in outcome["result"]["feature_results"]],
|
|
["f_F1", "f_F2", "f_F4", "f_F5", "f_F7", "f_F9", "f_F11", "f_F13"],
|
|
)
|
|
resolution = next(item for item in outcome["result"]["selector_resolution"] if item["feature_id"] == "f_F5")
|
|
self.assertEqual(resolution["resolution_mode"], "kernel_lineage")
|
|
|
|
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")
|
|
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00423838"), {})
|
|
f6_profile = next(
|
|
sketch for sketch in result.cdsl["geometry"]["sketches"]
|
|
if sketch["id"] == "sketch_F5__f_F6"
|
|
)["profile"]
|
|
# F6 unions the E4 disk with its adjacent annulus. The actual outer
|
|
# boundary is the untouched E5 source circle, not an inferred radius.
|
|
self.assertEqual(f6_profile["source_entity_id"], "E5")
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = Path(directory) / "selector-prefix.step"
|
|
outcome = rebuild_candidate(result.cdsl, rebuilt)
|
|
self.assertTrue(rebuilt.exists())
|
|
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")
|
|
cut_delta = next(
|
|
item for item in outcome["last_executable_prefix"]["result"]["topology_deltas"]
|
|
if item["feature_id"] == "f_F6" and item["operation"] == "subtract"
|
|
)
|
|
self.assertEqual(cut_delta["operation"], "subtract")
|
|
self.assertEqual(cut_delta["history_status"], "proven")
|
|
self.assertEqual(cut_delta["history_reason"], "per_member_exact_cut_history")
|
|
# The F6 tool is retained only as direct-prism source evidence. Its
|
|
# transient records cannot be selected as active topology, and F7's
|
|
# three-face vertex INTERSECT remains unsupported below.
|
|
transient_tool_records = [
|
|
item for item in outcome["last_executable_prefix"]["result"]["topology_records"]
|
|
if item["feature_id"] == "f_F6" and item.get("transient")
|
|
]
|
|
self.assertTrue(transient_tool_records)
|
|
self.assertTrue(any(
|
|
item.get("body_id") == "transient:f_F6" for item in transient_tool_records
|
|
))
|
|
self.assertTrue(all(
|
|
item.get("body_id") in {None, "transient:f_F6"} for item in transient_tool_records
|
|
))
|
|
self.assertFalse(any(
|
|
item["feature_id"] == "f_F7" and item["status"] == "resolved"
|
|
for item in outcome["error"].get("selector_resolutions") or ()
|
|
))
|
|
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"
|
|
cases = (("00246716", "F7"), ("00051308", "F3"))
|
|
for sample_id, shell_id 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(), sample_id), {})
|
|
diagnostic = next(item for item in result.diagnostics if item.get("feature_id") == shell_id)
|
|
self.assertEqual(diagnostic["code"], "unsupported_engine_capability")
|
|
self.assertEqual(diagnostic["capability"], "shell_face_selector")
|
|
self.assertIn("direct blind/two-sided linear extrusion", diagnostic["message"])
|
|
|
|
def test_curve_point_plane_uses_its_projected_attachment_origin(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0019/00192744.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00192744"), {})
|
|
sketches = {item["id"]: item for item in result.cdsl["geometry"]["sketches"]}
|
|
plane = sketches["sketch_F4"]["workplane"]
|
|
self.assertEqual(plane["origin_mm"], [0.0, -2000.0, -4000.0])
|
|
self.assertAlmostEqual(_global(plane, [0.0, 8944.27])[1], -10000.0, places=2)
|
|
self.assertAlmostEqual(_global(plane, [0.0, 8944.27])[2], 0.0, places=2)
|
|
|
|
def test_delete_pattern_copies_lower_to_explicit_exclusions(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0019/00192744.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00192744"), {})
|
|
features = {item["id"]: item for item in result.cdsl["features"]}
|
|
self.assertEqual(features["f_F6"]["params"]["excluded_instance_indices"], [2])
|
|
self.assertEqual(features["f_F11"]["params"]["excluded_instance_indices"], [2])
|
|
self.assertEqual(result.status, "converted_complete")
|
|
self.assertFalse(result.diagnostics)
|
|
self.assertEqual(features["f_F15"]["atomic_id"], "transform_bodies")
|
|
self.assertEqual(features["f_F15"]["params"]["source_feature_ids"], ["f_F1", "f_F5"])
|
|
self.assertEqual(features["f_F15"]["params"]["pattern_instance_refs"], [
|
|
{"pattern_feature_id": "f_F6", "source_feature_id": "f_F5", "instance_index": 1},
|
|
{"pattern_feature_id": "f_F6", "source_feature_id": "f_F5", "instance_index": 3},
|
|
])
|
|
|
|
def test_feature_face_profile_is_not_reused_as_original_sketch(self):
|
|
source = SOURCE.replace('qSketchRegion(id + "F0", true)', 'makeQuery(id+"F1.opExtrude","CAP_FACE",FACE,{"isStart":false})')
|
|
result = lower_model(parse_featurescript(source, "face-profile"), {})
|
|
self.assertIsNone(result.cdsl)
|
|
self.assertEqual(result.diagnostics[0]["code"], "unsupported_engine_capability")
|
|
self.assertIn("extrude_profile_topology:cap_face", result.diagnostics[0]["capability"])
|
|
|
|
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',
|
|
)
|
|
result = lower_model(parse_featurescript(source, "cap-intent"), {})
|
|
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.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"]["query_expr"], {
|
|
"version": "1.0",
|
|
"root": {
|
|
"node": "topology_query",
|
|
"owner": {"node": "opaque_call", "name": "__binary__", "arguments": [
|
|
{"node": "literal", "value": "id"},
|
|
{"node": "literal", "value": "+"},
|
|
{"node": "literal", "value": "F1.opExtrude"},
|
|
]},
|
|
"topology_type": {"node": "literal", "value": "CAP_FACE"},
|
|
"entity_type": {"node": "literal", "value": "FACE"},
|
|
"arguments": [{"node": "map", "entries": [{
|
|
"key": "isStart", "value": {"node": "literal", "value": "false"},
|
|
}]}],
|
|
},
|
|
})
|
|
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"},
|
|
})
|
|
through_all = deepcopy(kwargs)
|
|
through_all["feature_by_id"] = {
|
|
**kwargs["feature_by_id"],
|
|
"f_F3": {
|
|
**tool,
|
|
"params": {"distance_mm": 10, "end_condition": {"type": "through_all"}},
|
|
},
|
|
}
|
|
self.assertIsNone(_direct_primary_cut_intersection_selector(query, **through_all))
|
|
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_blend_edge_preserves_explicit_boolean_intersection_history(self):
|
|
"""A later unsupported blend query must not discard the F12 section intent."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0078/00789417.txt"
|
|
if not feature.exists():
|
|
self.skipTest("CADFS sample is not installed")
|
|
|
|
result = lower_model(parse_featurescript(feature.read_text(encoding="utf-8"), "00789417"), {})
|
|
|
|
self.assertEqual(result.status, "converted_partial")
|
|
f12 = next(item for item in result.cdsl["features"] if item["id"] == "f_F12")
|
|
section = f12["selectors"][0]["selector_intent"]
|
|
self.assertEqual(section["query_family"], "INTERSECT")
|
|
self.assertEqual(section["evidence"], "kernel_history")
|
|
self.assertEqual(section["disambiguation"], {"type": "source_qualified_boolean_section"})
|
|
f13 = next(item for item in result.cdsl["features"] if item["id"] == "f_F13")
|
|
deferred = f13["selectors"][0]["selector_intent"]
|
|
self.assertEqual(deferred["query_family"], "BLEND_EDGE")
|
|
self.assertEqual(deferred["evidence"], "feature_script_query")
|
|
self.assertEqual(deferred["derivation_policy"]["multiplicity"], "none")
|
|
self.assertNotIn("blend_sources", deferred)
|
|
from engine.cdsl_engine.semantic_validation import validate_semantic_cdsl
|
|
self.assertTrue(validate_semantic_cdsl(result.cdsl)["future_rebuild_ready"])
|
|
invalid = deepcopy(result.cdsl)
|
|
invalid_f13 = next(item for item in invalid["features"] if item["id"] == "f_F13")
|
|
invalid_f13["selectors"][0]["selector_intent"]["blend_sources"] = {
|
|
"edge": {
|
|
"query_family": "CAP_EDGE",
|
|
"owner_feature_id": "f_F3",
|
|
"source_entity": {"sketch_id": "F2", "entity_id": "E1"},
|
|
"lineage_role": "extrude.end",
|
|
},
|
|
"face": {
|
|
"query_family": "CAP_FACE",
|
|
"owner_feature_id": "f_F3",
|
|
"output_role": "extrude.end",
|
|
},
|
|
}
|
|
with self.assertRaisesRegex(ValueError, "deferred BLEND_EDGE cannot declare transition sources"):
|
|
validate_semantic_cdsl(invalid)
|
|
|
|
def test_explicit_boolean_intersect_resolves_from_the_unique_cap_builder_snapshot(self):
|
|
"""A propagated CAP role must not shadow its original boolean input face."""
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0078/00789417.txt"
|
|
if not feature.exists():
|
|
self.skipTest("CADFS sample is not installed")
|
|
|
|
from cadfs_to_cdsl.selector_binding import _bind_feature_selectors
|
|
from engine.cdsl_engine.runtime import prepare_cdsl_execution
|
|
|
|
candidate = lower_model(parse_featurescript(feature.read_text(encoding="utf-8"), "00789417"), {}).cdsl
|
|
execution = prepare_cdsl_execution(candidate)
|
|
self.assertTrue(execution.analysis.runtime_eligible)
|
|
body_ids = {"__current__": None}
|
|
|
|
for node in execution.analysis.plan:
|
|
if node.feature_id == "f_F12":
|
|
break
|
|
_bind_feature_selectors(
|
|
node.source_feature,
|
|
registry=execution.session.topology,
|
|
body_id_for_feature=body_ids,
|
|
)
|
|
execution.execute_next(strict=True)
|
|
body_ids[node.feature_id] = execution.session.body_id
|
|
body_ids["__current__"] = execution.session.body_id
|
|
|
|
selector = next(feature for feature in candidate["features"] if feature["id"] == "f_F12")["selectors"][0]
|
|
resolution = execution.session.topology.resolve(
|
|
selector,
|
|
active_body_id=body_ids["f_F10"],
|
|
)
|
|
self.assertEqual(resolution.status, "resolved")
|
|
self.assertEqual(resolution.resolution_mode, "kernel_intersection")
|
|
self.assertIsNotNone(resolution.record)
|
|
self.assertEqual(
|
|
len([item for item in resolution.evidence["relations"] if item["derivation"] == "intersection"]),
|
|
1,
|
|
)
|
|
inactive = execution.session.topology.resolve(selector, active_body_id=body_ids["__current__"])
|
|
self.assertEqual(inactive.status, "not_found")
|
|
self.assertEqual(inactive.diagnostic.code, "selector_body_member_inactive")
|
|
|
|
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_remove_sweep_lowers_to_a_cut_tool_without_selector_lineage(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
feature = root / "featurescript_rp/0025/00259897.txt"
|
|
if not feature.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(feature.read_text(), "00259897"), {})
|
|
sweep = next(item for item in result.cdsl["features"] if item["id"] == "f_F7")
|
|
self.assertEqual(sweep["atomic_id"], "sweep_cut")
|
|
self.assertNotIn("result_mode", sweep["params"])
|
|
self.assertNotIn("swept_face_contract", sweep["params"])
|
|
self.assertNotIn("swept_edge_contract", sweep["params"])
|
|
self.assertNotIn({
|
|
"code": "unsupported_engine_capability", "capability": "sweep_remove",
|
|
"feature_id": "F7", "operation": "sweep",
|
|
"message": "current CDSL sweep supports additive solid results only",
|
|
}, result.diagnostics)
|
|
|
|
def test_conversion_writes_status_and_sidecars(self):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
root = Path(tmp); source = root / "00000173.txt"; source.write_text(SOURCE)
|
|
sample = Sample("00000173", {"featurescript": str(source)}, {"featurescript": "x"})
|
|
status = convert_one(sample, root / "out", force=True)
|
|
directory = root / "out/samples/00000173"
|
|
self.assertEqual(status["conversion_status"], "converted_complete")
|
|
for name in ("candidate.cdsl.json", "history.json", "diagnostics.json", "status.json"):
|
|
self.assertTrue((directory / name).exists(), name)
|
|
self.assertEqual(json.loads((directory / "candidate.cdsl.json").read_text())["part_id"], "00000173")
|
|
|
|
def test_conversion_separates_semantic_validation_failure_from_parse_failure(self):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
root = Path(tmp); source = root / "00000173.txt"; source.write_text(SOURCE)
|
|
sample = Sample("00000173", {"featurescript": str(source)}, {"featurescript": "x"})
|
|
with patch(
|
|
"engine.cdsl_engine.semantic_validation.validate_semantic_cdsl",
|
|
side_effect=ValueError("semantic fixture rejection"),
|
|
):
|
|
status = convert_one(sample, root / "out", force=True)
|
|
directory = root / "out/samples/00000173"
|
|
self.assertEqual(status["conversion_status"], "semantic_validation_failed")
|
|
self.assertEqual(status["semantic_validation"]["error"]["message"], "semantic fixture rejection")
|
|
self.assertFalse((directory / "candidate.cdsl.json").exists())
|
|
self.assertTrue((directory / "candidate.invalid.cdsl.json").exists())
|
|
self.assertTrue((directory / "history.json").exists())
|
|
diagnostics = json.loads((directory / "diagnostics.json").read_text())
|
|
self.assertIn({
|
|
"code": "semantic_validation_failed",
|
|
"message": "semantic fixture rejection",
|
|
"type": "ValueError",
|
|
}, diagnostics)
|
|
|
|
def test_semantic_failure_preserves_and_rebuilds_a_separate_valid_prefix(self):
|
|
from engine.cdsl_engine.semantic_validation import validate_semantic_cdsl
|
|
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
root = Path(tmp)
|
|
source = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test/featurescript_rp/0096/00965724.txt"
|
|
if not source.exists(): self.skipTest("CADFS sample is not installed")
|
|
sample = Sample("00965724", {"featurescript": str(source)}, {"featurescript": "x"})
|
|
validation_calls = 0
|
|
|
|
def reject_complete_then_validate(cdsl):
|
|
nonlocal validation_calls
|
|
validation_calls += 1
|
|
if validation_calls == 1:
|
|
raise ValueError("semantic fixture rejection")
|
|
return validate_semantic_cdsl(cdsl)
|
|
|
|
with patch(
|
|
"engine.cdsl_engine.semantic_validation.validate_semantic_cdsl",
|
|
side_effect=reject_complete_then_validate,
|
|
):
|
|
status = convert_one(sample, root / "out", force=True)
|
|
directory = root / "out/samples/00965724"
|
|
self.assertEqual(status["conversion_status"], "semantic_validation_failed")
|
|
self.assertFalse((directory / "candidate.cdsl.json").exists())
|
|
self.assertTrue((directory / "candidate.invalid.cdsl.json").exists())
|
|
self.assertTrue((directory / "candidate.prefix.cdsl.json").exists())
|
|
self.assertEqual(status["semantic_valid_prefix"]["feature_count"], 1)
|
|
|
|
rebuilt = rebuild_one(sample, root / "out")
|
|
self.assertEqual(rebuilt["status"], "semantic_validation_failed")
|
|
self.assertEqual(rebuilt["prefix_rebuild_status"], "rebuilt")
|
|
self.assertTrue((directory / "prefix.rebuild.step").exists())
|
|
|
|
def test_direct_blind_cap_face_dressup_role_is_not_misclassified_as_imprint_00998718(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
source = root / "featurescript_rp/0099/00998718.txt"
|
|
if not source.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(source.read_text(), "00998718"), {})
|
|
cap = next(item for item in result.cdsl["features"] if item["id"] == "f_F8")["selectors"][1]
|
|
self.assertEqual(cap["owner_feature_id"], "f_F7")
|
|
self.assertEqual(cap["output_role"], "extrude.end")
|
|
from engine.cdsl_engine.semantic_validation import validate_semantic_cdsl
|
|
self.assertTrue(validate_semantic_cdsl(result.cdsl)["future_rebuild_ready"])
|
|
|
|
def test_query_set_cap_face_output_roles_inherit_the_dressup_selector_slot_00965724(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
source = root / "featurescript_rp/0096/00965724.txt"
|
|
if not source.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(source.read_text(), "00965724"), {})
|
|
chamfer = next(item for item in result.cdsl["features"] if item["id"] == "f_F2")
|
|
query_set = chamfer["selectors"][0]
|
|
self.assertEqual(query_set["selector_intent"]["query_family"], "QUERY_SET")
|
|
self.assertEqual(
|
|
[item["output_role"] for item in query_set["query_operands"]],
|
|
["extrude.start", "extrude.end"],
|
|
)
|
|
from engine.cdsl_engine.semantic_validation import validate_semantic_cdsl
|
|
self.assertTrue(validate_semantic_cdsl(result.cdsl)["future_rebuild_ready"])
|
|
|
|
invalid = deepcopy(result.cdsl)
|
|
invalid["features"][1]["selectors"][0]["query_operands"][1]["owner_feature_id"] = "f_missing"
|
|
with self.assertRaisesRegex(ValueError, "forward or missing owner_feature_id"):
|
|
validate_semantic_cdsl(invalid)
|
|
|
|
def test_dressup_with_an_omitted_selector_owner_defers_before_semantic_validation_00508029(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
source = root / "featurescript_rp/0050/00508029.txt"
|
|
if not source.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(source.read_text(), "00508029"), {})
|
|
self.assertEqual(result.status, "converted_partial")
|
|
self.assertNotIn("f_F16", {item["id"] for item in result.cdsl["features"]})
|
|
self.assertIn({
|
|
"code": "unsupported_engine_capability",
|
|
"capability": "selector_owner_unavailable",
|
|
"feature_id": "F16",
|
|
"operation": "chamfer",
|
|
"message": "selector owner F14 has no executable CDSL producer",
|
|
}, result.diagnostics)
|
|
from engine.cdsl_engine.semantic_validation import validate_semantic_cdsl
|
|
self.assertTrue(validate_semantic_cdsl(result.cdsl)["future_rebuild_ready"])
|
|
|
|
def test_unproven_extent_face_is_deferred_instead_of_stable_id_bound_00002892(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
source = root / "featurescript_rp/0000/00002892.txt"
|
|
if not source.exists(): self.skipTest("CADFS sample is not installed")
|
|
result = lower_model(parse_featurescript(source.read_text(), "00002892"), {})
|
|
self.assertNotIn("f_F3", {feature["id"] for feature in result.cdsl["features"]})
|
|
self.assertIn({
|
|
"code": "unsupported_engine_capability",
|
|
"capability": "extrude_extent_face_selector",
|
|
"feature_id": "F3",
|
|
"operation": "extrude",
|
|
"message": "current CDSL face extent requires a complete/proven active selector reference",
|
|
}, result.diagnostics)
|
|
from engine.cdsl_engine.semantic_validation import validate_semantic_cdsl
|
|
self.assertTrue(validate_semantic_cdsl(result.cdsl)["future_rebuild_ready"])
|
|
|
|
def test_force_rebuild_refreshes_the_candidate_from_source(self):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
root = Path(tmp); source = root / "00000173.txt"; source.write_text(SOURCE)
|
|
sample = Sample("00000173", {"featurescript": str(source)}, {"featurescript": "x"})
|
|
directory = root / "out/samples/00000173"; directory.mkdir(parents=True)
|
|
(directory / "candidate.cdsl.json").write_text(json.dumps({"part_id": "stale"}), encoding="utf-8")
|
|
(directory / "status.json").write_text(json.dumps({"sample_id": sample.sample_id}), encoding="utf-8")
|
|
status = rebuild_one(sample, root / "out", force=True)
|
|
self.assertEqual(status["rebuild_status"], "rebuilt")
|
|
self.assertEqual(json.loads((directory / "candidate.cdsl.json").read_text())["part_id"], "00000173")
|
|
|
|
|
|
if __name__ == "__main__": unittest.main()
|