Files
cdsl-cad/backend/engine/cdsl_engine/executors/surfaces.py
T
ganjihong 5ffb106f36 refactor(cdsl_engine): executor registry + per-family executor package
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.
2026-09-09 13:33:06 +08:00

76 lines
3.5 KiB
Python

"""Surface-feature executors (extrude_surface / revolve_surface).
Surface features register an independent shell and never touch the active
solid body's fuse/cut lifecycle.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from ..extents import _normal_from_sketch
from ..registry import atomic_executor
from ..specs import AxisSpec, PlaneSpec, vector_cross, vector_dot, vector_scale, vector_unit
from ..topology import FeaturePlanNode, FeatureResult
from .common import _revolve_axis, _validate_revolve_axis_in_sketch_plane
if TYPE_CHECKING: # pragma: no cover - import for type checkers only
from ..session import ExecutionSession
def _execute_revolve_surface(node: FeaturePlanNode, session: "ExecutionSession") -> FeatureResult:
# Surface revolve 的 profile 是单一闭合 wire。它只生成独立 shell,不能参与
# 当前实体 body 的 fuse/cut,也不能把其结果误报为新的实体 body。
sketch = session.sketches.get(str(node.sketch_id))
if sketch is None:
raise ValueError("surface revolve has no resolved sketch")
faces = session.adapter.faces_for_sketch(sketch)
if len(faces) != 1 or faces[0].inner_wires():
raise ValueError("surface revolve requires exactly one closed profile without holes")
axis = _revolve_axis(node, session)
_validate_revolve_axis_in_sketch_plane(axis, sketch)
angle = float(node.params.get("angle_deg") or 0.0)
if angle <= 0:
raise ValueError("surface revolve requires angle_deg > 0")
if bool(node.params.get("reverse")):
angle = -angle
surface_id = session.register_surface(
node.feature_id,
session.adapter.revolve_surface(faces[0].outer_wire(), angle, axis),
)
return session.result(node, include_body=False, surface_id=surface_id)
@atomic_executor("revolve_surface")
def _revolve_surface_executor(node: FeaturePlanNode, session: "ExecutionSession", sketch: dict[str, Any] | None) -> FeatureResult:
del sketch
return _execute_revolve_surface(node, session)
def _execute_extrude_surface(node: FeaturePlanNode, session: "ExecutionSession") -> FeatureResult:
# surfaceEntities 的曲面拉伸沿用实体特征已 lower 的距离,但始终独立登记为
# shell。它既不改变 active solid,也不以曲面参与实体 fuse/cut。
sketch = session.sketches.get(str(node.sketch_id))
if sketch is None:
raise ValueError("surface extrude has no resolved sketch")
direction = vector_unit(_normal_from_sketch(sketch), field_name="sketch normal")
if bool(node.params.get("reverse")):
direction = vector_scale(direction, -1)
distance = float(node.params.get("distance_mm") or 0.0)
if distance <= 0:
raise ValueError("surface extrude requires distance_mm > 0")
wires = session.adapter.surface_wires_for_sketch(sketch)
surface = session.adapter.extrude_surface(wires, vector_scale(direction, distance))
reverse_distance = float(node.params.get("reverse_distance_mm") or 0.0)
if reverse_distance > 0:
opposite = session.adapter.extrude_surface(wires, vector_scale(direction, -reverse_distance))
surface = session.adapter.combine_surfaces(surface, opposite)
surface_id = session.register_surface(node.feature_id, surface)
return session.result(node, include_body=False, surface_id=surface_id)
@atomic_executor("extrude_surface")
def _extrude_surface_executor(node: FeaturePlanNode, session: "ExecutionSession", sketch: dict[str, Any] | None) -> FeatureResult:
del sketch
return _execute_extrude_surface(node, session)