Files
cdsl-cad/backend/engine/cdsl_engine/executors/holes.py
T
likang 994d06aaea feat(selector): 增加离线候选遍历与严格回放验证 Demo
- 新增 selector_candidate_demo,移除 provenance intent 后枚举候选 selector
- 对候选分支执行有界重建与严格 STEP 比较
- 仅在候选遍历完整且唯一 strict 通过时生成 selector 映射记录
- 增加 selector 候选搜索、预算限制和记录生成的测试
- 保持生产 selector resolver 不受 Demo 逻辑影响
- 更新 CADFS 能力台账,记录 IMPRINT 派生 profile 的 lineage selector 缺口
2026-09-10 15:12:57 +08:00

100 lines
5.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Hole executors (hole_blind / hole_countersink / hole_counterbore / hole_wizard)."""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from ..registry import atomic_executor
from ..specs import HoleSpec, PlaneSpec, vector_scale
from ..topology import FeaturePlanNode, FeatureResult, RuntimeDiagnostic
from .common import _hole_starts, _host_plane
if TYPE_CHECKING: # pragma: no cover - import for type checkers only
from ..session import ExecutionSession
@atomic_executor("hole_blind", "hole_countersink", "hole_counterbore")
def _hole_executor(node: FeaturePlanNode, session: "ExecutionSession", sketch: dict[str, Any] | None) -> FeatureResult:
del sketch
return _execute_hole(node, session, wizard=False)
@atomic_executor("hole_wizard")
def _hole_wizard_executor(node: FeaturePlanNode, session: "ExecutionSession", sketch: dict[str, Any] | None) -> FeatureResult:
del sketch
return _execute_hole(node, session, wizard=True)
def _execute_hole(node: FeaturePlanNode, session: "ExecutionSession", *, wizard: bool = False) -> FeatureResult:
# 孔特征(hole)执行入口:在指定宿主面上按孔规格生成切除工具,并从主体上减去。
# 1. 校验:孔是切除操作,必须先有主体。
if session.body is None:
raise ValueError("hole feature has no body")
scope_feature_id = node.params.get("scope_feature_id")
if scope_feature_id is not None:
if not isinstance(scope_feature_id, str) or not scope_feature_id:
raise ValueError("hole scope_feature_id is invalid")
if len(session.body_members) != 1:
raise ValueError("hole scope body is no longer the sole active member")
scoped_body = session.body_members.get(scope_feature_id)
if scoped_body is None:
raise ValueError("hole scope body is no longer an independently selectable member")
scoped_solids = session.adapter.body_solids(scoped_body)
active_solids = session.adapter.body_solids(session.body)
if (
len(scoped_solids) != 1
or len(active_solids) != 1
or not scoped_solids[0].is_same(active_solids[0])
):
raise ValueError("hole scope body does not match the active body")
# 2. 确定宿主面 host_face
host_selector = node.params.get("host_face")
if isinstance(host_selector, dict) and isinstance(host_selector.get("frame"), dict):
# 若直接带 frame(平面定义),则以该平面为宿主,孔位按局部坐标解释。
host = PlaneSpec.from_mapping(host_selector["frame"])
positions_are_local = True
else:
# 否则从特征选择器中取 face,解析出宿主平面,孔位按世界坐标解释。
selectors = list(node.selectors)
if isinstance(host_selector, dict):
selectors.append(host_selector)
selector = next((item for item in selectors if item.get("kind") == "face"), None)
if selector is None:
raise ValueError("hole requires host_face selector or frame")
host = _host_plane(session.resolve(selector))
positions_are_local = False
# 3. 解析孔规格 HoleSpec(直径、深度、类型等,wizard 模式提供额外默认值)。
spec = HoleSpec.from_feature(node.atomic_id, node.params, wizard=wizard)
# 4. A host-face normal is an outward B-rep orientation, so its inverse
# always enters the material. Inferring direction from the global body
# centre fails for concave or multi-leg parts: for example, the top face
# of an L bracket can sit below the whole body's centre and the old rule
# drilled outward, producing a no-op feature reported as successful.
# The selected topology face is the local, authoritative orientation.
inward = vector_scale(host.normal, -1)
# 5. 生成孔切除工具:按孔规格、起始位置、内方向及“贯穿到主体底面”的深度构造工具实体。
tool = session.adapter.hole_tool(
spec,
_hole_starts(spec, host_plane=host, positions_are_local=positions_are_local),
inward,
session.adapter.body_span(session.body, inward) + 2.0,
)
# 6. 从主体上减去工具实体,登记新主体并返回结果。
# thread 是装饰螺纹(无螺距、不进实体几何,SolidWorks/STEP 的螺纹孔
# 即光滑孔):孔按光滑圆柱孔执行,同时记录 info 级诊断便于批量报告
# 追溯降级数量(issue #9capabilities 已不再拒绝 thread)。
diagnostics: list[RuntimeDiagnostic] = []
if wizard and node.params.get("thread"):
diagnostics.append(RuntimeDiagnostic(
code="thread_decoration_ignored",
message="Thread decoration is not modeled; the hole falls back to a plain cylindrical bore",
feature_id=node.feature_id,
))
result_body = session.adapter.cut(session.body, tool)
members = {scope_feature_id: result_body} if scope_feature_id is not None else None
session.register_body(
node.feature_id, result_body, replay_node=node, body_members=members,
)
return session.result(node, diagnostics=diagnostics)