修改了一些之前增加功能的bug

This commit is contained in:
2026-09-07 15:25:24 +08:00
parent ef5d393c20
commit 9160c2bace
7 changed files with 564 additions and 24 deletions
@@ -0,0 +1,166 @@
"""N2 pattern_circular —— 旋转几何契约层测试。
中文说明
--------
统计报告把 N2(直线/环形阵列)归为 A 类;runtime 的 pattern 采用源特征
重放范式(pattern_linear/_translated_node、pattern_mirror/_mirrored_node),
本次为 pattern_circular 补齐了缺失的"环形重放实现入口":旋转重放节点
(_rotated_node)与 Rodrigues 旋转辅助(_rotated_vector/_rotated_point)。
本文件是三个测试角度中的**几何单元层**:只验证旋转数学与草图/参数变换
函数的正确性,不驱动 executor、不跑布尔运算。旋转是环形阵列几何正确性
的全部基础——若绕轴旋转的坐标变换有误,任何 count/包角组合都会产出错误
实例位置。runtime 端到端层见 test_engine_circular_pattern_runtime.py。
契约要点(与 runtime 层的分工):
1. Rodrigues 旋转是等距变换:轴向分量守恒、长度/到轴距离不变、
非轴向按 cos/sin 旋转 → 用 90°/180° 与随机向量精确断言坐标。
2. 方向约定:绕轴 direction 正角度为右手逆时针(+Z 轴把 +X 转到 +Y)。
3. _rotated_sketch 只旋转世界坐标(workplane frame 与 start/end/center_mm),
2D 局部实体坐标不动(frame 旋转后由草图求解器映射到新世界位置)。
4. _box_circular_is_exact:box_add 是固定世界轴对齐图元,仅坐标轴旋转
且每份转角为 180° 整数倍时才精确(对齐 _execute_mirror_pattern 的
box 坐标平面限制思路)。
sys.path 说明:把 backend 与 backend/engine 加入搜索路径,直接 import
cdsl_engine 包内模块直测 adapter(与既有测试风格一致)。
"""
from __future__ import annotations
import math
import sys
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"))
from cdsl_engine.runtime import ( # noqa: E402
_box_circular_is_exact,
_coordinate_axis_direction,
_rotated_point,
_rotated_sketch,
_rotated_vector,
)
from cdsl_engine.runtime_types import AxisSpec # noqa: E402
X_AXIS = AxisSpec(origin_mm=(0.0, 0.0, 0.0), direction=(1.0, 0.0, 0.0))
Y_AXIS = AxisSpec(origin_mm=(0.0, 0.0, 0.0), direction=(0.0, 1.0, 0.0))
Z_AXIS = AxisSpec(origin_mm=(0.0, 0.0, 0.0), direction=(0.0, 0.0, 1.0))
# Rodrigues 要求单位轴(运行时 AxisSpec.from_mapping 会单位化);测试直接
# 构造 dataclass,必须显式传入单位方向。
_UNIT_DIAGONAL = math.sqrt(1.0 / 3.0)
SKEW_AXIS = AxisSpec(origin_mm=(0.0, 0.0, 0.0),
direction=(_UNIT_DIAGONAL, _UNIT_DIAGONAL, _UNIT_DIAGONAL))
def _assert_vector_close(test: unittest.TestCase, actual, expected, *, places: int = 9) -> None:
for actual_component, expected_component in zip(actual, expected):
test.assertAlmostEqual(float(actual_component), float(expected_component), places=places)
class CircularPatternGeometryTests(unittest.TestCase):
def test_rotated_point_around_z_axis_cardinal_angles(self) -> None:
# +Z 轴右手逆时针:90° 把 (10, 0, 0) 转到 (0, 10, 0),180° 到 (-10, 0, 0),
# 270° 到 (0, -10, 0)。z 坐标不变。
point = (10.0, 0.0, 4.0)
for angle_deg, expected in [
(90.0, (0.0, 10.0, 4.0)),
(180.0, (-10.0, 0.0, 4.0)),
(270.0, (0.0, -10.0, 4.0)),
(360.0, (10.0, 0.0, 4.0)),
]:
with self.subTest(angle=angle_deg):
rotated = _rotated_point(point, Z_AXIS, math.radians(angle_deg))
_assert_vector_close(self, rotated, expected)
def test_rotated_point_negative_angle_rotates_clockwise(self) -> None:
# -90° 顺时针:+X 转到 -Y。
rotated = _rotated_point((10.0, 0.0, 0.0), Z_AXIS, math.radians(-90.0))
_assert_vector_close(self, rotated, (0.0, -10.0, 0.0))
def test_rotated_point_about_arbitrary_axis_keeps_axis_distance(self) -> None:
# 绕空间对角轴:轴向分量守恒、到轴距离不变(等距变换)。
point = (3.0, -2.0, 7.0)
rotated = _rotated_point(point, SKEW_AXIS, math.radians(40.0))
axial = sum(component * value for component, value in zip(point, SKEW_AXIS.direction))
rotated_axial = sum(component * value for component, value in zip(rotated, SKEW_AXIS.direction))
self.assertAlmostEqual(rotated_axial, axial, places=9)
distance_sq = sum(c * c for c in point) - axial * axial
rotated_distance_sq = sum(c * c for c in rotated) - rotated_axial * rotated_axial
self.assertAlmostEqual(rotated_distance_sq, distance_sq, places=9)
def test_rotated_point_around_offset_axis(self) -> None:
# 轴不过原点:先平移到轴、旋转、再平移回。绕 (10, 0, 0) 竖直轴转 90°,
# 点 (10, 5, 0) 的相对矢量 (0, 5, 0) 转到 (-5, 0, 0) → (5, 0, 0)。
offset_axis = AxisSpec(origin_mm=(10.0, 0.0, 0.0), direction=(0.0, 0.0, 1.0))
rotated = _rotated_point((10.0, 5.0, 0.0), offset_axis, math.radians(90.0))
_assert_vector_close(self, rotated, (5.0, 0.0, 0.0))
def test_rotated_vector_preserves_length_and_normalizes_axis(self) -> None:
# 向量旋转不含平移项(原点到向量尾所在轴上的投影分量守恒)。
value = (2.0, 0.0, 0.0)
rotated = _rotated_vector(value, Z_AXIS, math.radians(90.0))
_assert_vector_close(self, rotated, (0.0, 2.0, 0.0))
for axis, expected in [
(X_AXIS, (2.0, 0.0, 0.0)), # 绕自身轴旋转不变
(Y_AXIS, (0.0, 0.0, -2.0)), # 绕 +Y 转 90°(右手)把 +X 转到 -Z
]:
with self.subTest(axis=axis.direction):
_assert_vector_close(self, _rotated_vector(value, axis, math.radians(90.0)), expected)
def test_rotated_vector_arbitrary_angle_is_length_preserving(self) -> None:
value = (1.0, 2.0, 3.0)
rotated = _rotated_vector(value, SKEW_AXIS, math.radians(71.0))
self.assertAlmostEqual(
sum(c * c for c in rotated), sum(c * c for c in value), places=9)
def test_rotated_sketch_rotates_workplane_and_world_contours_only(self) -> None:
# workplane frame(原点 + 三向量)与 contour 世界坐标点绕 +Z 转 90°;
# 2D 局部实体坐标保持原样(frame 旋转负责映射到新世界位置)。
sketch = {
"workplane": {"origin_mm": [0.0, 0.0, 0.0], "x_dir": [1.0, 0.0, 0.0],
"y_dir": [0.0, 1.0, 0.0], "normal": [0.0, 0.0, 1.0]},
"entities": [{"type": "circle", "center": [0.0, 1.0], "radius_mm": 2.0}],
"contour_edges_mm": [{"start_mm": [1.0, 2.0, 0.0], "end_mm": [3.0, 4.0, 5.0],
"center_mm": [2.0, 3.0, 0.0]}],
"contour_regions_mm": [{"outer": [{"start_mm": [0.0, 1.0, 0.0],
"end_mm": [1.0, 0.0, 0.0]}]}],
}
rotated = _rotated_sketch(sketch, Z_AXIS, math.radians(90.0))
_assert_vector_close(self, rotated["workplane"]["origin_mm"], (0.0, 0.0, 0.0))
_assert_vector_close(self, rotated["workplane"]["x_dir"], (0.0, 1.0, 0.0))
_assert_vector_close(self, rotated["workplane"]["y_dir"], (-1.0, 0.0, 0.0))
_assert_vector_close(self, rotated["workplane"]["normal"], (0.0, 0.0, 1.0))
self.assertEqual(rotated["entities"], sketch["entities"])
edge = rotated["contour_edges_mm"][0]
_assert_vector_close(self, edge["start_mm"], (-2.0, 1.0, 0.0))
_assert_vector_close(self, edge["end_mm"], (-4.0, 3.0, 5.0))
_assert_vector_close(self, edge["center_mm"], (-3.0, 2.0, 0.0))
outer = rotated["contour_regions_mm"][0]["outer"][0]
_assert_vector_close(self, outer["start_mm"], (-1.0, 0.0, 0.0))
_assert_vector_close(self, outer["end_mm"], (0.0, 1.0, 0.0))
def test_box_circular_exactness_boundary(self) -> None:
# box_add 固定世界轴对齐:仅坐标轴旋转且每份转角为 180° 整数倍时精确。
self.assertTrue(_box_circular_is_exact(Z_AXIS, math.radians(180.0)))
self.assertTrue(_box_circular_is_exact(Z_AXIS, math.radians(360.0)))
self.assertFalse(_box_circular_is_exact(Z_AXIS, math.radians(90.0)))
self.assertFalse(_box_circular_is_exact(Z_AXIS, math.radians(45.0)))
# 绕 x/y 的非 180° 转角同样不可精确表达;空间对角轴任何转角都不行。
self.assertFalse(_box_circular_is_exact(X_AXIS, math.radians(90.0)))
self.assertFalse(_box_circular_is_exact(SKEW_AXIS, math.radians(180.0)))
def test_coordinate_axis_direction_detection(self) -> None:
self.assertTrue(_coordinate_axis_direction([0.0, 0.0, 1.0]))
self.assertTrue(_coordinate_axis_direction((0.0, -1.0, 0.0)))
self.assertFalse(_coordinate_axis_direction([1.0, 1.0, 1.0]))
self.assertFalse(_coordinate_axis_direction([1.0, 0.0, 0.5]))
if __name__ == "__main__":
unittest.main()