Files
cdsl-cad/cadfs_to_cdsl/tests/test_lowering.py
T
likang 738934416e feat(cadfs): 扩展重建引擎能力并固化代表性模型回归
- 扩展 CDSL engine 的 shell、sweep、loft、reference plane、pattern 等运行时能力,
  支持新的实体结果模式、双向拉伸、曲线扫掠、镜像/圆周阵列及相关 selector 解析。
- 完善 Build123d 适配层的拓扑快照、Compound/ShapeList 兼容处理和旋转曲面识别,
  兼容 Python 3.12 / 当前 Build123d 缺少 axis_of_rotation 的合法曲面场景。
- 扩展 CDSL schema、profile schema、capability analysis、semantic validation 和
  sketch solver,使新增建模操作能够被校验、执行并保留可诊断的部分结果。
- 完善 CADFS FeatureScript lowering:
  支持 shell、sweep、surface/实体 loft、圆周阵列副本、镜像副本、删除阵列实例、
  新 body 操作、更多拉伸终止条件和 reference plane 变体。
- 补齐椭圆、B-spline、环形区域、imprint、SWEPT_FACE、CAP_FACE、OFFSET_FACE 等
  草图和拓扑引用的转换逻辑,改善后续特征的工作平面、轴线和 profile 定位精度。
- 改进 selector binding:支持 pattern 前缀复合 B-rep 快照、交集顶点引用、
  多面 match_mode=all、圆柱轴线/半径和面积下限等稳定匹配条件。
- 修复 MID_PLANE 法向统一后交线方向未同步的问题,恢复 00287955 基准面的正确位置;
  修复 00542223 sweep 路径反转后的切线契约和 00423838 的拓扑面数不稳定测试假设。
- 修正 CADFS 比较模块 import 路径,补充重建报告、批量重建脚本、目标文档和 README。
- 新增并扩展 engine、lowering、parser、selector binding、reports、integration 和
  Onshape pipeline 回归测试,覆盖代表性 CADFS 特征链及运行时兼容性。
2026-09-08 11:47:10 +08:00

719 lines
44 KiB
Python

