1718 lines
102 KiB
Python
1718 lines
102 KiB
Python
from __future__ import annotations
|
|
|
|
import json, math, tempfile, unittest
|
|
from copy import deepcopy
|
|
from pathlib import Path
|
|
from cadfs_to_cdsl.compare import compare_steps
|
|
from cadfs_to_cdsl.featurescript_parser import parse_featurescript
|
|
from cadfs_to_cdsl.ir import Call
|
|
from cadfs_to_cdsl.lowering import _arc, _contours, _global, _number, _record_single_body_successor, lower_model
|
|
from cadfs_to_cdsl.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
|
|
|
|
|
|
class LoweringTests(unittest.TestCase):
|
|
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_and_two_section_loft_swept_edges_bind_for_fillet(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)
|
|
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)
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = Path(directory) / "rebuild.step"
|
|
outcome = rebuild_candidate(result.cdsl, rebuilt)
|
|
self.assertEqual(outcome["status"], "rebuilt")
|
|
comparison = compare_steps(root / "step_abc" / sample_id[:4] / f"{sample_id}.step", rebuilt)
|
|
self.assertTrue(comparison["strict"]["passed"])
|
|
|
|
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_full_revolve_swept_circle_edges_bind_for_dressups(self):
|
|
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
|
|
for sample_id, dressup_id, selector_count in (
|
|
("00048326", "f_F3", 2),
|
|
("00025622", "f_F2", 1),
|
|
):
|
|
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"]), selector_count)
|
|
self.assertTrue(all(
|
|
selector["geometry"].get("curve_type") == "circle"
|
|
and len(selector["geometry"].get("circle_center_mm") or []) == 3
|
|
and selector["geometry"].get("radius_mm", 0) > 0
|
|
for selector in dressup["selectors"]
|
|
))
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = Path(directory) / "rebuild.step"
|
|
outcome = rebuild_candidate(result.cdsl, rebuilt)
|
|
self.assertEqual(outcome["status"], "rebuilt")
|
|
comparison = compare_steps(root / "step_abc" / sample_id[:4] / f"{sample_id}.step", rebuilt)
|
|
self.assertTrue(comparison["strict"]["passed"])
|
|
|
|
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_equivalent_imprint_profile_retains_direct_full_revolve_swept_edges(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"
|
|
for selector in fillet["selectors"]
|
|
))
|
|
|
|
from cadfs_to_cdsl.selector_binding import bind_candidate_selectors
|
|
prefix = dict(result.cdsl)
|
|
prefix["features"] = result.cdsl["features"][:2]
|
|
bound, evidence = bind_candidate_selectors(prefix)
|
|
selectors = next(item for item in bound["features"] if item["id"] == "f_F2")["selectors"]
|
|
self.assertEqual(len(evidence), 1)
|
|
self.assertEqual(len(selectors), 5)
|
|
self.assertTrue(all(selector.get("snapshot_id") for selector in selectors))
|
|
|
|
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_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 the exact single-body successor of F1 after a regular
|
|
# additive fusion. COPY must reference that live member rather
|
|
# than resurrecting F1's consumed runtime body key.
|
|
"00981258": {"f_F4": "f_F3", "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():
|
|
self.assertEqual(transforms[feature_id]["params"], {
|
|
"source_feature_ids": [source_feature_id],
|
|
"transform": {"type": "translation", "translation_mm": [0.0, 0.0, 0.0]},
|
|
"make_copy": True,
|
|
})
|
|
|
|
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_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"), {})
|
|
shell = next(feature for feature in result.cdsl["features"] if feature["id"] == "f_F7")
|
|
self.assertEqual(shell["params"]["target_feature_id"], "f_F5")
|
|
self.assertFalse(any(
|
|
diagnostic.get("capability") == "shell_parts_body_source"
|
|
for diagnostic in result.diagnostics
|
|
))
|
|
|
|
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_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")
|
|
|
|
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"]["geometry"]["plane_offset_mm"], 0.0)
|
|
swept = next(item for item in result.cdsl["features"] if item["id"] == "f_F6")
|
|
self.assertEqual(swept["params"]["end_condition"]["reference"]["geometry"]["radius_mm"], 24.3)
|
|
mirror = next(item for item in result.cdsl["features"] if item["id"] == "f_F18")
|
|
self.assertTrue(mirror["params"]["mirror_current_body"])
|
|
|
|
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_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_defers_for_non_direct_shell_provenance(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"), {})
|
|
|
|
self.assertNotIn("f_F14", {item["id"] for item in result.cdsl["features"]})
|
|
diagnostic = next(item for item in result.diagnostics if item.get("feature_id") == "F14")
|
|
self.assertEqual(diagnostic["capability"], "extrude_profile_topology:offset_face")
|
|
|
|
def test_circular_pattern_uses_the_proven_fused_body_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"]}
|
|
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"]}
|
|
self.assertEqual(result.status, "converted_complete")
|
|
self.assertEqual(features["f_F5"]["params"]["source_feature_ids"], ["f_F4"])
|
|
self.assertEqual(features["f_F6"]["params"], {
|
|
"operation": "union",
|
|
"keep_tools": False,
|
|
"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},
|
|
],
|
|
})
|
|
|
|
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_rebuilds_surface_limited_chamfer_history(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"], "rebuilt")
|
|
chamfer = next(item for item in rebuilt["result"]["feature_results"] if item["feature_id"] == "f_F10")
|
|
self.assertIn("chamfer_surface_limited", [item["code"] for item in chamfer["diagnostics"]])
|
|
self.assertAlmostEqual(rebuilt["result"]["volume_mm3"], 72577.33528261917, places=6)
|
|
|
|
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_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)
|
|
self.assertEqual(cap_extrude["selectors"], [{
|
|
"kind": "face", "owner_feature_id": "f_F3", "output_role": "extrude.start",
|
|
"source": "runtime_snapshot", "confidence": 1.0,
|
|
}])
|
|
|
|
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")
|
|
# F12's direct SWEPT_BODY(F1) names the one physical body after its
|
|
# additive and dress-up successors. The lowering must retain that
|
|
# proven successor, never substitute an implicit active aggregate.
|
|
self.assertEqual(features["f_F12"]["params"]["source_feature_ids"], ["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(any(item["code"] == "body_source_unavailable" for item in 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 unsupported body continuation stays
|
|
# explicit and F1--F7 remains an executable prefix.
|
|
prefix = deepcopy(result.cdsl)
|
|
prefix["features"] = [
|
|
item for item in prefix["features"]
|
|
if item["id"] in {"f_F1", "f_F2", "f_F4", "f_F5", "f_F7"}
|
|
]
|
|
from engine.cdsl_engine.runtime import rebuild_cdsl
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
rebuilt = rebuild_cdsl(prefix, Path(directory) / "two-sided-cap-prefix.step")
|
|
self.assertFalse(rebuilt["runtime_diagnostics"])
|
|
self.assertEqual(
|
|
[item["feature_id"] for item in rebuilt["feature_results"]],
|
|
["f_F1", "f_F2", "f_F4", "f_F5", "f_F7"],
|
|
)
|
|
|
|
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,
|
|
"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_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_complete")
|
|
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,
|
|
})
|
|
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_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_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_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_preserves_pattern_copy_cap_faces_and_offset_edge_fillet(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"]}
|
|
shell = features["f_F7"]
|
|
fillet = features["f_F8"]
|
|
|
|
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.assertEqual(shell["selectors"][1]["geometry"], {
|
|
"normal": [0.0, 0.0, 1.0], "plane_offset_mm": -50.0,
|
|
"center_mm": [0.0, -40.0, -50.0], "minimum_area_mm2": 481.0563750809371,
|
|
})
|
|
expected_centers = [[34.64101615137755, 20.0, -50.0], [0.0, -40.0, -50.0], [-34.64101615137754, 20.0, -50.0]]
|
|
for center, expected in zip([selector["geometry"]["center_mm"] for selector in shell["selectors"][:3]], expected_centers):
|
|
for value, target in zip(center, expected): self.assertAlmostEqual(value, target)
|
|
self.assertEqual(shell["selectors"][3]["geometry"]["center_mm"], [0.0, 0.0, 50.0])
|
|
self.assertEqual(shell["atomic_id"], "shell")
|
|
self.assertEqual(len(shell["selectors"]), 4)
|
|
self.assertEqual(
|
|
[selector["owner_feature_id"] for selector in shell["selectors"][:3]],
|
|
["f_F6.c1.f_F5", "f_F5", "f_F6.c2.f_F5"],
|
|
)
|
|
self.assertEqual(fillet["atomic_id"], "fillet")
|
|
self.assertEqual(fillet["selectors"][0]["geometry"]["source_circle_radius_mm"], 17.5)
|
|
self.assertEqual(shell["params"]["target_feature_id"], "f_F5")
|
|
self.assertFalse(any(
|
|
diagnostic.get("capability") == "shell_parts_body_source"
|
|
for diagnostic in result.diagnostics
|
|
))
|
|
self.assertNotIn("F8", {item.get("feature_id") for item in result.diagnostics})
|
|
|
|
def test_shell_lowers_and_executes_direct_linear_extrude_swept_faces(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("normal" in item["geometry"] for item in shell["selectors"]))
|
|
|
|
prefix = deepcopy(result.cdsl)
|
|
prefix["features"] = prefix["features"][:shell_index + 1]
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
outcome = rebuild_candidate(prefix, Path(directory) / "swept-face-shell.step")
|
|
self.assertEqual(outcome["status"], "rebuilt")
|
|
self.assertEqual(
|
|
[item["status"] for item in outcome["result"]["feature_results"]],
|
|
["executed", "executed"],
|
|
)
|
|
self.assertFalse(outcome["result"].get("diagnostics", []))
|
|
|
|
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"], "rebuild_failed")
|
|
self.assertEqual(outcome["error"]["type"], "RuntimeExecutionError")
|
|
self.assertIn("bound_cdsl", outcome)
|
|
self.assertEqual(outcome["last_executable_prefix"]["failed_feature_id"], "f_F5")
|
|
self.assertEqual(outcome["last_executable_prefix"]["last_feature_id"], "f_F4")
|
|
self.assertEqual(outcome["last_executable_prefix"]["result"]["solid_count"], 1)
|
|
|
|
def test_pattern_copy_selector_binding_rebuilds_the_full_history(self):
|
|
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"), {})
|
|
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"], "rebuilt")
|
|
resolved = [
|
|
item for item in outcome["result"]["selector_resolution"]
|
|
if item["feature_id"] == "f_F7"
|
|
and item["selector"].get("owner_feature_id") == "f_F4.c4.f_F1"
|
|
]
|
|
self.assertEqual(len(resolved), 2)
|
|
self.assertTrue(all(item["status"] == "resolved" for item in resolved))
|
|
|
|
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_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_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()
|