from __future__ import annotations 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")) sys.path.insert(0, str(ROOT / "backend" / "engine")) from cdsl_engine.sketch_solver import list_registered_shapes # noqa: E402 def _workplane() -> dict: return {"origin_mm": [0, 0, 0], "x_dir": [1, 0, 0], "normal": [0, 0, 1]} def _document(profile: dict) -> dict: return { "schema": "cad.cdsl.llm.v1", "schema_version": "1.1.0", "kind": "part", "part_id": "profile-chain", "meta": {"unit": "mm"}, "geometry": {"sketches": [{ "id": "base", "workplane": _workplane(), "profile": profile, }]}, "features": [{ "id": "base_add", "atomic_id": "extrude_add_blind", "depends_on": [], "params": {"distance_mm": 10}, "sketch_id": "base", }], } class EngineProfileContractTests(unittest.TestCase): """#10 Profile 链:annulus / circles 进入 CDSL-only 运行时与公开 schema。""" def test_annulus_and_circles_are_registered_runtime_profiles(self) -> None: registered = list_registered_shapes() self.assertIn("annulus", registered) self.assertIn("circles", registered) def test_annulus_profile_rebuilds_as_ring_extrusion(self) -> None: from cdsl_engine.runtime import rebuild_cdsl cdsl = _document({ "type": "annulus", "center": [0, 0], "inner_radius_mm": 2, "outer_radius_mm": 5, }) with tempfile.TemporaryDirectory() as directory: result = rebuild_cdsl(cdsl, Path(directory) / "ring.step") # 环面面积 π(5²-2²)=21π,拉伸 10 → 210π ≈ 659.734 self.assertAlmostEqual(result["volume_mm3"], 210 * math.pi, places=4) self.assertEqual(result["solid_count"], 1) def test_circles_profile_rebuilds_as_independent_solids(self) -> None: from cdsl_engine.runtime import rebuild_cdsl cdsl = _document({ "type": "circles", "items": [ {"center": [-4, 0], "radius_mm": 2}, {"center": [4, 0], "radius_mm": 2}, ], }) with tempfile.TemporaryDirectory() as directory: result = rebuild_cdsl(cdsl, Path(directory) / "two.step") # 两个独立圆柱:2 × π×2²×10 = 80π ≈ 251.327 self.assertAlmostEqual(result["volume_mm3"], 2 * math.pi * 4 * 10, places=4) self.assertEqual(result["solid_count"], 2) def test_annulus_validation_rejects_inverted_radii(self) -> None: from cdsl_engine.runtime import rebuild_cdsl cdsl = _document({ "type": "annulus", "center": [0, 0], "inner_radius_mm": 5, "outer_radius_mm": 2, }) with tempfile.TemporaryDirectory() as directory: # 能力检查阶段以 profile_resolution_failed blocker 拒绝该文档 # (reason 为 "annulus: inner_radius must be < outer_radius")。 with self.assertRaises(ValueError): rebuild_cdsl(cdsl, Path(directory) / "bad.step") if __name__ == "__main__": unittest.main()