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

1717 lines
88 KiB
Python

from __future__ import annotations
import hashlib
import json
import math
import sys
import tempfile
import unittest
from copy import deepcopy
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / "backend" / "engine"))
from cdsl_engine.batch_rebuild import _failure_category, _verification_classification, batch_analyze # noqa: E402
from cdsl_engine.capabilities import CapabilityAnalyzer # noqa: E402
from cdsl_engine.runtime_types import HoleSpec, PlaneSpec, TopologyRecord, TopologyRegistry # noqa: E402
from cdsl_engine.sketch_solver import SHAPE_GENERATORS, resolve_all_sketches # noqa: E402
def _workplane() -> dict:
return {"origin_mm": [0, 0, 0], "x_dir": [1, 0, 0], "normal": [0, 0, 1]}
def _rectangle(minimum: list[float], maximum: list[float]) -> dict:
return {"type": "polygon", "vertices": [
[minimum[0], minimum[1]], [maximum[0], minimum[1]],
[maximum[0], maximum[1]], [minimum[0], maximum[1]],
]}
def _selector_digest(selectors: list[str]) -> str:
return hashlib.sha256("\n".join(selectors).encode("utf-8")).hexdigest()
def _corpus_manifest(source: Path) -> dict:
return json.loads((source / "corpus-manifest.json").read_text(encoding="utf-8"))
class EngineRuntimeFoundationTests(unittest.TestCase):
def _base_block(self) -> dict:
return {
"schema": "cad.cdsl.llm.v1", "schema_version": "1.1.0", "kind": "part", "part_id": "runtime-block",
"meta": {"unit": "mm"},
"geometry": {"sketches": [{
"id": "base", "workplane": _workplane(),
"profile": _rectangle([-5, -5], [5, 5]),
}]},
"features": [{
"id": "base_add", "atomic_id": "extrude_add_blind", "depends_on": [],
"params": {"distance_mm": 10}, "sketch_id": "base",
}],
}
def test_runtime_module_has_no_build123d_import(self) -> None:
runtime_source = (ROOT / "backend" / "engine" / "cdsl_engine" / "runtime.py").read_text(encoding="utf-8")
self.assertNotIn("from build123d", runtime_source)
self.assertNotIn("import build123d", runtime_source)
def test_execution_session_declares_a_kernel_neutral_adapter_protocol(self) -> None:
from cdsl_engine.runtime import ExecutionSession, GeometryAdapter
self.assertIn("adapter", ExecutionSession.__dataclass_fields__)
self.assertTrue(getattr(GeometryAdapter, "_is_protocol", False))
self.assertIn("export", GeometryAdapter.__dict__)
def test_hole_spec_normalizes_wizard_subtypes_without_occ_dependencies(self) -> None:
spec = HoleSpec.from_feature("hole_wizard", {
"diameter_mm": 2, "depth_mm": 6, "end_condition": {"type": "blind", "solidworks_code": 0},
"positions": [{"mm": [1, 2, 3]}],
"countersink": {"diameter_mm": 4, "angle_rad": 1.5707963267948966},
}, wizard=True)
self.assertEqual(spec.positions_mm, ((1.0, 2.0, 3.0),))
self.assertEqual(spec.countersink, (4.0, 1.5707963267948966))
with self.assertRaisesRegex(ValueError, "larger than the main hole"):
HoleSpec.from_feature("hole_wizard", {
"diameter_mm": 2, "depth_mm": 6, "end_condition": {"type": "blind", "solidworks_code": 0}, "positions": [{"mm": [0, 0, 0]}],
"counterbore": {"diameter_mm": 2, "depth_mm": 1},
}, wizard=True)
def test_analytic_contours_create_a_region_with_hole(self) -> None:
cdsl = {
"schema": "cad.cdsl.llm.v1",
"geometry": {"sketches": [{
"id": "sketch", "workplane": _workplane(),
"profile": {"type": "analytic_contours", "contours": [
{"role": "outer", "closed": True, "segments": [
{"type": "line", "start": [0, 0], "end": [10, 0]},
{"type": "line", "start": [10, 0], "end": [10, 10]},
{"type": "line", "start": [10, 10], "end": [0, 10]},
{"type": "line", "start": [0, 10], "end": [0, 0]},
]},
{"role": "inner", "closed": True, "segments": [
{"type": "circle", "center": [5, 5], "radius_mm": 2},
]},
]},
}]},
"features": [],
}
sketch = resolve_all_sketches(cdsl)["geometry"]["sketches"][0]
region = sketch["contour_regions_mm"][0]
self.assertEqual(len(region["outer"]), 4)
self.assertEqual(len(region["holes"]), 1)
self.assertEqual(len(region["holes"][0]), 4)
def test_analytic_contours_normalize_disjoint_inner_roles(self) -> None:
cdsl = {
"schema": "cad.cdsl.llm.v1",
"geometry": {"sketches": [{
"id": "sketch", "workplane": _workplane(),
"profile": {"type": "analytic_contours", "contours": [
{"role": "outer", "closed": True, "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]},
]},
{"role": "inner", "closed": True, "segments": [
{"type": "line", "start": [4, 0], "end": [6, 0]},
{"type": "line", "start": [6, 0], "end": [6, 2]},
{"type": "line", "start": [6, 2], "end": [4, 2]},
{"type": "line", "start": [4, 2], "end": [4, 0]},
]},
]},
}]},
"features": [],
}
sketch = resolve_all_sketches(cdsl)["geometry"]["sketches"][0]
self.assertEqual(len(sketch["contour_regions_mm"]), 2)
self.assertTrue(all(not region["holes"] for region in sketch["contour_regions_mm"]))
def test_analytic_circle_contours_preserve_single_circular_wire_edges(self) -> None:
from cdsl_engine.build123d_adapter import Build123dGeometryAdapter
cdsl = {
"schema": "cad.cdsl.llm.v1",
"geometry": {"sketches": [{
"id": "sketch", "workplane": _workplane(),
"profile": {"type": "analytic_contours", "contours": [
{"role": "outer", "closed": True, "segments": [
{"type": "circle", "center": [-10, 0], "radius_mm": 2},
]},
{"role": "outer", "closed": True, "segments": [
{"type": "circle", "center": [10, 0], "radius_mm": 2},
]},
]},
}]},
"features": [],
}
sketch = resolve_all_sketches(cdsl)["geometry"]["sketches"][0]
faces = Build123dGeometryAdapter().faces_for_sketch(sketch)
self.assertEqual(len(faces), 2)
self.assertTrue(all(len(face.outer_wire().edges()) == 1 for face in faces))
def test_analytic_ellipse_preserves_its_workplane_orientation_and_volume(self) -> None:
from cdsl_engine.runtime import rebuild_cdsl
cdsl = self._base_block()
cdsl["geometry"]["sketches"][0] = {
"id": "ellipse",
"workplane": {"origin_mm": [0, 0, 0], "x_dir": [0, 1, 0], "normal": [0, 0, 1]},
"profile": {"type": "analytic_contours", "contours": [{
"role": "outer", "closed": True, "segments": [{
"type": "ellipse", "center": [0, 0], "major_radius_mm": 5,
"minor_radius_mm": 2, "major_axis": [3 / 5, 4 / 5],
}],
}]},
}
cdsl["features"][0]["sketch_id"] = "ellipse"
cdsl["features"][0]["params"] = {"distance_mm": 4}
sketch = resolve_all_sketches(cdsl)["geometry"]["sketches"][0]
edge = sketch["contour_regions_mm"][0]["outer"][0]
self.assertEqual(edge["major_axis_mm"], [-0.8, 0.6, 0.0])
with tempfile.TemporaryDirectory() as directory:
result = rebuild_cdsl(cdsl, Path(directory) / "ellipse.step")
self.assertEqual(result["solid_count"], 1)
self.assertAlmostEqual(result["volume_mm3"], math.pi * 5 * 2 * 4, places=5)
def test_bspline_profile_preserves_endpoint_tangents(self) -> None:
from cdsl_engine.build123d_adapter import Build123dGeometryAdapter
cdsl = {
"schema": "cad.cdsl.llm.v1",
"geometry": {"sketches": [{
"id": "spline", "workplane": _workplane(),
"profile": {"type": "analytic_contours", "contours": [{
"role": "outer", "closed": True, "segments": [
{"type": "line", "start": [0, 0], "end": [4, 0]},
{"type": "bspline", "start": [4, 0], "end": [0, 0],
"points": [[4, 0], [4, 3], [0, 0]],
"start_tangent": [0, 5], "end_tangent": [-5, 0]},
],
}]},
}]},
"features": [],
}
sketch = resolve_all_sketches(cdsl)["geometry"]["sketches"][0]
spline = next(edge for edge in sketch["contour_regions_mm"][0]["outer"] if edge["type"] == "bspline")
self.assertEqual(spline["start_tangent_mm"], [0.0, 5.0, 0.0])
self.assertEqual(spline["end_tangent_mm"], [-5.0, 0.0, 0.0])
edge = Build123dGeometryAdapter()._wire([spline]).edges()[0]
self.assertAlmostEqual(edge.tangent_at(0).Y, 1.0, places=6)
self.assertAlmostEqual(edge.tangent_at(1).X, -1.0, places=6)
def test_drafted_ellipse_exports_as_a_solid(self) -> None:
from build123d import import_step
from cdsl_engine.runtime import rebuild_cdsl
cdsl = self._base_block()
cdsl["geometry"]["sketches"].append({
"id": "ellipse", "workplane": {"origin_mm": [0, 0, 10], "x_dir": [1, 0, 0], "normal": [0, 0, 1]},
"profile": {"type": "analytic_contours", "contours": [{
"role": "outer", "closed": True, "segments": [{
"type": "ellipse", "center": [0, 0], "major_radius_mm": 3,
"minor_radius_mm": 2, "major_axis": [1, 0],
}],
}]},
})
cdsl["features"].append({
"id": "drafted_ellipse", "atomic_id": "extrude_add_blind", "depends_on": ["base_add"],
"params": {"distance_mm": 4, "draft": {"angle_deg": 1, "pull_direction": False}}, "sketch_id": "ellipse",
})
with tempfile.TemporaryDirectory() as directory:
output = Path(directory) / "drafted-ellipse.step"
result = rebuild_cdsl(cdsl, output)
exported = import_step(str(output))
self.assertEqual(result["solid_count"], 1)
self.assertEqual(len(exported.solids()), 1)
self.assertGreater(float(exported.volume), 1000.0)
def test_boolean_bodies_subtracts_explicit_new_body_sources(self) -> None:
from cdsl_engine.runtime import rebuild_cdsl
cdsl = {
"schema": "cad.cdsl.llm.v1", "schema_version": "1.1.0", "kind": "part", "part_id": "boolean-bodies",
"meta": {"unit": "mm"},
"geometry": {"sketches": [
{"id": "outer", "workplane": _workplane(), "profile": {"type": "circle", "radius_mm": 10}},
{"id": "inner", "workplane": _workplane(), "profile": {"type": "circle", "radius_mm": 4}},
]},
"features": [
{"id": "outer_body", "atomic_id": "extrude_add_blind", "depends_on": [], "sketch_id": "outer", "params": {"distance_mm": 10, "result_mode": "new_body"}},
{"id": "inner_body", "atomic_id": "extrude_add_blind", "depends_on": ["outer_body"], "sketch_id": "inner", "params": {"distance_mm": 10, "result_mode": "new_body"}},
{"id": "cut", "atomic_id": "boolean_bodies", "depends_on": ["outer_body", "inner_body"], "params": {"operation": "subtract", "target_feature_ids": ["outer_body"], "tool_feature_ids": ["inner_body"], "keep_tools": False}},
],
}
with tempfile.TemporaryDirectory() as tmp:
rebuilt = rebuild_cdsl(cdsl, Path(tmp) / "boolean.step")
self.assertEqual(rebuilt["solid_count"], 1)
self.assertAlmostEqual(rebuilt["volume_mm3"], math.pi * (10 ** 2 - 4 ** 2) * 10)
self.assertEqual([item["feature_id"] for item in rebuilt["feature_results"]], ["outer_body", "inner_body", "cut"])
def test_deferred_reference_is_currently_executable_without_a_sketch(self) -> None:
cdsl = {
"schema": "cad.cdsl.llm.v1",
"geometry": {"sketches": [{"id": "s", "workplane": _workplane(), "profile": {"type": "circle", "radius_mm": 1}}]},
"features": [{
"id": "plane", "atomic_id": "reference_plane", "depends_on": [],
"execution_status": "deferred", "params": {"plane": _workplane()},
}],
}
analysis = CapabilityAnalyzer(atomic_ids={"reference_plane"}, profile_types=SHAPE_GENERATORS).analyze(cdsl)
self.assertFalse(analysis.runtime_eligible)
self.assertEqual(analysis.feature_results[0].resolved_status, "executable")
self.assertEqual(analysis.document_blockers[0].code, "no_solid_feature")
def test_unused_invalid_profile_does_not_block_runtime_preflight(self) -> None:
from cdsl_engine.runtime import analyze_cdsl
cdsl = self._base_block()
cdsl["geometry"]["sketches"].append({
"id": "abandoned", "workplane": _workplane(),
"profile": {"type": "analytic_contours", "contours": [{
"role": "outer", "closed": True,
"segments": [{"type": "line", "start": [0, 0], "end": [1, 0]}],
}]},
})
analysis = analyze_cdsl(cdsl)
self.assertTrue(analysis.runtime_eligible)
def test_used_invalid_profile_is_a_feature_level_blocker(self) -> None:
from cdsl_engine.runtime import analyze_cdsl
cdsl = self._base_block()
cdsl["geometry"]["sketches"].append({
"id": "broken", "workplane": _workplane(),
"profile": {"type": "analytic_contours", "contours": [{
"role": "outer", "closed": True,
"segments": [{"type": "line", "start": [0, 0], "end": [1, 0]}],
}]},
})
cdsl["features"].append({
"id": "broken_cut", "atomic_id": "extrude_cut_blind", "depends_on": ["base_add"],
"params": {"distance_mm": 1}, "sketch_id": "broken",
})
analysis = analyze_cdsl(cdsl)
broken = next(item for item in analysis.feature_results if item.feature_id == "broken_cut")
self.assertEqual(broken.resolved_status, "blocked")
self.assertIn("profile_resolution_failed", [item.code for item in broken.blockers])
def test_used_empty_analytic_profile_is_a_feature_level_blocker(self) -> None:
from cdsl_engine.runtime import analyze_cdsl
cdsl = self._base_block()
cdsl["geometry"]["sketches"].append({
"id": "construction_only", "workplane": _workplane(),
"profile": {"type": "analytic_contours", "contours": []},
})
cdsl["features"].append({
"id": "empty_cut", "atomic_id": "extrude_cut_blind", "depends_on": ["base_add"],
"params": {"distance_mm": 1}, "sketch_id": "construction_only",
})
analysis = analyze_cdsl(cdsl)
empty = next(item for item in analysis.feature_results if item.feature_id == "empty_cut")
self.assertIn("profile_no_closed_region", [item.code for item in empty.blockers])
def test_selector_resolver_refuses_equal_candidates(self) -> None:
registry = TopologyRegistry()
plane = PlaneSpec.from_mapping(_workplane())
registry.register(TopologyRecord("first", "plane", "f1", geometry=plane.as_dict(), value=plane))
registry.register(TopologyRecord("second", "plane", "f2", geometry=plane.as_dict(), value=plane))
resolution = registry.resolve({"kind": "plane", "geometry": plane.as_dict()})
self.assertEqual(resolution.status, "ambiguous")
self.assertEqual(resolution.diagnostic.code, "selector_ambiguous")
def test_selector_resolver_prefers_exact_runtime_stable_id(self) -> None:
registry = TopologyRegistry()
geometry = {"curve_type": "circle", "center_mm": [0, 0, 0]}
registry.register(TopologyRecord("body:b:edge:18", "edge", "boss", "body:b", geometry))
registry.register(TopologyRecord("body:b:edge:20", "edge", "boss", "body:b", geometry))
resolution = registry.resolve({
"kind": "edge",
"stable_id": "body:b:edge:18",
"owner_feature_id": "boss",
"snapshot_id": "cad_test/rev_001",
"geometry": geometry,
}, active_body_id="body:b")
self.assertEqual(resolution.status, "resolved")
self.assertEqual(resolution.record.record_id, "body:b:edge:18")
def test_snapshot_stable_id_rejects_geometry_that_changed(self) -> None:
registry = TopologyRegistry()
registry.register(TopologyRecord(
"body:b:edge:18", "edge", "boss", "body:b",
{"curve_type": "circle", "center_mm": [0, 0, 0]},
))
resolution = registry.resolve({
"kind": "edge", "stable_id": "body:b:edge:18", "owner_feature_id": "boss",
"snapshot_id": "cad_test/rev_001",
"geometry": {"curve_type": "circle", "center_mm": [1, 0, 0]},
}, active_body_id="body:b")
self.assertEqual(resolution.status, "not_found")
self.assertEqual(resolution.diagnostic.code, "selector_geometry_mismatch")
def test_selector_resolver_normalizes_legacy_solidworks_plane_evidence(self) -> None:
registry = TopologyRegistry()
registry.register(TopologyRecord(
"face", "face", "f1", "body:f1",
geometry={
"surface_type": "plane", "plane_normal": [1, 0, 0],
"plane_offset_mm": 12.0, "bbox_mm": [12, -2, -3, 12, 2, 3], "area_mm2": 24,
},
))
resolution = registry.resolve({
"kind": "face",
"geometry": {
"surface": {"type": "plane", "parameters": [-1, 0, 0, -0.012, 0, 0]},
"box": [0.012, -0.002, -0.003, 0.012, 0.002, 0.003], "area": 0.000024,
},
})
self.assertEqual(resolution.status, "resolved")
self.assertEqual(resolution.record.record_id, "face")
def test_owner_selector_survives_a_later_body_snapshot_when_geometry_is_unchanged(self) -> None:
registry = TopologyRegistry()
geometry = {
"bbox_mm": [0, 0, 0, 0, 0, 10],
"center_mm": [0, 0, 5],
"length_mm": 10.0,
"curve_type": "line",
"start_mm": [0, 0, 0],
"end_mm": [0, 0, 10],
}
registry.replace_body_topology("base_add", "body:base_add", [
TopologyRecord("body:base_add:edge:0", "edge", "base_add", "body:base_add", geometry, "old-edge"),
])
registry.replace_body_topology("later_cut", "body:later_cut", [
TopologyRecord("body:later_cut:edge:3", "edge", "later_cut", "body:later_cut", geometry, "current-edge"),
])
resolution = registry.resolve(
{"kind": "edge", "owner_feature_id": "base_add", "geometry": geometry},
active_body_id="body:later_cut",
)
self.assertEqual(resolution.status, "resolved")
self.assertEqual(resolution.record.value, "current-edge")
self.assertEqual(resolution.record.feature_id, "later_cut")
self.assertEqual(resolution.record.owner_feature_ids, ("base_add",))
def test_ambiguous_predecessors_do_not_invent_topology_ownership(self) -> None:
registry = TopologyRegistry()
geometry = {"center_mm": [1, 2, 3]}
registry.replace_body_topology("base_add", "body:base_add", [
TopologyRecord("body:base_add:vertex:0", "vertex", "base_add", "body:base_add", geometry),
TopologyRecord("body:base_add:vertex:1", "vertex", "base_add", "body:base_add", geometry),
])
registry.replace_body_topology("later_cut", "body:later_cut", [
TopologyRecord("body:later_cut:vertex:0", "vertex", "later_cut", "body:later_cut", geometry),
])
resolution = registry.resolve(
{"kind": "vertex", "owner_feature_id": "base_add", "geometry": geometry},
active_body_id="body:later_cut",
)
self.assertEqual(resolution.status, "not_found")
def test_changed_adjacency_prevents_owner_provenance_transfer(self) -> None:
registry = TopologyRegistry()
unchanged_geometry = {
"bbox_mm": [0, 0, 0, 1, 1, 0], "center_mm": [0.5, 0.5, 0],
"normal": [0, 0, 1], "area_mm2": 1, "surface_type": "plane",
}
registry.replace_body_topology("base_add", "body:base_add", [
TopologyRecord(
"body:base_add:face:0", "face", "base_add", "body:base_add",
{**unchanged_geometry, "adjacency_signature": ["line:1.000000:2"]},
),
])
registry.replace_body_topology("later_cut", "body:later_cut", [
TopologyRecord(
"body:later_cut:face:0", "face", "later_cut", "body:later_cut",
{**unchanged_geometry, "adjacency_signature": ["line:1.000000:2", "circle:1.000000:1"]},
),
])
resolution = registry.resolve(
{"kind": "face", "owner_feature_id": "base_add", "geometry": unchanged_geometry},
active_body_id="body:later_cut",
)
self.assertEqual(resolution.status, "not_found")
def test_batch_analysis_writes_one_report_per_input(self) -> None:
document = {
"schema": "cad.cdsl.llm.v1", "schema_version": "1.1.0", "kind": "part", "part_id": "batch-part",
"meta": {"unit": "mm"},
"geometry": {"sketches": [{"id": "s", "workplane": _workplane(), "profile": {"type": "circle", "radius_mm": 1}}]},
"features": [{
"id": "f", "atomic_id": "reference_plane", "depends_on": [], "execution_status": "deferred",
"params": {"plane": _workplane()},
}],
}
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
input_path = root / "input"
input_path.mkdir()
(input_path / "batch-part.cdsl.json").write_text(json.dumps(document), encoding="utf-8")
manifest = batch_analyze(input_path, root / "out", atomic_ids=frozenset({"reference_plane"}))
report = json.loads((root / "out" / "parts" / "batch-part.report.json").read_text(encoding="utf-8"))
self.assertEqual(manifest["part_count"], 1)
self.assertTrue(report["semantic_valid"])
self.assertFalse(report["runtime_eligible"])
self.assertEqual(report["first_blocker"]["code"], "no_solid_feature")
def test_batch_analysis_part_filter_is_exact_and_rejects_unknown_ids(self) -> None:
document = {
"schema": "cad.cdsl.llm.v1", "schema_version": "1.1.0", "kind": "part",
"meta": {"unit": "mm"}, "geometry": {"sketches": []}, "features": [],
}
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
input_path = root / "input"
input_path.mkdir()
for part_id in ("first", "second"):
(input_path / f"{part_id}.cdsl.json").write_text(
json.dumps({**document, "part_id": part_id}), encoding="utf-8",
)
manifest = batch_analyze(
input_path, root / "out", atomic_ids=frozenset({"reference_plane"}), part_ids=["second"],
)
self.assertEqual(manifest["part_count"], 1)
self.assertEqual(manifest["results"], [{"part_id": "second", "report": "parts/second.report.json"}])
with self.assertRaisesRegex(ValueError, "do not exist"):
batch_analyze(input_path, root / "bad", part_ids=["missing"])
def test_batch_resume_migrates_derived_failure_category_without_rebuilding(self) -> None:
document = {
"schema": "cad.cdsl.llm.v1", "schema_version": "1.1.0", "kind": "part", "part_id": "cached",
"meta": {"unit": "mm"}, "geometry": {"sketches": []}, "features": [],
}
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
source = root / "input"
source.mkdir()
(source / "cached.cdsl.json").write_text(json.dumps(document), encoding="utf-8")
report_path = root / "out" / "parts" / "cached.report.json"
report_path.parent.mkdir(parents=True)
report_path.write_text(json.dumps({
"part_id": "cached", "semantic_valid": True, "runtime_eligible": False, "compiled": False,
"build_attempted": False, "built": False, "geometry_verified": False, "feature_results": [],
"first_blocker": {"code": "missing_host_face"},
}), encoding="utf-8")
manifest = batch_analyze(source, root / "out")
report = json.loads(report_path.read_text(encoding="utf-8"))
self.assertEqual(manifest["failure_category_counts"], {"input_incomplete": 1})
self.assertEqual(report["failure_category"], "input_incomplete")
def test_full_export_batch_has_a_machine_readable_report_for_every_input(self) -> None:
"""Keep the shipped CDSL corpus on the capability-report path.
STEP construction is intentionally left to the resumable build job;
this CI-sized pass proves that every current export gets a semantic
result and a first blocker rather than being silently skipped.
"""
source = ROOT / "json_to_cdsl" / "output"
selectors = [path.name.removesuffix(".cdsl.json") for path in sorted(source.glob("*.cdsl.json"))]
corpus = _corpus_manifest(source)
input_count = len(selectors)
self.assertGreater(input_count, 0)
self.assertEqual(input_count, corpus["document_count"])
self.assertEqual(_selector_digest(selectors), corpus["document_stems_sha256"])
with tempfile.TemporaryDirectory() as directory:
out = Path(directory) / "baseline"
manifest = batch_analyze(source, out)
reports = [json.loads(path.read_text(encoding="utf-8")) for path in (out / "parts").glob("*.report.json")]
self.assertEqual(manifest["part_count"], input_count)
self.assertEqual(manifest["completed_count"], input_count)
self.assertTrue(manifest["complete"])
self.assertEqual(manifest["semantic_valid_count"], input_count)
self.assertEqual(len(reports), input_count)
self.assertTrue(all(report["runtime_eligible"] or report.get("first_blocker") for report in reports))
def test_p3_static_pool_is_explicit_and_reports_missing_capture_separately(self) -> None:
from cdsl_engine.phase_pools import select_p3_static_pool
from cdsl_engine.runtime import analyze_cdsl
source = ROOT / "json_to_cdsl" / "output"
part_ids = select_p3_static_pool(source)
expected = _corpus_manifest(source)["phase_pools"]["p3"]
self.assertEqual(len(part_ids), expected["selector_count"])
self.assertEqual(_selector_digest(part_ids), expected["selectors_sha256"])
# Static pool membership is input-based. Runtime preflight remains a
# separate signal and must retain captured blockers for its members.
expected_blockers = {
"027784": {"missing_revolve_axis"},
"104237": {"missing_extent_reference"},
"239358": {"missing_extent_reference", "dependency_unavailable"},
"241720": {"missing_extent_reference"},
}
for part_id, blockers in expected_blockers.items():
self.assertIn(part_id, part_ids)
cdsl = json.loads((source / f"{part_id}.cdsl.json").read_text(encoding="utf-8"))
result = analyze_cdsl(cdsl)
actual = {
blocker.code
for feature in result.feature_results
for blocker in feature.blockers
}
self.assertTrue(blockers <= actual)
def test_p3_phase_pool_can_be_passed_to_batch_rebuild(self) -> None:
from cdsl_engine.phase_pools import select_p3_static_pool
source = ROOT / "json_to_cdsl" / "output"
pool = select_p3_static_pool(source)
self.assertIn("Bearing-Spacer-1", pool)
with tempfile.TemporaryDirectory() as directory:
manifest = batch_analyze(source, Path(directory) / "p3", part_ids=pool, max_parts=1)
self.assertEqual(manifest["part_count"], len(pool))
self.assertEqual(manifest["completed_count"], 1)
self.assertFalse(manifest["complete"])
def test_phase_pool_membership_is_regressed_for_p4_and_p6(self) -> None:
from cdsl_engine.phase_pools import select_static_phase_pool
source = ROOT / "json_to_cdsl" / "output"
expected_pools = _corpus_manifest(source)["phase_pools"]
for phase in ("p4", "p6"):
selectors = select_static_phase_pool(source, phase)
expected = expected_pools[phase]
self.assertEqual(len(selectors), expected["selector_count"])
self.assertEqual(_selector_digest(selectors), expected["selectors_sha256"])
with self.assertRaisesRegex(ValueError, "Unknown CDSL runtime phase"):
select_static_phase_pool(source, "p5")
def test_construction_bspline_is_auditable_but_not_an_executable_contour(self) -> None:
from cdsl_engine.semantic_validation import validate_semantic_cdsl
cdsl = self._base_block()
cdsl["features"][0]["execution_status"] = "supported"
profile = {
"type": "analytic_contours",
"contours": [{
"role": "outer", "closed": True,
"segments": [
{"type": "line", "start": [-5, -5], "end": [5, -5]},
{"type": "line", "start": [5, -5], "end": [5, 5]},
{"type": "line", "start": [5, 5], "end": [-5, 5]},
{"type": "line", "start": [-5, 5], "end": [-5, -5]},
],
}],
"construction": [{
"type": "bspline", "degree": 2,
"control_points": [[-5, 0], [0, 2], [5, 0]],
"knots": [0, 0, 0, 1, 1, 1], "periodic": False,
}],
}
cdsl["geometry"]["sketches"][0]["profile"] = profile
validate_semantic_cdsl(cdsl)
resolved = resolve_all_sketches(cdsl)
entities = resolved["geometry"]["sketches"][0]["entities"]
self.assertNotIn("bspline", {entity["type"] for entity in entities})
profile["contours"][0]["segments"][0] = profile["construction"][0]
with self.assertRaisesRegex(ValueError, "CDSL schema violation"):
validate_semantic_cdsl(cdsl)
def test_nested_pattern_source_is_replayable_after_its_first_execution(self) -> None:
from cdsl_engine.runtime import rebuild_cdsl
cdsl = self._base_block()
cdsl["features"].extend([
{
"id": "first_pattern", "atomic_id": "pattern_linear", "depends_on": ["base_add"],
"params": {"source_feature_ids": ["base_add"], "direction_1": [1, 0, 0],
"spacing_1_mm": 20, "pattern_count_1": 2},
},
{
"id": "nested_pattern", "atomic_id": "pattern_linear", "depends_on": ["first_pattern"],
"params": {"source_feature_ids": ["first_pattern"], "direction_1": [0, 1, 0],
"spacing_1_mm": 20, "pattern_count_1": 2},
},
])
with tempfile.TemporaryDirectory() as directory:
result = rebuild_cdsl(cdsl, Path(directory) / "nested.step")
self.assertEqual(result["engine"], "cdsl_session_runtime")
def test_pattern_source_without_body_definition_is_blocked_in_preflight(self) -> None:
from cdsl_engine.runtime import analyze_cdsl
cdsl = self._base_block()
cdsl["features"].insert(0, {
"id": "context", "atomic_id": "reference_plane", "depends_on": [],
"params": {"plane": _workplane()},
})
cdsl["features"].append({
"id": "pattern", "atomic_id": "pattern_linear", "depends_on": ["base_add", "context"],
"params": {"source_feature_ids": ["context"], "direction_1": [1, 0, 0],
"spacing_1_mm": 20, "pattern_count_1": 2},
})
analysis = analyze_cdsl(cdsl)
pattern = next(result for result in analysis.feature_results if result.feature_id == "pattern")
self.assertEqual(pattern.resolved_status, "unsupported")
self.assertIn("unsupported_pattern_source", [blocker.code for blocker in pattern.blockers])
def test_truth_comparison_keeps_frame_mismatch_distinct_from_verified_geometry(self) -> None:
truth = {"bounding_box_mm": [-10, -200, -10, 10, 200, 10], "solid_count": 1}
common = {
"truth": truth,
"volume_relative_error": 1e-8,
"area_relative_error": 1e-8,
"solid_count_matches": True,
}
self.assertEqual(
_verification_classification(
actual_box=[-10, 990, 0, 10, 1010, 400], box_delta=1190, **common,
),
"coordinate_frame_mismatch_candidate",
)
self.assertEqual(
_verification_classification(
actual_box=[-10, -200, -10, 10, 200, 10], box_delta=0, **common,
),
"verified",
)
self.assertEqual(
_verification_classification(
actual_box=[-10, 990, 0, 10, 1010, 400], box_delta=1190,
truth=truth, volume_relative_error=0.1, area_relative_error=1e-8, solid_count_matches=True,
),
"geometry_mismatch",
)
def test_batch_failure_categories_preserve_input_selector_capability_and_occ_boundaries(self) -> None:
self.assertEqual(_failure_category({"geometry_verified": True}), "geometry_verified")
self.assertEqual(_failure_category({"runtime_eligible": True, "build_attempted": False}), "runtime_eligible_not_built")
self.assertEqual(_failure_category({"built": True, "numeric_comparison": {"classification": "coordinate_frame_mismatch_candidate"}}), "coordinate_frame_mismatch_candidate")
self.assertEqual(_failure_category({"built": False, "first_blocker": {"code": "missing_host_face"}}), "input_incomplete")
self.assertEqual(_failure_category({"built": False, "first_blocker": {"code": "extent_target_not_reached"}}), "input_incomplete")
self.assertEqual(_failure_category({"built": False, "first_blocker": {"code": "selector_ambiguous"}}), "selector_resolution")
self.assertEqual(_failure_category({"built": False, "first_blocker": {"code": "unsupported_hole_subtype"}}), "unsupported_capability")
self.assertEqual(_failure_category({"built": False, "first_blocker": {"code": "build_timeout"}}), "occ_execution_failure")
def test_mirror_pattern_replays_a_selector_free_cut(self) -> None:
from cdsl_engine.runtime import rebuild_cdsl
cdsl = {
"schema": "cad.cdsl.llm.v1", "schema_version": "1.1.0", "kind": "part", "part_id": "mirror-test",
"meta": {"unit": "mm"},
"geometry": {"sketches": [
{"id": "base", "workplane": _workplane(), "profile": _rectangle([-5, -5], [5, 5])},
{"id": "cut", "workplane": _workplane(), "profile": {"type": "circle", "center": [2, 0], "radius_mm": 1}},
]},
"features": [
{"id": "ref", "atomic_id": "reference_plane", "depends_on": [], "params": {"plane": {"origin_mm": [0, 0, 0], "x_dir": [0, 1, 0], "normal": [1, 0, 0]}}},
{"id": "base_add", "atomic_id": "extrude_add_blind", "depends_on": ["ref"], "params": {"distance_mm": 2}, "sketch_id": "base"},
{"id": "cut_1", "atomic_id": "extrude_cut_blind", "depends_on": ["base_add"], "params": {"distance_mm": 2}, "sketch_id": "cut"},
{
"id": "mirror", "atomic_id": "pattern_mirror", "depends_on": ["cut_1", "ref"],
"params": {"source_feature_ids": ["cut_1"], "mirror_plane": {"kind": "plane", "stable_id": "source", "source": "solidworks", "confidence": 1, "owner_feature_id": "ref"}},
"selectors": [{"kind": "plane", "stable_id": "source", "source": "solidworks", "confidence": 1, "owner_feature_id": "ref"}],
},
],
}
with tempfile.TemporaryDirectory() as directory:
result = rebuild_cdsl(cdsl, Path(directory) / "mirror.step")
self.assertAlmostEqual(result["volume_mm3"], 200 - 4 * 3.141592653589793, places=5)
self.assertIn("mirror", [item["feature_id"] for item in result["feature_results"]])
def test_circular_pattern_fuses_face_sharing_new_body_instances(self) -> None:
from cdsl_engine.runtime import rebuild_cdsl
cdsl = {
"schema": "cad.cdsl.llm.v1", "schema_version": "1.1.0", "kind": "part", "part_id": "circular-fuse",
"meta": {"unit": "mm"},
"geometry": {"sketches": [{
"id": "wedge", "workplane": _workplane(),
"profile": {"type": "polygon", "vertices": [[0, 0], [10, 0], [0, 10]]},
}]},
"features": [
{"id": "wedge_add", "atomic_id": "extrude_add_blind", "depends_on": [],
"params": {"distance_mm": 10, "result_mode": "new_body"}, "sketch_id": "wedge"},
{"id": "wedge_pattern", "atomic_id": "pattern_circular", "depends_on": ["wedge_add"], "params": {
"source_feature_ids": ["wedge_add"], "axis": {"origin_mm": [0, 0, 0], "direction": [0, 0, 1]},
"pattern_count": 4, "sweep_angle_deg": 360,
}},
],
}
with tempfile.TemporaryDirectory() as directory:
result = rebuild_cdsl(cdsl, Path(directory) / "circular-fuse.step")
self.assertEqual(result["solid_count"], 1)
self.assertAlmostEqual(result["volume_mm3"], 2000.0, places=5)
def test_circular_pattern_skips_explicitly_deleted_copy_instances(self) -> None:
from cdsl_engine.runtime import rebuild_cdsl
cdsl = {
"schema": "cad.cdsl.llm.v1", "schema_version": "1.1.0", "kind": "part", "part_id": "circular-delete-copy",
"meta": {"unit": "mm"},
"geometry": {"sketches": [{
"id": "wedge", "workplane": _workplane(),
"profile": {"type": "polygon", "vertices": [[0, 0], [10, 0], [0, 10]]},
}]},
"features": [
{"id": "wedge_add", "atomic_id": "extrude_add_blind", "depends_on": [],
"params": {"distance_mm": 10, "result_mode": "new_body"}, "sketch_id": "wedge"},
{"id": "wedge_pattern", "atomic_id": "pattern_circular", "depends_on": ["wedge_add"], "params": {
"source_feature_ids": ["wedge_add"], "axis": {"origin_mm": [0, 0, 0], "direction": [0, 0, 1]},
"pattern_count": 4, "sweep_angle_deg": 360, "excluded_instance_indices": [2],
}},
],
}
with tempfile.TemporaryDirectory() as directory:
result = rebuild_cdsl(cdsl, Path(directory) / "circular-delete-copy.step")
self.assertAlmostEqual(result["volume_mm3"], 1500.0, places=5)
def test_mirror_pattern_can_union_the_active_body(self) -> None:
from cdsl_engine.runtime import rebuild_cdsl
cdsl = {
"schema": "cad.cdsl.llm.v1", "schema_version": "1.1.0", "kind": "part", "part_id": "mirror-active-body",
"meta": {"unit": "mm"},
"geometry": {"sketches": [
{"id": "base", "workplane": _workplane(), "profile": _rectangle([-5, -5], [5, 5])},
{"id": "half_cut", "workplane": _workplane(), "profile": _rectangle([-5, 0], [5, 5])},
{"id": "hole", "workplane": _workplane(), "profile": {"type": "circle", "center": [0, -2], "radius_mm": 1}},
]},
"features": [
{"id": "mirror_plane", "atomic_id": "reference_plane", "depends_on": [], "params": {"plane": {"origin_mm": [0, 0, 0], "x_dir": [1, 0, 0], "normal": [0, 1, 0]}}, "execution_status": "supported"},
{"id": "base_add", "atomic_id": "extrude_add_blind", "depends_on": ["mirror_plane"], "params": {"distance_mm": 2}, "sketch_id": "base", "execution_status": "supported"},
{"id": "half_remove", "atomic_id": "extrude_cut_blind", "depends_on": ["base_add"], "params": {"distance_mm": 2}, "sketch_id": "half_cut", "execution_status": "supported"},
{"id": "hole_remove", "atomic_id": "extrude_cut_blind", "depends_on": ["half_remove"], "params": {"distance_mm": 2}, "sketch_id": "hole", "execution_status": "supported"},
{
"id": "mirror", "atomic_id": "pattern_mirror", "depends_on": ["hole_remove", "mirror_plane"],
"params": {"source_feature_ids": ["base_add"], "mirror_current_body": True, "mirror_plane": {"kind": "plane", "owner_feature_id": "mirror_plane"}},
"selectors": [{"kind": "plane", "owner_feature_id": "mirror_plane"}], "execution_status": "supported",
},
],
}
with tempfile.TemporaryDirectory() as directory:
result = rebuild_cdsl(cdsl, Path(directory) / "mirror-active-body.step")
self.assertAlmostEqual(result["volume_mm3"], 200 - 4 * 3.141592653589793, places=5)
self.assertEqual(result["solid_count"], 1)
def test_mirrored_local_circle_preserves_its_reflected_world_position(self) -> None:
from cdsl_engine.build123d_adapter import Build123dGeometryAdapter
from cdsl_engine.runtime import _mirrored_sketch
from cdsl_engine.sketch_solver import resolve_all_sketches
cdsl = {
"schema": "cad.cdsl.llm.v1",
"geometry": {"sketches": [{
"id": "circles", "workplane": _workplane(),
"profile": {"type": "circle", "center": [2, 3], "radius_mm": 1},
}]},
"features": [],
}
sketch = resolve_all_sketches(cdsl)["geometry"]["sketches"][0]
mirror_plane = PlaneSpec.from_mapping({
"origin_mm": [0, 0, 0], "x_dir": [0, 1, 0], "normal": [1, 0, 0],
})
mirrored = _mirrored_sketch(sketch, mirror_plane)
face = Build123dGeometryAdapter().faces_for_sketch(mirrored)[0]
center = face.center()
self.assertAlmostEqual(center.X, -2.0)
self.assertAlmostEqual(center.Y, 3.0)
self.assertAlmostEqual(center.Z, 0.0)
def test_circle_profile_retains_one_topological_circle_edge(self) -> None:
from cdsl_engine.build123d_adapter import Build123dGeometryAdapter
cdsl = {
"schema": "cad.cdsl.llm.v1",
"geometry": {"sketches": [{
"id": "circle", "workplane": _workplane(),
"profile": {"type": "circle", "center": [2, 3], "radius_mm": 1},
}]},
"features": [],
}
sketch = resolve_all_sketches(cdsl)["geometry"]["sketches"][0]
self.assertNotIn("contour_edges_mm", sketch)
face = Build123dGeometryAdapter().faces_for_sketch(sketch)[0]
self.assertEqual(len(face.outer_wire().edges()), 1)
def test_new_body_mode_preserves_an_overlapping_result_body(self) -> None:
from cdsl_engine.runtime import rebuild_cdsl
cdsl = self._base_block()
cdsl["geometry"]["sketches"].append({
"id": "overlap", "workplane": _workplane(),
"profile": _rectangle([-2, -5], [8, 5]),
})
cdsl["features"].append({
"id": "second_body", "atomic_id": "extrude_add_blind", "depends_on": ["base_add"],
"params": {"distance_mm": 10, "result_mode": "new_body"}, "sketch_id": "overlap",
})
with tempfile.TemporaryDirectory() as directory:
result = rebuild_cdsl(cdsl, Path(directory) / "two-bodies.step")
self.assertEqual(result["solid_count"], 2)
def test_sweep_add_builds_a_solid_from_a_closed_profile_and_bspline_path(self) -> None:
from cdsl_engine.runtime import rebuild_cdsl
profile_plane = {"origin_mm": [0, 0, 0], "x_dir": [1, 0, 0], "normal": [0, 1, 0]}
cdsl = {
"schema": "cad.cdsl.llm.v1", "schema_version": "1.1.0", "kind": "part", "part_id": "sweep-add",
"meta": {"unit": "mm"},
"geometry": {"sketches": [{
"id": "profile", "workplane": profile_plane,
"profile": {"type": "circle", "radius_mm": 2},
}]},
"features": [{
"id": "sweep", "atomic_id": "sweep_add", "depends_on": [], "sketch_id": "profile",
"params": {"path": {
"workplane": _workplane(),
"segment": {
"type": "bspline", "start": [0, 0], "end": [0, 20],
"points": [[0, 0], [0, 10], [0, 20]],
"start_tangent": [0, 10], "end_tangent": [0, 10],
},
}},
}],
}
with tempfile.TemporaryDirectory() as directory:
result = rebuild_cdsl(cdsl, Path(directory) / "sweep.step")
self.assertEqual(result["solid_count"], 1)
self.assertAlmostEqual(result["volume_mm3"], 80 * math.pi, delta=1e-4)
def test_circular_pattern_rotates_a_sweep_profile_and_path(self) -> None:
from cdsl_engine.runtime import rebuild_cdsl
cdsl = {
"schema": "cad.cdsl.llm.v1", "schema_version": "1.1.0", "kind": "part", "part_id": "sweep-pattern",
"meta": {"unit": "mm"},
"geometry": {"sketches": [{
"id": "profile", "workplane": {"origin_mm": [10, 0, 0], "x_dir": [1, 0, 0], "normal": [0, 0, -1]},
"profile": {"type": "circle", "radius_mm": 1},
}]},
"features": [
{"id": "sweep", "atomic_id": "sweep_add", "depends_on": [], "sketch_id": "profile", "params": {"path": {
"workplane": {"origin_mm": [0, 0, 0], "x_dir": [1, 0, 0], "normal": [0, 1, 0]},
"segment": {"type": "line", "start": [10, 0], "end": [10, 10]},
}}},
{"id": "pattern", "atomic_id": "pattern_circular", "depends_on": ["sweep"], "params": {
"source_feature_ids": ["sweep"], "axis": {"origin_mm": [0, 0, 0], "direction": [0, 0, 1]},
"pattern_count": 3, "sweep_angle_deg": 360,
}},
],
}
with tempfile.TemporaryDirectory() as directory:
result = rebuild_cdsl(cdsl, Path(directory) / "sweep-pattern.step")
self.assertEqual(result["solid_count"], 3)
self.assertAlmostEqual(result["volume_mm3"], 30 * math.pi, delta=1e-4)
def test_shell_removes_a_selected_cap_and_offsets_inward(self) -> None:
from cdsl_engine.runtime import rebuild_cdsl
base = self._base_block()
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
baseline = rebuild_cdsl(base, root / "baseline.step")
top = next(
item for item in baseline["topology_records"]
if item["kind"] == "face"
and item["feature_id"] == "base_add"
and item["geometry"].get("surface_type") == "plane"
and item["geometry"].get("normal", [0, 0, 0])[2] > 0.9
)
shelled = deepcopy(base)
shelled["features"].append({
"id": "shell", "atomic_id": "shell", "depends_on": ["base_add"],
"params": {"thickness_mm": 1, "inward": True},
"selectors": [{
"kind": "face", "stable_id": top["record_id"], "snapshot_id": top["record_id"],
"source": "runtime_snapshot", "confidence": 1, "owner_feature_id": "base_add",
"geometry": top["geometry"],
}],
})
result = rebuild_cdsl(shelled, root / "shell.step")
self.assertEqual(result["solid_count"], 1)
self.assertAlmostEqual(result["volume_mm3"], 424.0, places=5)
def test_fillet_and_hole_wizard_use_resolved_face_edge_selectors(self) -> None:
from cdsl_engine.runtime import rebuild_cdsl
base = self._base_block()
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
baseline = rebuild_cdsl(base, root / "baseline.step")
edge = next(item for item in baseline["topology_records"] if item["kind"] == "edge")
top_face = next(
item for item in baseline["topology_records"]
if item["kind"] == "face" and item["geometry"]["surface_type"] == "plane" and item["geometry"]["normal"][2] > 0.9
)
with_fillet = deepcopy(base)
with_fillet["features"].append({
"id": "fillet", "atomic_id": "fillet", "depends_on": ["base_add"], "params": {"radius_mm": 1},
"selectors": [{"kind": "edge", "stable_id": "edge", "source": "solidworks", "confidence": 1, "owner_feature_id": "base_add", "geometry": edge["geometry"]}],
})
filleted = rebuild_cdsl(with_fillet, root / "fillet.step")
with_hole = deepcopy(base)
with_hole["features"].append({
"id": "hole", "atomic_id": "hole_wizard", "depends_on": ["base_add"],
"params": {
"hole_type": "plain", "diameter_mm": 2, "depth_mm": 5,
"end_condition": {"type": "blind", "solidworks_code": 0}, "positions": [{"mm": [0, 0, 10]}],
"host_face": {"kind": "face", "stable_id": "top", "source": "inferred_from_step", "confidence": 1, "geometry": top_face["geometry"]},
},
"selectors": [{"kind": "face", "stable_id": "top", "source": "inferred_from_step", "confidence": 1, "geometry": top_face["geometry"]}],
})
holed = rebuild_cdsl(with_hole, root / "hole.step")
self.assertLess(filleted["volume_mm3"], baseline["volume_mm3"])
self.assertAlmostEqual(holed["volume_mm3"], 1000 - 5 * 3.141592653589793, places=5)
def test_hole_frame_uses_local_coordinates(self) -> None:
from cdsl_engine.runtime import rebuild_cdsl
base = self._base_block()
base["features"].append({
"id": "hole", "atomic_id": "hole_blind", "depends_on": ["base_add"],
"sketch_id": "base",
"params": {
"diameter_mm": 2, "depth_mm": 5, "positions": [{"mm": [0, 0, 0]}],
"host_face": {"frame": {"origin_mm": [0, 0, 10], "x_dir": [1, 0, 0], "y_dir": [0, 1, 0], "normal": [0, 0, 1]}},
},
})
with tempfile.TemporaryDirectory() as directory:
result = rebuild_cdsl(base, Path(directory) / "local-hole.step")
self.assertAlmostEqual(result["volume_mm3"], 1000 - 5 * 3.141592653589793, places=5)
def test_counterbore_and_countersink_holes_execute_with_explicit_host_frames(self) -> None:
"""Keep both legacy hole contracts covered by an actual kernel rebuild."""
from cdsl_engine.runtime import rebuild_cdsl
host_frame = {
"origin_mm": [0, 0, 10], "x_dir": [1, 0, 0],
"y_dir": [0, 1, 0], "normal": [0, 0, 1],
}
for atomic_id, details in (
("hole_counterbore", {"counterbore_diameter_mm": 4, "counterbore_depth_mm": 2}),
("hole_countersink", {"countersink_diameter_mm": 4, "countersink_angle_rad": 1.5707963267948966}),
):
with self.subTest(atomic_id=atomic_id):
cdsl = self._base_block()
cdsl["features"].append({
"id": atomic_id, "atomic_id": atomic_id, "depends_on": ["base_add"],
"params": {
"diameter_mm": 2, "depth_mm": 5, "positions": [{"mm": [0, 0, 0]}],
"host_face": {"frame": host_frame}, **details,
},
})
with tempfile.TemporaryDirectory() as directory:
result = rebuild_cdsl(cdsl, Path(directory) / f"{atomic_id}.step")
self.assertLess(result["volume_mm3"], 1000.0)
self.assertIn(atomic_id, [item["feature_id"] for item in result["feature_results"]])
def test_sphere_add_executes_without_a_sketch(self) -> None:
from cdsl_engine.runtime import rebuild_cdsl
cdsl = {
"schema": "cad.cdsl.llm.v1", "schema_version": "1.1.0", "kind": "part", "part_id": "sphere",
"meta": {"unit": "mm"}, "geometry": {"sketches": []},
"features": [{
"id": "sphere", "atomic_id": "sphere_add", "depends_on": [],
"params": {"radius_mm": 2, "center_mm": [0, 0, 0]},
}],
}
with tempfile.TemporaryDirectory() as directory:
result = rebuild_cdsl(cdsl, Path(directory) / "sphere.step")
self.assertAlmostEqual(result["volume_mm3"], 32 * 3.141592653589793 / 3, places=5)
self.assertEqual(result["feature_results"][-1]["feature_id"], "sphere")
def test_pattern_with_selector_source_is_blocked_before_execution(self) -> None:
from cdsl_engine.runtime import analyze_cdsl
cdsl = self._base_block()
cdsl["features"].extend([
{"id": "fillet", "atomic_id": "fillet", "depends_on": ["base_add"], "params": {"radius_mm": 1}, "selectors": [{"kind": "edge", "stable_id": "edge", "source": "solidworks", "confidence": 1}]},
{"id": "repeat", "atomic_id": "pattern_linear", "depends_on": ["fillet"], "params": {"source_feature_ids": ["fillet"], "direction_1": [1, 0, 0], "spacing_1_mm": 10, "pattern_count_1": 2}},
])
analysis = analyze_cdsl(cdsl)
pattern = next(item for item in analysis.feature_results if item.feature_id == "repeat")
self.assertFalse(pattern.executable)
self.assertIn("unsupported_pattern_selector_transform", [item.code for item in pattern.blockers])
def test_chamfer_and_linear_pattern_execute_without_selector_guessing(self) -> None:
from cdsl_engine.runtime import rebuild_cdsl
base = self._base_block()
base["geometry"]["sketches"].append({
"id": "cut", "workplane": _workplane(), "profile": {"type": "circle", "center": [-2, 0], "radius_mm": 1},
})
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
baseline = rebuild_cdsl(base, root / "baseline.step")
edge = next(item for item in baseline["topology_records"] if item["kind"] == "edge")
chamfered = deepcopy(base)
chamfered["features"].append({
"id": "chamfer", "atomic_id": "chamfer", "depends_on": ["base_add"], "params": {"distance_mm": 1},
"selectors": [{"kind": "edge", "stable_id": "edge", "source": "solidworks", "confidence": 1, "owner_feature_id": "base_add", "geometry": edge["geometry"]}],
})
chamfer_result = rebuild_cdsl(chamfered, root / "chamfer.step")
patterned = deepcopy(base)
patterned["features"].extend([
{"id": "cut_1", "atomic_id": "extrude_cut_blind", "depends_on": ["base_add"], "params": {"distance_mm": 10}, "sketch_id": "cut"},
{"id": "repeat", "atomic_id": "pattern_linear", "depends_on": ["cut_1"], "params": {"source_feature_ids": ["cut_1"], "direction_1": [1, 0, 0], "spacing_1_mm": 2, "pattern_count_1": 3}},
])
pattern_result = rebuild_cdsl(patterned, root / "pattern.step")
self.assertLess(chamfer_result["volume_mm3"], baseline["volume_mm3"])
self.assertAlmostEqual(pattern_result["volume_mm3"], 1000 - 3 * 10 * 3.141592653589793, places=5)
def test_chamfer_consumes_angle_rad_instead_of_silent_45_degree_fallback(self) -> None:
from cdsl_engine.runtime import rebuild_cdsl
import math
base = self._base_block()
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
baseline = rebuild_cdsl(base, root / "baseline.step")
edge = next(item for item in baseline["topology_records"] if item["kind"] == "edge")
selector = {
"kind": "edge", "stable_id": "edge", "source": "solidworks", "confidence": 1,
"owner_feature_id": "base_add", "geometry": edge["geometry"],
}
equal = deepcopy(base)
equal["features"].append({
"id": "chamfer_45", "atomic_id": "chamfer", "depends_on": ["base_add"],
"params": {"distance_mm": 1, "angle_rad": math.pi / 4}, "selectors": [deepcopy(selector)],
})
equal_result = rebuild_cdsl(equal, root / "chamfer-45.step")
# 45° Distance-Angle 等价于等距倒角:tan(45°)=1,切掉 0.5*1*1*10=5 mm³。
self.assertAlmostEqual(equal_result["volume_mm3"], 1000 - 5, places=5)
slanted = deepcopy(base)
slanted["features"].append({
"id": "chamfer_30", "atomic_id": "chamfer", "depends_on": ["base_add"],
"params": {"distance_mm": 1, "angle_rad": math.pi / 6}, "selectors": [deepcopy(selector)],
})
slanted_result = rebuild_cdsl(slanted, root / "chamfer-30.step")
# 30°:第二距离 = 1*tan(30°)≈0.577,切掉 0.5*1*0.577*10≈2.887 mm³,
# 体积明显大于 45° 等距倒角(995),验证 angle_rad 被消费而非静默 45°。
self.assertAlmostEqual(slanted_result["volume_mm3"], 1000 - 0.5 * math.tan(math.pi / 6) * 10, places=5)
def test_linear_pattern_replays_hole_with_explicit_host_frame(self) -> None:
from cdsl_engine.runtime import rebuild_cdsl
cdsl = self._base_block()
cdsl["features"].extend([
{
"id": "hole_1", "atomic_id": "hole_blind", "depends_on": ["base_add"], "sketch_id": "base",
"params": {
"diameter_mm": 2, "depth_mm": 5, "positions": [{"mm": [0, 0, 0]}],
"host_face": {"frame": {
"origin_mm": [0, 0, 10], "x_dir": [1, 0, 0],
"y_dir": [0, 1, 0], "normal": [0, 0, 1],
}},
},
},
{
"id": "repeat", "atomic_id": "pattern_linear", "depends_on": ["hole_1"],
"params": {
"source_feature_ids": ["hole_1"], "direction_1": [1, 0, 0],
"spacing_1_mm": 4, "pattern_count_1": 2,
},
},
])
with tempfile.TemporaryDirectory() as directory:
result = rebuild_cdsl(cdsl, Path(directory) / "patterned-holes.step")
self.assertAlmostEqual(result["volume_mm3"], 1000 - 2 * 5 * 3.141592653589793, places=5)
def test_mirror_pattern_replays_local_hole_coordinates_in_the_correct_quadrant(self) -> None:
from cdsl_engine.runtime import rebuild_cdsl
cdsl = self._base_block()
cdsl["features"].insert(0, {
"id": "mirror_plane", "atomic_id": "reference_plane", "depends_on": [],
"params": {"plane": {"origin_mm": [0, 0, 0], "x_dir": [0, 1, 0], "normal": [1, 0, 0]}},
})
cdsl["features"].extend([
{
"id": "hole_1", "atomic_id": "hole_blind", "depends_on": ["base_add"], "sketch_id": "base",
"params": {
"diameter_mm": 2, "depth_mm": 5, "positions": [{"mm": [2, 3, 0]}],
"host_face": {"frame": {
"origin_mm": [0, 0, 10], "x_dir": [1, 0, 0],
"y_dir": [0, 1, 0], "normal": [0, 0, 1],
}},
},
},
{
"id": "mirror", "atomic_id": "pattern_mirror", "depends_on": ["hole_1", "mirror_plane"],
"params": {
"source_feature_ids": ["hole_1"],
"mirror_plane": {"kind": "plane", "owner_feature_id": "mirror_plane"},
},
"selectors": [{"kind": "plane", "owner_feature_id": "mirror_plane"}],
},
])
with tempfile.TemporaryDirectory() as directory:
result = rebuild_cdsl(cdsl, Path(directory) / "mirrored-holes.step")
mirrored_cylinder = next(
item for item in result["topology_records"]
if item["record_id"].startswith("body:mirror.m.hole_1:face")
and item["geometry"].get("surface_type") == "cylinder"
and item["geometry"]["bbox_mm"][0] < -2.9
)
self.assertEqual(mirrored_cylinder["geometry"]["bbox_mm"][:2], [-3.0, 2.0])
self.assertEqual(mirrored_cylinder["geometry"]["bbox_mm"][3:5], [-1.0, 4.0])
def test_pattern_replays_selected_sources_in_history_order(self) -> None:
from cdsl_engine.runtime import rebuild_cdsl
cdsl = self._base_block()
cdsl["geometry"]["sketches"].append({
"id": "cut", "workplane": _workplane(),
"profile": {"type": "circle", "center": [0, 0], "radius_mm": 1},
})
cdsl["features"].extend([
{
"id": "cut_1", "atomic_id": "extrude_cut_blind", "depends_on": ["base_add"],
"params": {"distance_mm": 10}, "sketch_id": "cut",
},
{
"id": "repeat", "atomic_id": "pattern_linear", "depends_on": ["cut_1"],
# SolidWorks selection exports are not guaranteed to match
# history order; the executor must replay the boss before its cut.
"params": {
"source_feature_ids": ["cut_1", "base_add"],
"direction_1": [1, 0, 0], "spacing_1_mm": 20, "pattern_count_1": 2,
},
},
])
with tempfile.TemporaryDirectory() as directory:
result = rebuild_cdsl(cdsl, Path(directory) / "ordered-pattern.step")
self.assertAlmostEqual(result["volume_mm3"], 2 * (1000 - 10 * 3.141592653589793), places=5)
def test_through_all_extent_uses_current_body(self) -> None:
from cdsl_engine.runtime import rebuild_cdsl
cdsl = self._base_block()
cdsl["geometry"]["sketches"].append({
"id": "cut", "workplane": _workplane(), "profile": {"type": "circle", "center": [0, 0], "radius_mm": 1},
})
cdsl["features"].append({
"id": "cut_all", "atomic_id": "extrude_cut_blind", "depends_on": ["base_add"],
"params": {"distance_mm": 0, "end_condition": {"type": "through_all", "solidworks_code": 1}}, "sketch_id": "cut",
})
with tempfile.TemporaryDirectory() as directory:
result = rebuild_cdsl(cdsl, Path(directory) / "through.step")
self.assertAlmostEqual(result["volume_mm3"], 1000 - 10 * 3.141592653589793, places=5)
def test_two_sided_extrude_uses_independent_forward_and_reverse_distances(self) -> None:
from cdsl_engine.runtime import rebuild_cdsl
cdsl = self._base_block()
feature = cdsl["features"][0]
feature["atomic_id"] = "extrude_add_two_sided"
feature["params"] = {
"distance_mm": 2, "reverse_distance_mm": 3,
"end_condition": {"type": "blind"}, "reverse_end_condition": {"type": "blind"},
}
with tempfile.TemporaryDirectory() as directory:
result = rebuild_cdsl(cdsl, Path(directory) / "two-sided.step")
self.assertAlmostEqual(result["volume_mm3"], 500.0)
self.assertEqual(result["bbox_mm"]["min"][2], -3.0)
self.assertEqual(result["bbox_mm"]["max"][2], 2.0)
def test_two_sided_extrude_requires_reverse_distance_contract(self) -> None:
from cdsl_engine.runtime import analyze_cdsl
cdsl = self._base_block()
feature = cdsl["features"][0]
feature["atomic_id"] = "extrude_add_two_sided"
analysis = analyze_cdsl(cdsl)
self.assertIn("missing_parameter", [item.code for item in analysis.feature_results[0].blockers])
def test_two_sided_cut_uses_independent_forward_and_reverse_distances(self) -> None:
from cdsl_engine.runtime import rebuild_cdsl
cdsl = self._base_block()
cdsl["geometry"]["sketches"].append({
"id": "cut", "workplane": {**_workplane(), "origin_mm": [0, 0, 5]},
"profile": {"type": "circle", "center": [0, 0], "radius_mm": 1},
})
cdsl["features"].append({
"id": "cut_1", "atomic_id": "extrude_cut_two_sided", "depends_on": ["base_add"],
"params": {
"distance_mm": 3, "reverse_distance_mm": 5,
"end_condition": {"type": "blind"}, "reverse_end_condition": {"type": "blind"},
}, "sketch_id": "cut",
})
with tempfile.TemporaryDirectory() as directory:
result = rebuild_cdsl(cdsl, Path(directory) / "two-sided-cut.step")
self.assertAlmostEqual(result["volume_mm3"], 1000.0 - 8.0 * 3.141592653589793, places=5)
def test_revolve_can_resolve_an_owner_qualified_reference_axis(self) -> None:
from cdsl_engine.runtime import rebuild_cdsl
cdsl = {
"schema": "cad.cdsl.llm.v1", "schema_version": "1.1.0", "kind": "part", "part_id": "axis-revolve",
"meta": {"unit": "mm"},
"geometry": {"sketches": [{
"id": "profile", "workplane": _workplane(),
"profile": _rectangle([2, 1], [4, 2]),
}]},
"features": [
{
"id": "axis", "atomic_id": "reference_axis", "depends_on": [],
"params": {"axis": {"origin_mm": [0, 0, 0], "direction": [1, 0, 0]}},
},
{
"id": "revolve", "atomic_id": "revolve_add", "depends_on": ["axis"], "sketch_id": "profile",
"params": {
"angle_deg": 360, "axis": {"selector": {"kind": "axis", "owner_feature_id": "axis"}},
},
"selectors": [{"kind": "axis", "owner_feature_id": "axis"}],
},
],
}
with tempfile.TemporaryDirectory() as directory:
result = rebuild_cdsl(cdsl, Path(directory) / "axis-revolve.step")
self.assertAlmostEqual(result["volume_mm3"], 6 * 3.141592653589793, places=5)
def test_revolve_cut_executes_against_an_existing_body(self) -> None:
from cdsl_engine.runtime import rebuild_cdsl
cdsl = self._base_block()
cdsl["geometry"]["sketches"].append({
"id": "revolve_cut_profile", "workplane": _workplane(),
"profile": _rectangle([-4, 1], [4, 2]),
})
cdsl["features"].append({
"id": "revolve_cut", "atomic_id": "revolve_cut", "depends_on": ["base_add"],
"sketch_id": "revolve_cut_profile",
"params": {"angle_deg": 360, "axis": {"origin_mm": [0, 0, 0], "direction": [1, 0, 0]}},
})
with tempfile.TemporaryDirectory() as directory:
result = rebuild_cdsl(cdsl, Path(directory) / "revolve-cut.step")
self.assertLess(result["volume_mm3"], 1000.0)
self.assertIn("revolve_cut", [item["feature_id"] for item in result["feature_results"]])
def test_revolve_surface_preserves_the_active_solid_and_registers_a_shell(self) -> None:
from cdsl_engine.runtime import rebuild_cdsl
cdsl = self._base_block()
cdsl["geometry"]["sketches"].append({
"id": "surface_profile", "workplane": _workplane(),
"profile": _rectangle([2, 1], [4, 2]),
})
cdsl["features"].append({
"id": "surface_revolve", "atomic_id": "revolve_surface", "depends_on": ["base_add"],
"sketch_id": "surface_profile",
"params": {"angle_deg": 360, "axis": {"origin_mm": [0, 0, 0], "direction": [1, 0, 0]}},
})
with tempfile.TemporaryDirectory() as directory:
result = rebuild_cdsl(cdsl, Path(directory) / "surface-revolve.step")
surface = next(item for item in result["feature_results"] if item["feature_id"] == "surface_revolve")
self.assertNotIn("body_id", surface)
self.assertEqual(surface["surface_id"], "surface:surface_revolve")
self.assertEqual(result["solid_count"], 1)
self.assertAlmostEqual(result["volume_mm3"], 1000.0)
faces = [
item for item in result["topology_records"]
if item["feature_id"] == "surface_revolve" and item["kind"] == "face"
]
self.assertEqual(len(faces), 4)
def test_extrude_surface_preserves_the_active_solid_and_registers_a_shell(self) -> None:
from cdsl_engine.runtime import rebuild_cdsl
cdsl = self._base_block()
cdsl["geometry"]["sketches"].append({
"id": "surface_profile", "workplane": _workplane(),
"profile": {
"type": "analytic_contours",
"contours": [{
"role": "unknown", "closed": True,
"segments": [{"type": "circle", "center": [5, 5], "radius_mm": 2}],
}],
},
})
cdsl["features"].append({
"id": "surface_extrude", "atomic_id": "extrude_surface", "depends_on": ["base_add"],
"sketch_id": "surface_profile", "params": {"distance_mm": 10},
})
with tempfile.TemporaryDirectory() as directory:
result = rebuild_cdsl(cdsl, Path(directory) / "surface-extrude.step")
surface = next(item for item in result["feature_results"] if item["feature_id"] == "surface_extrude")
self.assertNotIn("body_id", surface)
self.assertEqual(surface["surface_id"], "surface:surface_extrude")
self.assertEqual(result["solid_count"], 1)
self.assertAlmostEqual(result["volume_mm3"], 1000.0)
faces = [
item for item in result["topology_records"]
if item["feature_id"] == "surface_extrude" and item["kind"] == "face"
]
self.assertEqual(len(faces), 1)
def test_extrude_surface_exports_a_surface_only_step(self) -> None:
from build123d import import_step
from cdsl_engine.runtime import rebuild_cdsl
cdsl = {
"schema": "cad.cdsl.llm.v1", "schema_version": "1.1.0", "kind": "part", "part_id": "surface-only",
"meta": {"unit": "mm"},
"geometry": {"sketches": [{
"id": "surface_profile", "workplane": _workplane(),
"profile": {
"type": "analytic_contours",
"contours": [{
"role": "unknown", "closed": True,
"segments": [{"type": "circle", "center": [5, 5], "radius_mm": 2}],
}],
},
}]},
"features": [{
"id": "surface_extrude", "atomic_id": "extrude_surface", "depends_on": [],
"sketch_id": "surface_profile", "params": {"distance_mm": 10},
}],
}
with tempfile.TemporaryDirectory() as directory:
out_step = Path(directory) / "surface-only.step"
result = rebuild_cdsl(cdsl, out_step)
rebuilt = import_step(str(out_step))
self.assertEqual(result["solid_count"], 0)
self.assertEqual(result["surface_count"], 1)
self.assertEqual(result["surface_face_count"], 1)
self.assertAlmostEqual(result["volume_mm3"], 0.0)
self.assertAlmostEqual(result["surface_area_mm2"], 40 * math.pi)
self.assertEqual(len(rebuilt.solids()), 0)
self.assertEqual(len(rebuilt.faces()), 1)
def test_surface_limited_chamfer_requires_an_explicit_shell_boundary(self) -> None:
from build123d import Plane, Solid
from cdsl_engine.build123d_adapter import Build123dGeometryAdapter
adapter = Build123dGeometryAdapter()
base = Solid.make_cylinder(19, 20, Plane(origin=(0, 0, -10))).cut(
Solid.make_cylinder(16, 20, Plane(origin=(0, 0, -10))),
)
lower = Solid.make_cylinder(28.5, 20, Plane(origin=(0, 0, -20))).cut(
Solid.make_cylinder(16, 20, Plane(origin=(0, 0, -20))),
)
upper = Solid.make_cylinder(25.5, 25, Plane(origin=(0, 0, 10))).cut(
Solid.make_cylinder(17.5, 25, Plane(origin=(0, 0, 10))),
)
body = adapter.fuse(adapter.fuse(base, lower), upper)
selected = [
edge for edge in body.edges()
if str(edge.geom_type).split(".")[-1].lower() == "circle"
and abs(float(edge.radius) - 28.5) <= 1e-6
and abs(edge.arc_center.Z) <= 1e-6
]
self.assertEqual(len(selected), 1)
with self.assertRaisesRegex(ValueError, "explicit surface support"):
adapter.surface_limited_chamfer(body, 10, selected, [])
wire = adapter._circle_wire([0, 0], 19, PlaneSpec.from_mapping(_workplane()))
support = adapter.extrude_surface([wire], [0, 0, 10])
rebuilt = adapter.surface_limited_chamfer(body, 10, selected, [support])
self.assertLess(rebuilt.volume, body.volume)
cones = [face for face in rebuilt.faces() if str(face.geom_type).split(".")[-1].lower() == "cone"]
self.assertEqual(len(cones), 1)
def test_unowned_revolve_feature_selector_is_preflight_blocked(self) -> None:
from cdsl_engine.runtime import analyze_cdsl
cdsl = json.loads((ROOT / "json_to_cdsl" / "output" / "027784.cdsl.json").read_text(encoding="utf-8"))
analysis = analyze_cdsl(cdsl)
revolve = next(item for item in analysis.feature_results if item.feature_id == "f_007")
self.assertIn("missing_revolve_axis", [item.code for item in revolve.blockers])
def test_tangent_propagation_does_not_expand_to_unrelated_box_edges(self) -> None:
from build123d import Box
from cdsl_engine.build123d_adapter import Build123dGeometryAdapter
body = Box(10, 10, 10)
selected = body.edges()[0]
expanded = Build123dGeometryAdapter().tangent_edges(body, [selected])
self.assertEqual(len(expanded), 1)
self.assertTrue(expanded[0].is_same(selected))
def test_inconsistent_quarter_arc_flags_are_normalized_from_loop_orientation(self) -> None:
from cdsl_engine.batch_rebuild import analyze_document
fixture = ROOT / "json_to_cdsl" / "output" / "053393.cdsl.json"
with tempfile.TemporaryDirectory() as directory:
report = analyze_document(fixture, out_step=Path(directory) / "053393.step")
self.assertTrue(report["runtime_eligible"])
self.assertTrue(report["built"])
self.assertTrue(report["geometry_verified"])
def test_exported_two_sided_fixture_is_geometry_verified(self) -> None:
from cdsl_engine.batch_rebuild import analyze_document
fixture = ROOT / "json_to_cdsl" / "output" / "046112.cdsl.json"
with tempfile.TemporaryDirectory() as directory:
report = analyze_document(fixture, out_step=Path(directory) / "046112.step")
self.assertTrue(report["runtime_eligible"])
self.assertTrue(report["built"])
self.assertTrue(report["geometry_verified"])
def test_selector_driven_extrude_extents_require_unique_rebuilt_topology(self) -> None:
from cdsl_engine.runtime import rebuild_cdsl
base = self._base_block()
base["geometry"]["sketches"].append({
"id": "cut", "workplane": _workplane(),
"profile": {"type": "circle", "center": [0, 0], "radius_mm": 1},
})
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
baseline = rebuild_cdsl(base, root / "baseline.step")
top_face = next(
item for item in baseline["topology_records"]
if item["kind"] == "face" and item["geometry"]["surface_type"] == "plane"
and item["geometry"]["normal"][2] > 0.9
)
top_vertex = next(
item for item in baseline["topology_records"]
if item["kind"] == "vertex" and item["geometry"]["center_mm"][2] > 9.9
)
for name, end_condition, expected_depth in (
("surface", {"type": "up_to_surface", "reference": {"kind": "face", "owner_feature_id": "base_add", "geometry": top_face["geometry"]}}, 10.0),
("vertex", {"type": "up_to_vertex", "reference": {"kind": "vertex", "owner_feature_id": "base_add", "geometry": top_vertex["geometry"]}}, 10.0),
("offset", {"type": "offset_from_surface", "reference": {"kind": "face", "owner_feature_id": "base_add", "geometry": top_face["geometry"]}}, 8.0),
("next", {"type": "through_next"}, 10.0),
):
cdsl = deepcopy(base)
cdsl["features"].append({
"id": f"cut_{name}", "atomic_id": "extrude_cut_blind", "depends_on": ["base_add"],
"params": {"distance_mm": 2 if name == "offset" else 0, "end_condition": end_condition},
"sketch_id": "cut",
})
result = rebuild_cdsl(cdsl, root / f"{name}.step")
self.assertAlmostEqual(result["volume_mm3"], 1000 - expected_depth * 3.141592653589793, places=5)
def test_up_to_vertex_intersection_requires_one_shared_current_body_vertex(self) -> None:
from cdsl_engine.runtime import RuntimeExecutionError, rebuild_cdsl
base = self._base_block()
base["geometry"]["sketches"].append({
"id": "cut", "workplane": _workplane(),
"profile": {"type": "circle", "center": [0, 0], "radius_mm": 1},
})
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
baseline = rebuild_cdsl(base, root / "intersection-baseline.step")
faces = [item for item in baseline["topology_records"] if item["kind"] == "face"]
top = next(item for item in faces if item["geometry"]["normal"][2] > 0.9)
right = next(item for item in faces if item["geometry"]["normal"][0] > 0.9)
back = next(item for item in faces if item["geometry"]["normal"][1] > 0.9)
def face_selector(face: dict) -> dict:
return {
"kind": "face", "owner_feature_id": "base_add",
"stable_id": f"intersection-{face['record_id']}",
"source": "runtime_snapshot", "confidence": 1.0,
"geometry": face["geometry"],
}
cdsl = deepcopy(base)
cdsl["features"].append({
"id": "cut_to_intersection", "atomic_id": "extrude_cut_blind", "depends_on": ["base_add"],
"params": {"distance_mm": 0, "end_condition": {
"type": "up_to_vertex", "reference": {
"kind": "vertex", "owner_feature_id": "base_add",
"stable_id": "top-right-back", "source": "runtime_snapshot", "confidence": 1.0,
"intersection_of": [face_selector(top), face_selector(right), face_selector(back)],
},
}},
"sketch_id": "cut",
})
result = rebuild_cdsl(cdsl, root / "intersection.step")
self.assertAlmostEqual(result["volume_mm3"], 1000 - 10 * 3.141592653589793, places=5)
ambiguous = deepcopy(cdsl)
ambiguous["features"][-1]["params"]["end_condition"]["reference"]["intersection_of"] = [
face_selector(right), face_selector(back),
]
with self.assertRaises(RuntimeExecutionError) as error:
rebuild_cdsl(ambiguous, root / "ambiguous-intersection.step")
self.assertEqual(error.exception.diagnostic.code, "intersection_vertex_unresolved")
def test_through_next_trims_a_partially_overlapping_profile_to_the_next_body_face(self) -> None:
from cdsl_engine.runtime import rebuild_cdsl
base = self._base_block()
base["geometry"]["sketches"].append({
"id": "partial", "workplane": {"origin_mm": [0, 0, 12], "x_dir": [1, 0, 0], "normal": [0, 0, -1]},
"profile": {"type": "polygon", "vertices": [[3, -3], [9, -3], [9, 3], [3, 3]]},
})
base["features"].append({
"id": "partial_add", "atomic_id": "extrude_add_blind", "depends_on": ["base_add"],
"sketch_id": "partial", "params": {"distance_mm": 0, "end_condition": {"type": "through_next"}},
})
with tempfile.TemporaryDirectory() as directory:
result = rebuild_cdsl(base, Path(directory) / "partial-through-next.step")
self.assertAlmostEqual(result["volume_mm3"], 1000 + 24, places=5)
def test_through_next_keeps_leading_material_before_a_cylindrical_body(self) -> None:
from cdsl_engine.runtime import rebuild_cdsl
cdsl = {
"schema": "cad.cdsl.llm.v1", "schema_version": "1.1.0", "kind": "part", "part_id": "leading-through-next",
"meta": {"unit": "mm"},
"geometry": {"sketches": [
{"id": "base", "workplane": _workplane(), "profile": {"type": "circle", "radius_mm": 10}},
{"id": "lead", "workplane": {
"origin_mm": [0, -50, 25], "x_dir": [1, 0, 0], "normal": [0, 1, 0],
}, "profile": {"type": "circle", "radius_mm": 2}},
]},
"features": [
{"id": "base_add", "atomic_id": "extrude_add_blind", "depends_on": [],
"params": {"distance_mm": 50}, "sketch_id": "base"},
{"id": "lead_add", "atomic_id": "extrude_add_blind", "depends_on": ["base_add"],
"params": {"distance_mm": 0, "end_condition": {"type": "through_next"}, "result_mode": "new_body"},
"sketch_id": "lead"},
],
}
with tempfile.TemporaryDirectory() as directory:
result = rebuild_cdsl(cdsl, Path(directory) / "leading-through-next.step")
self.assertEqual(result["solid_count"], 2)
self.assertAlmostEqual(result["bbox_mm"]["min"][1], -50.0, places=5)
def test_up_to_surface_from_a_cylindrical_wall_uses_the_next_body_face(self) -> None:
from cdsl_engine.runtime import rebuild_cdsl
base = {
"schema": "cad.cdsl.llm.v1", "schema_version": "1.1.0", "kind": "part", "part_id": "wall-hole",
"meta": {"unit": "mm"},
"geometry": {"sketches": [
{"id": "tube", "workplane": _workplane(), "profile": {"type": "circle", "radius_mm": 10}},
{"id": "tube_bore", "workplane": _workplane(), "profile": {"type": "circle", "radius_mm": 7}},
{"id": "wall_hole", "workplane": {
"origin_mm": [10, 0, 5], "x_dir": [0, 1, 0], "normal": [-1, 0, 0],
}, "profile": {"type": "circle", "radius_mm": 1}},
]},
"features": [
{"id": "tube_add", "atomic_id": "extrude_add_blind", "depends_on": [],
"params": {"distance_mm": 10}, "sketch_id": "tube"},
{"id": "tube_cut", "atomic_id": "extrude_cut_blind", "depends_on": ["tube_add"],
"params": {"distance_mm": 10}, "sketch_id": "tube_bore"},
],
}
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
baseline = rebuild_cdsl(base, root / "tube.step")
outer_face = next(
item for item in baseline["topology_records"]
if item["kind"] == "face" and item["geometry"]["surface_type"] == "cylinder"
and abs(item["geometry"]["radius_mm"] - 10) <= 1e-6
)
cdsl = deepcopy(base)
cdsl["features"].append({
"id": "wall_hole_cut", "atomic_id": "extrude_cut_blind", "depends_on": ["tube_cut"],
"params": {"distance_mm": 0, "end_condition": {"type": "up_to_surface", "reference": {
"kind": "face", "owner_feature_id": "tube_add", "geometry": outer_face["geometry"],
}}},
"sketch_id": "wall_hole",
})
result = rebuild_cdsl(cdsl, root / "wall-hole.step")
self.assertLess(result["volume_mm3"], baseline["volume_mm3"] - 8)
self.assertGreater(result["volume_mm3"], baseline["volume_mm3"] - 12)
def test_up_to_body_extent_uses_a_uniquely_resolved_body_record(self) -> None:
from cdsl_engine.runtime import rebuild_cdsl
base = self._base_block()
base["geometry"]["sketches"].append({
"id": "cut", "workplane": _workplane(), "profile": {"type": "circle", "radius_mm": 1},
})
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
baseline = rebuild_cdsl(base, root / "baseline.step")
body = next(item for item in baseline["topology_records"] if item["kind"] == "body")
cdsl = deepcopy(base)
cdsl["features"].append({
"id": "cut_to_body", "atomic_id": "extrude_cut_blind", "depends_on": ["base_add"],
"params": {"distance_mm": 0, "end_condition": {
"type": "up_to_body", "reference": {
"kind": "body", "owner_feature_id": "base_add", "geometry": body["geometry"],
},
}},
"sketch_id": "cut",
})
result = rebuild_cdsl(cdsl, root / "up-to-body.step")
self.assertAlmostEqual(result["volume_mm3"], 1000 - 10 * 3.141592653589793, places=5)
def test_selector_dependent_extent_without_reference_is_preflight_blocked(self) -> None:
from cdsl_engine.runtime import analyze_cdsl
cdsl = self._base_block()
cdsl["features"][0]["params"]["end_condition"] = {"type": "up_to_surface"}
analysis = analyze_cdsl(cdsl)
result = analysis.feature_results[0]
self.assertIn("missing_extent_reference", [item.code for item in result.blockers])
def test_hole_wizard_unsupported_extent_is_preflight_blocked(self) -> None:
from cdsl_engine.runtime import analyze_cdsl
cdsl = self._base_block()
cdsl["features"].append({
"id": "hole", "atomic_id": "hole_wizard", "depends_on": ["base_add"],
"params": {
"hole_type": "plain", "diameter_mm": 2, "depth_mm": 2,
"positions": [{"mm": [0, 0, 0]}], "host_face": {"kind": "face"},
"end_condition": {"type": "up_to_surface", "solidworks_code": 0},
},
})
analysis = analyze_cdsl(cdsl)
result = next(item for item in analysis.feature_results if item.feature_id == "hole")
self.assertIn("unsupported_hole_extent", [item.code for item in result.blockers])
def test_legacy_hole_atomics_have_the_same_host_and_shape_preflight_contract(self) -> None:
from cdsl_engine.runtime import analyze_cdsl
cdsl = self._base_block()
cdsl["features"].append({
"id": "hole", "atomic_id": "hole_counterbore", "depends_on": ["base_add"], "sketch_id": "base",
"params": {
"diameter_mm": 2, "depth_mm": 3, "positions": [{"mm": [0, 0, 0]}],
"counterbore_diameter_mm": 2, "counterbore_depth_mm": 1,
},
})
analysis = analyze_cdsl(cdsl)
result = next(item for item in analysis.feature_results if item.feature_id == "hole")
codes = {item.code for item in result.blockers}
self.assertIn("missing_host_face", codes)
self.assertIn("invalid_hole_spec", codes)
def test_body_mutation_without_a_preceding_solid_is_preflight_blocked(self) -> None:
from cdsl_engine.runtime import analyze_cdsl
cdsl = {
"schema": "cad.cdsl.llm.v1", "schema_version": "1.1.0", "kind": "part", "part_id": "no-body",
"meta": {"unit": "mm"}, "geometry": {"sketches": []},
"features": [{
"id": "hole", "atomic_id": "hole_wizard", "depends_on": [],
"params": {
"hole_type": "plain", "diameter_mm": 2, "depth_mm": 3,
"end_condition": {"type": "blind", "solidworks_code": 0}, "positions": [{"mm": [0, 0, 0]}],
"host_face": {"frame": _workplane()},
},
}],
}
result = analyze_cdsl(cdsl).feature_results[0]
self.assertIn("missing_active_body", [item.code for item in result.blockers])
if __name__ == "__main__":
unittest.main()