738934416e
- 扩展 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 特征链及运行时兼容性。
78 lines
3.8 KiB
Python
78 lines
3.8 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
import unittest
|
|
|
|
from cadfs_to_cdsl.featurescript_parser import parse_featurescript
|
|
from cadfs_to_cdsl.lowering import lower_model
|
|
from cadfs_to_cdsl.selector_binding import _score, bind_candidate_selectors
|
|
|
|
|
|
class SelectorBindingTests(unittest.TestCase):
|
|
def test_face_normal_match_is_orientation_independent(self) -> None:
|
|
score = _score(
|
|
{"normal": [0.0, 0.0, 1.0], "plane_offset_mm": 12.0},
|
|
{"normal": [0.0, 0.0, -1.0], "plane_offset_mm": 12.0},
|
|
)
|
|
self.assertEqual(score, 1.0)
|
|
|
|
def test_rotational_face_axis_direction_is_orientation_independent(self) -> None:
|
|
score = _score(
|
|
{"axis_direction": [0.0, 0.0, 1.0]},
|
|
{"axis_direction": [0.0, 0.0, -1.0]},
|
|
)
|
|
self.assertEqual(score, 1.0)
|
|
|
|
def test_axis_origin_distinguishes_parallel_cylinders(self) -> None:
|
|
score = _score(
|
|
{"axis_origin_mm": [0.0, 25.4, 33.37]},
|
|
{"axis_origin_mm": [33.38, 25.4, 0.0]},
|
|
)
|
|
self.assertEqual(score, 0.0)
|
|
|
|
def test_empty_snapshot_score_is_not_treated_as_a_match(self) -> None:
|
|
self.assertEqual(_score({}, {"normal": [0.0, 0.0, 1.0]}), 0.0)
|
|
|
|
def test_swept_face_area_lower_bound_rejects_coplanar_fragment(self) -> None:
|
|
expected = {"normal": [0.0, 1.0, 0.0], "plane_offset_mm": 54.69, "minimum_area_mm2": 285.0}
|
|
self.assertIsNone(_score(expected, {"normal": [0.0, 1.0, 0.0], "plane_offset_mm": 54.69, "area_mm2": 0.64}))
|
|
self.assertEqual(_score(expected, {"normal": [0.0, 1.0, 0.0], "plane_offset_mm": 54.69, "area_mm2": 463.7}), 1.0)
|
|
|
|
def test_reversed_cap_normal_reverses_its_plane_offset(self) -> None:
|
|
score = _score(
|
|
{"normal": [0.0, 0.0, 1.0], "plane_offset_mm": 10.0},
|
|
{
|
|
"normal": [0.0, 0.0, 1.0],
|
|
"plane_normal": [0.0, 0.0, -1.0],
|
|
"plane_offset_mm": -10.0,
|
|
},
|
|
)
|
|
|
|
self.assertEqual(score, 1.0)
|
|
|
|
def test_intersection_vertex_binds_pattern_and_current_body_prefixes(self) -> None:
|
|
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")
|
|
candidate = lower_model(parse_featurescript(feature.read_text(), "00423838"), {})
|
|
bound, evidence = bind_candidate_selectors(candidate.cdsl)
|
|
|
|
f7 = next(item for item in bound["features"] if item["id"] == "f_F7")
|
|
reference = f7["params"]["end_condition"]["reference"]
|
|
components = reference["intersection_of"]
|
|
self.assertEqual(len(components), 3)
|
|
self.assertEqual([item["binding_feature_id"] for item in components], ["f_F4", "f_F4", "f_F6"])
|
|
# 绑定发生在 F4 完整 pattern 前缀;owner 保留 instance 4 的语义来源,
|
|
# snapshot 则指向该前缀的 active Compound B-rep。
|
|
self.assertEqual([item["owner_feature_id"] for item in components[:2]], ["f_F4.c4.f_F1", "f_F4.c4.f_F1"])
|
|
self.assertTrue(all(item["snapshot_id"].startswith("body:f_F4:") for item in components[:2]))
|
|
self.assertEqual(components[2]["match_mode"], "all")
|
|
# 同一圆柱面可能被前缀 boolean 切成不同数量的 B-rep face;关键是
|
|
# match_mode=all 保留每个可匹配片段,而不是把它收缩为任意一个面。
|
|
matched = components[2]["matched_selectors"]
|
|
self.assertTrue(matched)
|
|
self.assertTrue(all(item["owner_feature_id"] == "f_F6" for item in matched))
|
|
self.assertTrue(all(item["snapshot_id"].startswith("body:f_F6:") for item in matched))
|
|
f7_evidence = next(item for item in evidence if item["feature_id"] == "f_F7")
|
|
self.assertEqual(len(f7_evidence["resolved"]), len(matched) + 2)
|