e7ade4aa00
同心圆截面改为输出外圆与内圆两条 analytic_contours 轮廓,避免旧 annulus 宏导致 CDSL 文档不符合 schema 且运行时无法执行
171 lines
7.6 KiB
Python
171 lines
7.6 KiB
Python
"""#10 Profile chain: evidence_v2 importer must lower annulus to analytic_contours.
|
||
|
||
The CDSL-only runtime and ``cdsl_schema.json`` accept only three generic
|
||
profiles (``circle``, ``polygon``, ``analytic_contours``). The legacy
|
||
``annulus`` macro is still emitted by ``evidence_v2_to_cdsl._analytic_profile``
|
||
for concentric circles, which makes the resulting document schema-invalid and
|
||
runtime-ineligible. This test suite pins the lowering contract: concentric
|
||
circles must produce an ``analytic_contours`` profile (outer + inner ring).
|
||
|
||
中文说明
|
||
--------
|
||
这个文件在测试什么(issue #10「10 Profile 链」的回归测试):
|
||
|
||
1. 背景:CDSL-only 运行时(backend/engine/cdsl_engine)与
|
||
cdsl_schema.json 只接受三种通用 profile 类型:
|
||
circle / polygon / analytic_contours。
|
||
而 evidence_v2 导入器(json_to_cdsl/evidence_v2_to_cdsl.py 的
|
||
_analytic_profile)在遇到"同心双圆"(垫圈/圆环 annulus 截面)时,
|
||
仍会输出旧宏 {type: "annulus"},导致产出的 CDSL 文档 schema 不合法、
|
||
运行时不可执行(报 profile_resolution_failed / unsupported_profile)。
|
||
|
||
2. 本测试套件把"降级契约"固定下来:同心双圆必须降级为
|
||
analytic_contours 轮廓(外圆 role=outer + 内圆 role=inner),
|
||
并保证 importer 输出能被 CDSL-only 运行时端到端重建为实体。
|
||
|
||
3. sys.path 说明:把 json_to_cdsl 与 backend/engine 加入搜索路径,
|
||
是为了让测试能直接 import 导入器内部的 _analytic_profile(灰盒测试)
|
||
以及 CDSL-only 运行时的 rebuild_cdsl(端到端测试)。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import math
|
||
import sys
|
||
import tempfile
|
||
import unittest
|
||
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"))
|
||
sys.path.insert(0, str(ROOT / "json_to_cdsl"))
|
||
|
||
try:
|
||
import build123d # noqa: F401
|
||
_HAS_BUILD123D = True
|
||
except ImportError:
|
||
_HAS_BUILD123D = False
|
||
|
||
from evidence_v2_to_cdsl import _analytic_profile # noqa: E402
|
||
|
||
|
||
def _circle_segment(center: list[float], radius_mm: float, *, construction: bool = False) -> dict:
|
||
"""One full SolidWorks sketch circle segment (start == end == center).
|
||
|
||
构造一条 SolidWorks 草图中的完整圆线段(start == end == center,
|
||
即闭合圆)。radius_mm 除以 1000 转成米,与导入器内部单位保持一致。
|
||
"""
|
||
return {
|
||
"geometry": {
|
||
"segment_type": "swSketchARC",
|
||
"construction": construction,
|
||
"start": center,
|
||
"end": center,
|
||
"center": center,
|
||
"direction": 1,
|
||
"curve": {"type": "circle", "parameters": [*center, 0, 0, 0, 1, radius_mm / 1000.0]},
|
||
}
|
||
}
|
||
|
||
|
||
def _ring_cdsl(profile: dict) -> dict:
|
||
"""Assemble a minimal CDSL document whose single feature extrudes a ring profile.
|
||
|
||
组装一份最简 CDSL 文档:一张草图(ring,携带被测试的 profile)+
|
||
一个拉伸特征(extrude_add_blind)。供端到端测试使用,
|
||
验证 importer 产出的 profile 能否被运行时重建为实体。
|
||
"""
|
||
return {
|
||
"schema": "cad.cdsl.llm.v1",
|
||
"schema_version": "1.1.0",
|
||
"kind": "part",
|
||
"part_id": "annulus-ring",
|
||
"meta": {"unit": "mm"},
|
||
"geometry": {"sketches": [{
|
||
"id": "ring",
|
||
"workplane": {"origin_mm": [0, 0, 0], "x_dir": [1, 0, 0], "normal": [0, 0, 1]},
|
||
"profile": profile,
|
||
}]},
|
||
"features": [{
|
||
"id": "ring_add", "atomic_id": "extrude_add_blind", "depends_on": [],
|
||
"params": {"distance_mm": 20}, "sketch_id": "ring",
|
||
}],
|
||
}
|
||
|
||
|
||
class EvidenceV2AnnulusLoweringTests(unittest.TestCase):
|
||
"""annulus 降级契约的回归测试套件。
|
||
|
||
三个测试用例分别覆盖:
|
||
1. test_evidence_v2_emits_analytic_contours_for_annulus
|
||
主契约:同心双圆(外 r=10mm,内 r=5mm)必须降级为 analytic_contours,
|
||
且恰好携带 outer(10) + inner(5) 两条轮廓。
|
||
2. test_evidence_v2_emits_analytic_contours_for_multiple_circles
|
||
回归护栏:两个独立(圆心不同、互不包含)的圆本来就输出
|
||
analytic_contours,修复 annulus 分支时不得破坏该既有行为。
|
||
3. test_evidence_v2_annulus_output_rebuilds_to_ring_solid
|
||
端到端:importer 输出的 analytic_contours 必须能被 CDSL-only 运行时
|
||
rebuild_cdsl 重建为实体,且体积与圆环公式
|
||
π * (r外² - r内²) * 高度 一致(误差 1%)。
|
||
"""
|
||
|
||
def test_evidence_v2_emits_analytic_contours_for_annulus(self) -> None:
|
||
"""Concentric circles must lower to a generic ring (outer + inner)."""
|
||
# 主契约测试:同圆心 (0,0) 的两个圆,外径 10mm、内径 5mm
|
||
sketch = {"segments": [
|
||
_circle_segment([0, 0], 10.0),
|
||
_circle_segment([0, 0], 5.0),
|
||
]}
|
||
profile = _analytic_profile(sketch)
|
||
# 1) profile 类型必须是 analytic_contours(而不是旧宏 annulus)
|
||
self.assertEqual(profile["type"], "analytic_contours")
|
||
contours = profile["contours"]
|
||
# 2) 必须恰好有两条轮廓
|
||
self.assertEqual(len(contours), 2)
|
||
# 3) 两条轮廓的角色分别是 outer(外圆)与 inner(内圆)
|
||
self.assertEqual({contour["role"] for contour in contours}, {"outer", "inner"})
|
||
outer = next(contour for contour in contours if contour["role"] == "outer")
|
||
inner = next(contour for contour in contours if contour["role"] == "inner")
|
||
# 4) 半径正确:外 10mm / 内 5mm
|
||
self.assertEqual(outer["segments"][0]["radius_mm"], 10.0)
|
||
self.assertEqual(inner["segments"][0]["radius_mm"], 5.0)
|
||
|
||
def test_evidence_v2_emits_analytic_contours_for_multiple_circles(self) -> None:
|
||
"""Regression guard: independent circles already lower to contours."""
|
||
# 回归护栏:两个圆心不同(相距 30mm)、互不包含的独立圆,
|
||
# 修复 annulus 分支前后都必须保持输出 analytic_contours(各一条 outer 轮廓)
|
||
sketch = {"segments": [
|
||
_circle_segment([0, 0], 10.0),
|
||
_circle_segment([30, 0], 5.0),
|
||
]}
|
||
profile = _analytic_profile(sketch)
|
||
self.assertEqual(profile["type"], "analytic_contours")
|
||
self.assertEqual(len(profile["contours"]), 2)
|
||
|
||
@unittest.skipUnless(_HAS_BUILD123D, "build123d is not available")
|
||
def test_evidence_v2_annulus_output_rebuilds_to_ring_solid(self) -> None:
|
||
"""Importer output must be consumed end-to-end by the CDSL-only runtime."""
|
||
# 端到端:直接取 _analytic_profile 的输出组装成 CDSL 文档,
|
||
# 交给 CDSL-only 运行时 rebuild_cdsl 重建实体,再断言体积符合圆环公式。
|
||
# 此测试验证修复后运行时不再报 profile_resolution_failed / unsupported_profile。
|
||
from cdsl_engine.runtime import rebuild_cdsl
|
||
|
||
sketch = {"segments": [
|
||
_circle_segment([0, 0], 10.0),
|
||
_circle_segment([0, 0], 5.0),
|
||
]}
|
||
profile = _analytic_profile(sketch)
|
||
cdsl = _ring_cdsl(profile)
|
||
with tempfile.TemporaryDirectory() as directory:
|
||
out_step = Path(directory) / "ring.step"
|
||
result = rebuild_cdsl(cdsl, out_step)
|
||
# 圆环体积 = π * (r外² - r内²) * 高度 = π * (100 - 25) * 20
|
||
expected = math.pi * (10.0 ** 2 - 5.0 ** 2) * 20.0
|
||
self.assertAlmostEqual(result["volume_mm3"], expected, delta=expected * 0.01)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
unittest.main()
|