"""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)