133 lines
6.2 KiB
Python
133 lines
6.2 KiB
Python
"""Body-graph executors (boolean_bodies / transform_bodies / delete_bodies).
|
|
|
|
These operate on explicitly named body members instead of the aggregate
|
|
session body, so adjacent independent solids never accidentally become tools
|
|
or targets of one another.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
from ..registry import atomic_executor
|
|
from ..specs import transform_copy_member_id
|
|
from ..topology import FeaturePlanNode, FeatureResult, TopologyDelta
|
|
from .common import _combine_members, _member_sources
|
|
|
|
if TYPE_CHECKING: # pragma: no cover - import for type checkers only
|
|
from ..session import ExecutionSession
|
|
|
|
|
|
def _execute_boolean_bodies(node: FeaturePlanNode, session: "ExecutionSession") -> FeatureResult:
|
|
# booleanBodies 总是作用于 source feature 的明确 body 输出,不能回退为
|
|
# 当前聚合 body。这样相邻独立实体不会意外成为工具或目标。
|
|
params = node.params
|
|
target_ids = _member_sources(
|
|
node, session, "target_feature_ids", pattern_instance_parameter="target_pattern_instance_refs",
|
|
allow_transform_copies=True, transform_copy_parameter="target_transform_copy_refs",
|
|
)
|
|
tool_ids = _member_sources(
|
|
node, session, "tool_feature_ids", pattern_instance_parameter="tool_pattern_instance_refs",
|
|
allow_transform_copies=True, transform_copy_parameter="tool_transform_copy_refs",
|
|
)
|
|
targets = {feature_id: session.body_members[feature_id] for feature_id in target_ids}
|
|
tools = {feature_id: session.body_members[feature_id] for feature_id in tool_ids}
|
|
target = _combine_members(session, targets)
|
|
tool = _combine_members(session, tools)
|
|
operation = str(params.get("operation") or "")
|
|
topology_delta: TopologyDelta | None = None
|
|
if operation == "union":
|
|
result, topology_delta = session.adapter.fuse_with_topology_delta(target, tool)
|
|
elif operation == "subtract":
|
|
result, topology_delta = session.adapter.cut_with_topology_delta(target, tool)
|
|
elif operation == "intersect":
|
|
result, topology_delta = session.adapter.intersect_with_topology_delta(target, tool)
|
|
else:
|
|
raise ValueError(f"unsupported booleanBodies operation {operation!r}")
|
|
members = {
|
|
feature_id: body
|
|
for feature_id, body in session.body_members.items()
|
|
if feature_id not in set(target_ids + tool_ids)
|
|
}
|
|
members[node.feature_id] = result
|
|
if bool(params.get("keep_tools")):
|
|
# In a targetless FeatureScript body-set operation every selected
|
|
# member is semantically a tool. Lowering chooses the first source
|
|
# member only to satisfy CDSL's binary executor shape, so retain that
|
|
# left operand too when the explicit targetless contract requests it.
|
|
if bool(params.get("targetless_body_set")):
|
|
members.update(targets)
|
|
members.update(tools)
|
|
session.register_body(
|
|
node.feature_id, _combine_members(session, members), body_members=members, topology_delta=topology_delta,
|
|
)
|
|
return session.result(node)
|
|
|
|
|
|
@atomic_executor("boolean_bodies")
|
|
def _boolean_bodies_executor(node: FeaturePlanNode, session: "ExecutionSession", sketch: dict[str, Any] | None) -> FeatureResult:
|
|
del sketch
|
|
return _execute_boolean_bodies(node, session)
|
|
|
|
|
|
def _execute_transform_bodies(node: FeaturePlanNode, session: "ExecutionSession") -> FeatureResult:
|
|
# FeatureScript transform targets explicit bodies. Do not move the
|
|
# aggregate session body, because it may include unrelated members.
|
|
source_ids = _member_sources(
|
|
node, session, "source_feature_ids", pattern_instance_parameter="pattern_instance_refs", allow_transform_copies=True,
|
|
)
|
|
make_copy = bool(node.params.get("make_copy"))
|
|
direct_sources = node.params.get("source_feature_ids") or []
|
|
if make_copy and isinstance(direct_sources, list) and len(direct_sources) > 1:
|
|
# The aggregate is only an export compound. Each source transform has
|
|
# its own B-rep builder and is the only output a later COPY query may
|
|
# select. Do not attach an aggregate topology delta to source members.
|
|
members = dict(session.body_members)
|
|
members.update({
|
|
transform_copy_member_id(node.feature_id, source_id): session.adapter.transform(
|
|
session.body_members[source_id], dict(node.params.get("transform") or {}),
|
|
)
|
|
for source_id in source_ids
|
|
})
|
|
session.register_body(
|
|
node.feature_id, _combine_members(session, members), body_members=members,
|
|
)
|
|
return session.result(node)
|
|
source = _combine_members(session, {feature_id: session.body_members[feature_id] for feature_id in source_ids})
|
|
transformed, topology_delta = session.adapter.transform_with_topology_delta(
|
|
source, dict(node.params.get("transform") or {}),
|
|
)
|
|
members = dict(session.body_members)
|
|
if not make_copy:
|
|
for feature_id in source_ids:
|
|
members.pop(feature_id)
|
|
members[node.feature_id] = transformed
|
|
session.register_body(
|
|
node.feature_id, _combine_members(session, members), body_members=members, topology_delta=topology_delta,
|
|
)
|
|
return session.result(node)
|
|
|
|
|
|
@atomic_executor("transform_bodies")
|
|
def _transform_bodies_executor(node: FeaturePlanNode, session: "ExecutionSession", sketch: dict[str, Any] | None) -> FeatureResult:
|
|
del sketch
|
|
return _execute_transform_bodies(node, session)
|
|
|
|
|
|
def _execute_delete_bodies(node: FeaturePlanNode, session: "ExecutionSession") -> FeatureResult:
|
|
# Deletion is a body-graph operation, never a Boolean subtraction. A
|
|
# selected member can be disjoint or overlap another independent body.
|
|
source_ids = _member_sources(node, session, "target_feature_ids")
|
|
members = {feature_id: body for feature_id, body in session.body_members.items() if feature_id not in set(source_ids)}
|
|
if members:
|
|
session.register_body(node.feature_id, _combine_members(session, members), body_members=members)
|
|
else:
|
|
session.clear_body()
|
|
return session.result(node)
|
|
|
|
|
|
@atomic_executor("delete_bodies")
|
|
def _delete_bodies_executor(node: FeaturePlanNode, session: "ExecutionSession", sketch: dict[str, Any] | None) -> FeatureResult:
|
|
del sketch
|
|
return _execute_delete_bodies(node, session)
|