feat(selector): 增加离线候选遍历与严格回放验证 Demo
- 新增 selector_candidate_demo,移除 provenance intent 后枚举候选 selector - 对候选分支执行有界重建与严格 STEP 比较 - 仅在候选遍历完整且唯一 strict 通过时生成 selector 映射记录 - 增加 selector 候选搜索、预算限制和记录生成的测试 - 保持生产 selector resolver 不受 Demo 逻辑影响 - 更新 CADFS 能力台账,记录 IMPRINT 派生 profile 的 lineage selector 缺口
This commit is contained in:
@@ -23,6 +23,7 @@ from .topology import (
|
||||
TopologyDelta,
|
||||
TopologyRecord,
|
||||
TopologyRegistry,
|
||||
validate_selector_provenance_intent,
|
||||
)
|
||||
|
||||
|
||||
@@ -38,6 +39,7 @@ class GeometryAdapter(Protocol):
|
||||
def body_geometry(self, body: Any) -> dict[str, Any]: ...
|
||||
def surface_geometry(self, surface: Any) -> dict[str, Any]: ...
|
||||
def faces_for_sketch(self, sketch: dict[str, Any]) -> list[Any]: ...
|
||||
def faces_for_sketch_with_source_anchors(self, sketch: dict[str, Any]) -> tuple[list[Any], list[dict[str, Any]]]: ...
|
||||
def face_with_holes(self, outer: Any, holes: list[Any]) -> Any: ...
|
||||
def loft(self, sketches: list[dict[str, Any]]) -> Any: ...
|
||||
def loft_with_topology_delta(self, sketches: list[dict[str, Any]]) -> tuple[Any, TopologyDelta | None]: ...
|
||||
@@ -115,6 +117,7 @@ class ExecutionSession:
|
||||
body_members: dict[str, Any] | None = None,
|
||||
topology_delta: TopologyDelta | None = None,
|
||||
topology_predecessors: list[TopologyRecord] | None = None,
|
||||
topology_anchors: list[TopologyRecord] | None = None,
|
||||
) -> None:
|
||||
# #7 multi-body:主体可能是 Compound(多个独立实体,例如两个不相交的
|
||||
# 拉伸)。body_id 现在反映真实实体结构而不是"最后一个特征的 id":
|
||||
@@ -123,12 +126,20 @@ class ExecutionSession:
|
||||
self.body = body
|
||||
self.body_id = f"body:{feature_id}"
|
||||
self.body_members = dict(body_members) if body_members is not None else {feature_id: body}
|
||||
# Source-profile anchors are transient construction facts, but unlike
|
||||
# generic role predecessors they must remain addressable by a later
|
||||
# selector intent. They never receive a body id, so active selector
|
||||
# scans cannot mistake them for current model topology.
|
||||
anchors = list(topology_anchors or ())
|
||||
for anchor in anchors:
|
||||
self.topology.register(anchor)
|
||||
predecessors = [*(topology_predecessors or ()), *anchors]
|
||||
solids = self.adapter.body_solids(body)
|
||||
if len(solids) <= 1:
|
||||
self.topology.replace_body_topology(
|
||||
feature_id, self.body_id, self.adapter.topology_records(body, feature_id, self.body_id),
|
||||
topology_delta=topology_delta,
|
||||
additional_predecessors=topology_predecessors or (),
|
||||
additional_predecessors=predecessors,
|
||||
)
|
||||
else:
|
||||
# 一个 Compound 的全部成员共享同一个前置 body snapshot。逐个登记会让
|
||||
@@ -141,7 +152,7 @@ class ExecutionSession:
|
||||
]
|
||||
self.topology.replace_body_topologies(
|
||||
feature_id, members, active_body_id=self.body_id, topology_delta=topology_delta,
|
||||
additional_predecessors=topology_predecessors or (),
|
||||
additional_predecessors=predecessors,
|
||||
)
|
||||
self.topology.register(TopologyRecord(
|
||||
record_id=self.body_id, kind="body", feature_id=feature_id, body_id=self.body_id,
|
||||
@@ -150,6 +161,25 @@ class ExecutionSession:
|
||||
if replay_node is not None:
|
||||
self.replay_definitions[feature_id] = replay_node
|
||||
|
||||
def register_transient_prism_tool(
|
||||
self,
|
||||
feature_id: str,
|
||||
tool: Any,
|
||||
*,
|
||||
topology_delta: TopologyDelta,
|
||||
topology_anchors: list[TopologyRecord],
|
||||
) -> list[TopologyRecord]:
|
||||
"""Keep one direct-prism primary tool as boolean-input evidence only."""
|
||||
snapshot_id = f"transient:{feature_id}"
|
||||
records = self.adapter.topology_records(tool, feature_id, snapshot_id)
|
||||
return list(self.topology.register_transient_snapshot(
|
||||
feature_id,
|
||||
snapshot_id,
|
||||
records,
|
||||
topology_delta=topology_delta,
|
||||
anchors=topology_anchors,
|
||||
))
|
||||
|
||||
def register_surface(self, feature_id: str, surface: Any) -> str:
|
||||
# 曲面 feature 与实体 body 生命周期相互独立:不能调用 register_body,
|
||||
# 否则 surface 会覆盖 active solid 并改变最终 STEP 的实体结果。
|
||||
@@ -236,6 +266,17 @@ class ExecutionSession:
|
||||
|
||||
def resolve(self, selector: dict[str, Any]) -> SelectorResolution:
|
||||
if selector.get("intersection_of") is not None:
|
||||
# The session computes vertex intersections directly from resolved
|
||||
# face components, so enforce the same outer provenance gate that
|
||||
# TopologyRegistry.resolve applies before any geometry operation.
|
||||
validation_error = validate_selector_provenance_intent(selector)
|
||||
if validation_error is not None:
|
||||
return self._record_selector_resolution(SelectorResolution(
|
||||
selector=selector,
|
||||
status="not_found",
|
||||
candidates=(),
|
||||
diagnostic=validation_error,
|
||||
))
|
||||
return self._record_selector_resolution(self._resolve_intersection_vertex(selector))
|
||||
owner = str(selector.get("owner_feature_id") or "")
|
||||
active_body_id = f"surface:{owner}" if owner in self.surface_members else self.body_id
|
||||
|
||||
Reference in New Issue
Block a user