5ffb106f36
Phase 3 of the decoupling refactor (behavior-preserving): - registry.py: atomic_executor decorator, ALL_ATOMIC_IDS with fail-fast registration validation, execute_node dispatcher - executors/: one module per family (extrude, revolve, surfaces, loft_sweep, bodies, context, primitives, parametric, holes, dressup, patterns) + shared helpers in executors/common - executors/__init__: explicit aggregation + completeness check (registry must cover every declared atomic id at import time) - runtime.py: slimmed to entry points (analyze_cdsl/rebuild_cdsl) plus full historical re-exports incl. test-referenced privates Adding an atomic operation now touches only one executor module and its schema contract; the shared registry never changes. Verified against baseline: zero new failures.
64 lines
3.5 KiB
Python
64 lines
3.5 KiB
Python
"""Reference-geometry executors (reference_plane / reference_axis).
|
|
|
|
Context features produce no solid; they register durable topology contexts
|
|
that later features resolve through owner-qualified selectors.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
from ..registry import atomic_executor
|
|
from ..specs import AxisSpec, PlaneSpec, vector_add, vector_cross, vector_dot, vector_scale, vector_unit
|
|
from ..topology import FeaturePlanNode, FeatureResult
|
|
|
|
if TYPE_CHECKING: # pragma: no cover - import for type checkers only
|
|
from ..session import ExecutionSession
|
|
|
|
|
|
@atomic_executor("reference_plane")
|
|
def _reference_plane_executor(node: FeaturePlanNode, session: "ExecutionSession", sketch: dict[str, Any] | None) -> FeatureResult:
|
|
del sketch
|
|
# 基准面特征(reference_plane)执行入口:从参数解析平面并登记为拓扑上下文。
|
|
|
|
# 1. 从特征参数 plane 中解析出平面定义 PlaneSpec(原点到法向)。
|
|
plane = PlaneSpec.from_mapping(node.params.get("plane") or {})
|
|
# 2. 将该平面注册到拓扑上下文,供后续特征(如草图基准、参考轴)引用。
|
|
session.topology.register_context(node.feature_id, plane)
|
|
# 3. 返回结果对象,并将该平面作为上下文一并携带。
|
|
return session.result(node, context=plane)
|
|
|
|
|
|
@atomic_executor("reference_axis")
|
|
def _reference_axis_executor(node: FeaturePlanNode, session: "ExecutionSession", sketch: dict[str, Any] | None) -> FeatureResult:
|
|
del sketch
|
|
# 基准轴特征(reference_axis)执行入口:由参数直接定义轴,或由两个基准平面求交线得到轴。
|
|
|
|
# 1. 尝试直接取参数:若同时给出原点 origin_mm 与方向 direction,则直接构造轴。
|
|
params = node.params.get("axis") or {}
|
|
if params.get("origin_mm") and params.get("direction"):
|
|
axis = AxisSpec.from_mapping(params)
|
|
else:
|
|
# 2. 否则从特征选择器中筛选出已解析的基准平面。
|
|
planes = [session.resolve(selector) for selector in node.selectors if selector.get("kind") == "plane"]
|
|
resolved = [item.record.value for item in planes if item.status == "resolved" and isinstance(item.record.value, PlaneSpec)]
|
|
# 3. 校验:轴需要两个非平行的平面,不足两个则报错。
|
|
if len(resolved) < 2:
|
|
raise ValueError("reference axis requires two uniquely resolved planes")
|
|
# 4. 用两平面法线叉积求交线方向;若方向长度接近 0 说明两平面平行,无法成轴。
|
|
first, second = resolved[0], resolved[1]
|
|
n1, n2 = first.normal, second.normal
|
|
direction = vector_cross(n1, n2)
|
|
squared_length = vector_dot(direction, direction)
|
|
if squared_length <= 1e-18:
|
|
raise ValueError("reference planes are parallel and cannot define an axis")
|
|
# 5. 求交线上的一点:两平面到各自原点的垂距参与线性组合,得到交线上的最近点。
|
|
d1 = vector_dot(n1, first.origin_mm)
|
|
d2 = vector_dot(n2, second.origin_mm)
|
|
point = vector_scale(vector_add(vector_scale(vector_cross(n2, direction), d1), vector_scale(vector_cross(direction, n1), d2)), 1 / squared_length)
|
|
# 6. 由该点与归一化的交线方向组合成基准轴 AxisSpec。
|
|
axis = AxisSpec(origin_mm=point, direction=vector_unit(direction, field_name="reference axis"))
|
|
# 7. 注册为拓扑上下文,并返回结果对象(携带该轴)。
|
|
session.topology.register_context(node.feature_id, axis)
|
|
return session.result(node, context=axis)
|