feat(cadfs): expand selector provenance coverage

This commit is contained in:
2026-09-14 11:00:14 +08:00
parent 994d06aaea
commit de21ee8a5b
45 changed files with 19837 additions and 842 deletions
@@ -17,6 +17,60 @@ if TYPE_CHECKING: # pragma: no cover - import for type checkers only
from ..session import ExecutionSession
def _single_member_dressup_members(
node: FeaturePlanNode,
session: "ExecutionSession",
body: Any,
selected_edges: list[Any],
) -> dict[str, Any] | None:
"""Preserve unchanged body members after one exact-member dress-up.
The adapter only reports Compound history when every selected edge maps to
one source solid. Keep the same proof at the body-graph layer: all other
members must appear unchanged in the result and exactly one result solid
must remain for the changed member. Otherwise aggregate replay remains
valid, but no member-lifecycle transfer is asserted.
"""
if not selected_edges or len(session.body_members) < 2:
return None
source_members: dict[str, Any] = {}
for member_id, member in session.body_members.items():
solids = session.adapter.body_solids(member)
if len(solids) != 1:
return None
source_members[member_id] = solids[0]
selected_members = {
member_id
for edge in selected_edges
for member_id, member in source_members.items()
if any(edge.is_same(candidate) for candidate in member.edges())
}
if len(selected_members) != 1:
return None
changed_member_id = next(iter(selected_members))
result_solids = session.adapter.body_solids(body)
unchanged: dict[str, Any] = {}
matched_result_indexes: set[int] = set()
for member_id, member in source_members.items():
if member_id == changed_member_id:
continue
matches = [
index for index, result in enumerate(result_solids)
if session.topology._same_topology_value(member, result)
]
if len(matches) != 1 or matches[0] in matched_result_indexes:
return None
matched_result_indexes.add(matches[0])
unchanged[member_id] = result_solids[matches[0]]
changed = [
result for index, result in enumerate(result_solids)
if index not in matched_result_indexes
]
if len(changed) != 1:
return None
return {**unchanged, node.feature_id: changed[0]}
def _execute_fillet(node: FeaturePlanNode, session: "ExecutionSession") -> FeatureResult:
# 圆角特征(fillet)执行入口:对选中边按半径做圆角,平滑尖角与棱边。
@@ -28,11 +82,15 @@ def _execute_fillet(node: FeaturePlanNode, session: "ExecutionSession") -> Featu
if radius <= 0:
raise ValueError("fillet radius_mm must be > 0")
# 3. 解析目标边(支持 tangent_propagation 相切传播),并执行圆角。
edges = _selector_edges(node, session, tangent_propagation=bool(node.params.get("tangent_propagation")))
body, topology_delta = session.adapter.fillet_with_topology_delta(
session.body, radius, _selector_edges(node, session, tangent_propagation=bool(node.params.get("tangent_propagation"))),
session.body, radius, edges,
)
# 4. 登记新主体并返回结果。
session.register_body(node.feature_id, body, replay_node=node, topology_delta=topology_delta)
session.register_body(
node.feature_id, body, replay_node=node, topology_delta=topology_delta,
body_members=_single_member_dressup_members(node, session, body, edges),
)
return session.result(node)
@@ -86,7 +144,10 @@ def _execute_chamfer(node: FeaturePlanNode, session: "ExecutionSession") -> Feat
detail={"distance_mm": distance, "surface_count": len(session.surface_members)},
))
# 5. 登记新主体并返回结果。
session.register_body(node.feature_id, body, replay_node=node, topology_delta=topology_delta)
session.register_body(
node.feature_id, body, replay_node=node, topology_delta=topology_delta,
body_members=_single_member_dressup_members(node, session, body, edges),
)
return session.result(node, diagnostics=diagnostics)