94 lines
4.4 KiB
Python
94 lines
4.4 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)
|
|
|
|
|
|
def _execute_loft_surface(node: FeaturePlanNode, session: "ExecutionSession") -> FeatureResult:
|
|
profile_ids = node.params.get("profile_sketch_ids") or []
|
|
profiles: list[dict[str, Any]] = []
|
|
for sketch_id in profile_ids:
|
|
profile = session.sketches.get(str(sketch_id))
|
|
if profile is None:
|
|
raise ValueError(f"surface loft profile sketch {sketch_id!r} is not resolved")
|
|
profiles.append(profile)
|
|
surface_id = session.register_surface(node.feature_id, session.adapter.loft_surface(profiles))
|
|
return session.result(node, include_body=False, surface_id=surface_id)
|
|
|
|
|
|
@atomic_executor("loft_surface")
|
|
def _loft_surface_executor(node: FeaturePlanNode, session: "ExecutionSession", sketch: dict[str, Any] | None) -> FeatureResult:
|
|
del sketch
|
|
return _execute_loft_surface(node, session)
|