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:
@@ -17,12 +17,28 @@ _Ctx = dict[str, Any]
|
||||
_TOLERANCE_MM = 1e-5
|
||||
|
||||
|
||||
def _circle(center: list[float], radius_mm: float, construction: bool = False) -> _Ctx:
|
||||
return {"type": "circle", "center": [float(center[0]), float(center[1])], "radius_mm": float(radius_mm), "construction": construction}
|
||||
def _circle(
|
||||
center: list[float],
|
||||
radius_mm: float,
|
||||
construction: bool = False,
|
||||
source_entity_id: str | None = None,
|
||||
) -> _Ctx:
|
||||
output = {"type": "circle", "center": [float(center[0]), float(center[1])], "radius_mm": float(radius_mm), "construction": construction}
|
||||
if source_entity_id is not None:
|
||||
output["source_entity_id"] = source_entity_id
|
||||
return output
|
||||
|
||||
|
||||
def _line(start: list[float], end: list[float], construction: bool = False) -> _Ctx:
|
||||
return {"type": "line", "start": [float(start[0]), float(start[1])], "end": [float(end[0]), float(end[1])], "construction": construction}
|
||||
def _line(
|
||||
start: list[float],
|
||||
end: list[float],
|
||||
construction: bool = False,
|
||||
source_entity_id: str | None = None,
|
||||
) -> _Ctx:
|
||||
output = {"type": "line", "start": [float(start[0]), float(start[1])], "end": [float(end[0]), float(end[1])], "construction": construction}
|
||||
if source_entity_id is not None:
|
||||
output["source_entity_id"] = source_entity_id
|
||||
return output
|
||||
|
||||
|
||||
def _point(point: list[float]) -> list[float]:
|
||||
@@ -118,7 +134,8 @@ def _gen_circle(profile: _Ctx, _: _Ctx) -> tuple[list[_Ctx], list[_Ctx]]:
|
||||
cx, cy = float(center[0]), float(center[1])
|
||||
# 直接圆 profile 必须保留为一条完整的圆边。若拆成四条圆弧,后续按边
|
||||
# 选择的圆角/倒角会把同一拓扑圆误解为四个独立目标。
|
||||
return [_circle([cx, cy], radius)], []
|
||||
source_entity_id = profile.get("source_entity_id")
|
||||
return [_circle([cx, cy], radius, source_entity_id=source_entity_id if isinstance(source_entity_id, str) else None)], []
|
||||
|
||||
|
||||
def _gen_polygon(profile: _Ctx, meta: _Ctx) -> tuple[list[_Ctx], list[_Ctx]]:
|
||||
@@ -215,7 +232,16 @@ def _circle_edges(segment: _Ctx) -> list[_Ctx]:
|
||||
clockwise = bool(segment.get("clockwise", False))
|
||||
angles = [0.0, -90.0, -180.0, -270.0, -360.0] if clockwise else [0.0, 90.0, 180.0, 270.0, 360.0]
|
||||
points = [[cx + radius * math.cos(math.radians(angle)), cy + radius * math.sin(math.radians(angle)), 0.0] for angle in angles]
|
||||
return [_contour_arc(points[index], points[index + 1], [cx, cy, 0.0], radius, clockwise) for index in range(4)]
|
||||
edges = [_contour_arc(points[index], points[index + 1], [cx, cy, 0.0], radius, clockwise) for index in range(4)]
|
||||
source_entity_id = segment.get("source_entity_id")
|
||||
if isinstance(source_entity_id, str) and source_entity_id:
|
||||
# Region construction needs four arc segments, but this marker records
|
||||
# that all four came from exactly one logical source circle. It is not
|
||||
# an edge anchor: callers must rebuild one native circle wire and pass
|
||||
# final-face identity checks before using it for lineage.
|
||||
for edge in edges:
|
||||
edge["logical_circle_source_entity_id"] = source_entity_id
|
||||
return edges
|
||||
|
||||
|
||||
def _ellipse_edges(segment: _Ctx) -> list[_Ctx]:
|
||||
@@ -242,14 +268,14 @@ def _ellipse_edges(segment: _Ctx) -> list[_Ctx]:
|
||||
def _segment_edges(segment: _Ctx) -> list[_Ctx]:
|
||||
kind = segment.get("type")
|
||||
if kind == "line":
|
||||
return [_contour_line(segment["start"], segment["end"])]
|
||||
if kind == "arc":
|
||||
return [_contour_arc(segment["start"], segment["end"], segment["center"], segment.get("radius_mm"), segment.get("clockwise"))]
|
||||
if kind == "circle":
|
||||
return _circle_edges(segment)
|
||||
if kind == "ellipse":
|
||||
return _ellipse_edges(segment)
|
||||
if kind == "bspline":
|
||||
edges = [_contour_line(segment["start"], segment["end"])]
|
||||
elif kind == "arc":
|
||||
edges = [_contour_arc(segment["start"], segment["end"], segment["center"], segment.get("radius_mm"), segment.get("clockwise"))]
|
||||
elif kind == "circle":
|
||||
edges = _circle_edges(segment)
|
||||
elif kind == "ellipse":
|
||||
edges = _ellipse_edges(segment)
|
||||
elif kind == "bspline":
|
||||
points = segment.get("points") or []
|
||||
if len(points) < 2:
|
||||
raise ValueError("analytic_contours: bspline needs at least 2 interpolation points")
|
||||
@@ -304,8 +330,18 @@ def _segment_edges(segment: _Ctx) -> list[_Ctx]:
|
||||
raise ValueError("analytic_contours: periodic bspline does not accept endpoint tangents")
|
||||
output["start_tangent_mm"] = _point(start_tangent)
|
||||
output["end_tangent_mm"] = _point(end_tangent)
|
||||
return [output]
|
||||
raise ValueError(f"analytic_contours: unsupported segment type {kind!r}")
|
||||
edges = [output]
|
||||
else:
|
||||
raise ValueError(f"analytic_contours: unsupported segment type {kind!r}")
|
||||
source_entity_id = segment.get("source_entity_id")
|
||||
# Only a one-edge construction has the direct, one-to-one source identity
|
||||
# required by profile-to-prism lineage. Circles expanded to arcs carry a
|
||||
# separate logical-circle marker, not a source edge identity; the adapter
|
||||
# may use it only to reconstruct one native circle wire with exact final
|
||||
# membership proof. Other multi-edge approximations remain unanchored.
|
||||
if isinstance(source_entity_id, str) and len(edges) == 1:
|
||||
edges[0]["source_entity_id"] = source_entity_id
|
||||
return edges
|
||||
|
||||
|
||||
def _imprint_segment_edges(segment: _Ctx) -> list[_Ctx]:
|
||||
@@ -466,11 +502,18 @@ def _gen_analytic_contours(profile: _Ctx, meta: _Ctx) -> tuple[list[_Ctx], list[
|
||||
raw_edges: list[_Ctx] = []
|
||||
for segment in contour.get("segments") or []:
|
||||
if segment.get("type") == "line":
|
||||
entities.append(_line(segment["start"], segment["end"]))
|
||||
entities.append(_line(
|
||||
segment["start"], segment["end"],
|
||||
source_entity_id=segment.get("source_entity_id") if isinstance(segment.get("source_entity_id"), str) else None,
|
||||
))
|
||||
elif segment.get("type") == "circle":
|
||||
if contour_open:
|
||||
raise ValueError(f"analytic_contours: open contour {index} cannot contain a full circle segment")
|
||||
entities.append(_circle(segment.get("center") or [0.0, 0.0], float(segment.get("radius_mm") or 0.0)))
|
||||
entities.append(_circle(
|
||||
segment.get("center") or [0.0, 0.0],
|
||||
float(segment.get("radius_mm") or 0.0),
|
||||
source_entity_id=segment.get("source_entity_id") if isinstance(segment.get("source_entity_id"), str) else None,
|
||||
))
|
||||
raw_edges.extend(_segment_edges(segment))
|
||||
if raw_edges:
|
||||
edges = _join(raw_edges, allow_open=contour_open)
|
||||
|
||||
Reference in New Issue
Block a user