Files
cdsl-cad/backend/tests/test_engine_loft_geometry.py
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

161 lines
6.7 KiB
Python

from __future__ import annotations
import tempfile
import unittest
from pathlib import Path
from engine.cdsl_engine.runtime import rebuild_cdsl
from engine.cdsl_engine.sketch_solver import resolve_profile
def _spline_profile(sketch_id: str, z: float) -> dict:
return {
"id": sketch_id,
"workplane": {"origin_mm": [0, 0, z], "x_dir": [1, 0, 0], "normal": [0, 0, 1]},
"profile": {
"type": "analytic_contours",
"contours": [{
"role": "outer",
"closed": True,
"segments": [{
"type": "bspline",
"start": [0, 0],
"end": [0, 0],
"points": [[0, 0], [20, 0], [25, 10], [15, 18], [0, 10], [0, 0]],
}],
}],
},
}
class LoftGeometryTests(unittest.TestCase):
def test_periodic_bspline_supports_centripetal_parameterization(self):
sketch = {
"id": "profile",
"workplane": {"origin_mm": [0, 0, 0], "x_dir": [1, 0, 0], "normal": [0, 0, 1]},
"profile": {"type": "analytic_contours", "contours": [{
"role": "outer", "closed": True, "segments": [{
"type": "bspline", "periodic": True, "parameterization": "centripetal",
"start": [0, 0], "end": [0, 0],
"points": [[0, 0], [16, 0], [16, 9], [0, 9], [0, 0]],
}],
}]},
}
resolved = resolve_profile(sketch)
parameters = resolved["contour_regions_mm"][0]["outer"][0]["parameters"]
self.assertEqual(parameters, [0.0, 4.0, 7.0, 11.0, 14.0])
def test_open_bspline_segment_can_close_with_other_contour_edges(self):
cdsl = {
"schema": "cad.cdsl.llm.v1",
"schema_version": "1.1.0",
"kind": "part",
"part_id": "open-bspline-contour",
"meta": {"unit": "mm"},
"geometry": {"sketches": [{
"id": "profile",
"workplane": {"origin_mm": [0, 0, 0], "x_dir": [1, 0, 0], "normal": [0, 0, 1]},
"profile": {"type": "analytic_contours", "contours": [{
"role": "outer", "closed": True,
"segments": [
{"type": "line", "start": [0, 0], "end": [0, 10]},
{
"type": "bspline", "start": [0, 10], "end": [0, 0],
"points": [[0, 10], [4, 10], [4, 0], [0, 0]],
},
],
}]},
}]},
"features": [{
"id": "add", "atomic_id": "extrude_add_blind", "depends_on": [],
"sketch_id": "profile", "params": {"distance_mm": 5},
}],
}
with tempfile.TemporaryDirectory() as tmp:
rebuilt = rebuild_cdsl(cdsl, Path(tmp) / "open-bspline-contour.step")
self.assertGreater(rebuilt["volume_mm3"], 1.0)
def test_reversed_bspline_transports_the_parameter_domain(self):
sketch = {
"id": "profile",
"workplane": {"origin_mm": [0, 0, 0], "x_dir": [1, 0, 0], "normal": [0, 0, 1]},
"profile": {"type": "analytic_contours", "contours": [{
"role": "outer", "closed": True,
"segments": [
{"type": "line", "start": [0, 0], "end": [0, 4]},
{
"type": "bspline", "start": [3, 0], "end": [0, 4],
"points": [[3, 0], [3, 2], [1, 4], [0, 4]],
"parameters": [0, 2, 4, 6],
"start_tangent": [2, 0], "end_tangent": [0, -3],
},
{"type": "line", "start": [3, 0], "end": [0, 0]},
],
}]},
}
resolved = resolve_profile(sketch)
spline = next(edge for edge in resolved["contour_regions_mm"][0]["outer"] if edge["type"] == "bspline")
self.assertEqual(spline["points_mm"], [[0.0, 4.0, 0.0], [1.0, 4.0, 0.0], [3.0, 2.0, 0.0], [3.0, 0.0, 0.0]])
self.assertEqual(spline["parameters"], [0.0, 2.0, 4.0, 6.0])
self.assertEqual(spline["start_tangent_mm"], [0.0, 3.0, 0.0])
self.assertEqual(spline["end_tangent_mm"], [-2.0, 0.0, 0.0])
def test_bspline_loft_and_mirror_replay_produce_step(self):
cdsl = {
"schema": "cad.cdsl.llm.v1",
"schema_version": "1.1.0",
"kind": "part",
"part_id": "bspline-loft-mirror",
"meta": {"unit": "mm"},
"geometry": {"sketches": [_spline_profile("lower", 0), _spline_profile("upper", 20)]},
"features": [
{
"id": "mirror_plane",
"atomic_id": "reference_plane",
"depends_on": [],
"params": {"plane": {"origin_mm": [35, 0, 0], "x_dir": [0, 1, 0], "normal": [1, 0, 0]}},
"execution_status": "supported",
},
{
"id": "loft",
"atomic_id": "loft_add",
"depends_on": [],
"params": {"profile_sketch_ids": ["lower", "upper"]},
"execution_status": "supported",
},
{
"id": "mirror",
"atomic_id": "pattern_mirror",
"depends_on": ["mirror_plane", "loft"],
"params": {
"source_feature_ids": ["loft"],
"mirror_plane": {
"kind": "plane",
"owner_feature_id": "mirror_plane",
"stable_id": "mirror-plane",
"source": "runtime_snapshot",
"confidence": 1.0,
},
},
"selectors": [{
"kind": "plane",
"owner_feature_id": "mirror_plane",
"stable_id": "mirror-plane",
"source": "runtime_snapshot",
"confidence": 1.0,
}],
"execution_status": "supported",
},
],
}
with tempfile.TemporaryDirectory() as tmp:
rebuilt = rebuild_cdsl(cdsl, Path(tmp) / "loft.step")
self.assertGreater(rebuilt["volume_mm3"], 1.0)
self.assertEqual(rebuilt["solid_count"], 2)
self.assertEqual([item["feature_id"] for item in rebuilt["feature_results"]], ["mirror_plane", "loft", "mirror.m.loft", "mirror"])
if __name__ == "__main__":
unittest.main()