from __future__ import annotations
import json, math, tempfile, unittest
from pathlib import Path
from cadfs_to_cdsl.featurescript_parser import parse_featurescript
from cadfs_to_cdsl.lowering import _arc, _contours, _global, lower_model
from cadfs_to_cdsl.pipeline import compare_one, convert_one, rebuild_one
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"}), 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")
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_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_remains_an_explicit_engine_capability_gap(self):
source = TRANSFORM_SOURCE.replace('"makeCopy":false', '"makeCopy":true')
result = lower_model(parse_featurescript(source, "transform-copy"), {})
self.assertEqual(result.status, "converted_partial")
self.assertEqual(result.diagnostics[-1]["code"], "unsupported_engine_capability")
self.assertEqual(result.diagnostics[-1]["capability"], "transform")
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_circular_pattern_replays_the_fused_swept_body_history(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_F1", "f_F5"])
def test_shared_sketch_edge_produces_each_bounded_region(self):
segments = [
{"type": "line", "start": [0, 0], "end": [1, 0]},
{"type": "line", "start": [1, 0], "end": [2, 0]},
{"type": "line", "start": [2, 0], "end": [2, 1]},
{"type": "line", "start": [2, 1], "end": [1, 1]},
{"type": "line", "start": [1, 1], "end": [0, 1]},
{"type": "line", "start": [0, 1], "end": [0, 0]},
{"type": "line", "start": [1, 0], "end": [1, 1]},
]
contours, construction = _contours(segments)
self.assertEqual(len(contours), 2)
self.assertFalse(construction)
self.assertEqual(sorted(len(item["segments"]) for item in contours), [4, 4])
def test_vertex_on_long_line_is_split_before_region_extraction(self):
segments = [
{"type": "line", "start": [0, 0], "end": [2, 0]},
{"type": "line", "start": [2, 0], "end": [2, 2]},
{"type": "line", "start": [2, 2], "end": [0, 2]},
{"type": "line", "start": [0, 2], "end": [0, 0]},
{"type": "line", "start": [2, 0.5], "end": [3, 0.5]},
{"type": "line", "start": [3, 0.5], "end": [3, 1.5]},
{"type": "line", "start": [3, 1.5], "end": [2, 1.5]},
]
contours, construction = _contours(segments)
self.assertEqual(len(contours), 2)
self.assertFalse(construction)
def test_surface_operation_on_a_closed_region_lowers_to_a_solid_revolve(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
feature = root / "featurescript_rp/0095/00957738.txt"
if not feature.exists(): self.skipTest("CADFS sample is not installed")
result = lower_model(parse_featurescript(feature.read_text(), "00957738"), {})
self.assertEqual(result.status, "converted_complete")
feature = next(item for item in result.cdsl["features"] if item["id"] == "f_F2")
self.assertEqual(feature["atomic_id"], "revolve_add")
self.assertNotIn("result_mode", feature["params"])
def test_explicit_surface_body_lowers_to_the_surface_revolve_contract(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
feature = root / "featurescript_rp/0078/00784880.txt"
if not feature.exists(): self.skipTest("CADFS sample is not installed")
result = lower_model(parse_featurescript(feature.read_text(), "00784880"), {})
self.assertEqual(result.status, "converted_complete")
feature = next(item for item in result.cdsl["features"] if item["id"] == "f_F3")
self.assertEqual(feature["atomic_id"], "revolve_surface")
self.assertNotIn("result_mode", feature["params"])
def test_mixed_extrude_lowers_selected_circular_surface_wires(self):
root = Path(__file__).parents[2] / "data/cadfs-sample/CADFS_test"
feature = root / "featurescript_rp/0071/00710855.txt"
if not feature.exists(): self.skipTest("CADFS sample is not installed")
result = lower_model(parse_featurescript(feature.read_text(), "00710855"), {})
self.assertEqual(result.status, "converted_complete")
features = {item["id"]: item for item in result.cdsl["features"]}
self.assertEqual(features["f_F1_surface"]["atomic_id"], "extrude_surface")
self.assertEqual(features["f_F1_surface"]["params"], {
"distance_mm": 63.5,
"reverse": False,
"reverse_distance_mm": 63.5,
})
self.assertEqual(features["f_F5_surface"]["atomic_id"], "extrude_surface")
sketches = {item["id"]: item for item in result.cdsl["geometry"]["sketches"]}
radii = [
contour["segments"][0]["radius_mm"]
for contour in sketches["sketch_F0__f_F1_surface"]["profile"]["contours"]
]
self.assertEqual(radii, [19.0, 16.0])
profile = sketches[features["f_F5"]["sketch_id"]]["profile"]
self.assertEqual(profile["type"], "analytic_contours")
self.assertEqual(
[(item["role"], item["segments"][0]["radius_mm"]) for item in profile["contours"]],
[("outer", 25.5), ("inner", 19.0)],
)
def test_mixed_extrude_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_cap_face_profile_preserves_the_selected_circular_output_region(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"]}
sketches = {item["id"]: item for item in result.cdsl["geometry"]["sketches"]}
cap_profile = sketches[features["f_F5"]["sketch_id"]]["profile"]
self.assertEqual(features["f_F5"]["sketch_id"], "sketch_F3__f_F5")
self.assertEqual(cap_profile["type"], "analytic_contours")
self.assertEqual([
contour["segments"][0]["radius_mm"]
for contour in cap_profile["contours"]
], [34.0, 32.0])
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_intersect_partition_profile_lowers_to_its_bounded_circular_region(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_partial")
self.assertEqual({item["feature_id"] for item in result.diagnostics}, {"F7", "F12"})
features = {item["id"]: item for item in result.cdsl["features"]}
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"], "analytic_contours")
self.assertEqual(
{contour["role"]: contour["segments"][0]["radius_mm"] for contour in first["contours"]},
{"outer": 2.75, "inner": 2.25},
)
self.assertEqual(second, {"type": "circle", "center": [-50.8, 0.0], "radius_mm": 2.25})
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_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_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})
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.assertNotIn("F7", {item.get("feature_id") for item in result.diagnostics})
self.assertNotIn("F8", {item.get("feature_id") for item in result.diagnostics})
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_partial")
self.assertEqual(
[(item["feature_id"], item["capability"]) for item in result.diagnostics if item.get("code") == "unsupported_engine_capability"],
[("F15", "transform")],
)
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()