994d06aaea
- 新增 selector_candidate_demo,移除 provenance intent 后枚举候选 selector - 对候选分支执行有界重建与严格 STEP 比较 - 仅在候选遍历完整且唯一 strict 通过时生成 selector 映射记录 - 增加 selector 候选搜索、预算限制和记录生成的测试 - 保持生产 selector resolver 不受 Demo 逻辑影响 - 更新 CADFS 能力台账,记录 IMPRINT 派生 profile 的 lineage selector 缺口
248 lines
10 KiB
Python
248 lines
10 KiB
Python
"""Session-based CDSL execution entry points.
|
|
|
|
The runtime was split into focused modules (behavior-preserving move):
|
|
|
|
- ``registry``: atomic executor registry, ``atomic_executor`` decorator, and
|
|
the ``execute_node`` dispatcher.
|
|
- ``executors/``: one module per executor family; importing the package
|
|
performs the registration and verifies registry completeness.
|
|
- ``session``: ``ExecutionSession`` and the ``GeometryAdapter`` protocol.
|
|
- ``runtime_base``: shared error types and the ``ExtentVector`` value.
|
|
- ``extents``: end-condition planning.
|
|
- ``pattern_transform``: translate/mirror/rotate parameter algebra for replay.
|
|
|
|
This module keeps the ``analyze_cdsl`` / ``rebuild_cdsl`` entry points and
|
|
re-exports the historical ``cdsl_engine.runtime`` names, including the
|
|
private helpers referenced by the test suite, so every existing import keeps
|
|
working.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from copy import deepcopy
|
|
from dataclasses import dataclass, field
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from . import executors # noqa: F401 (importing performs executor registration)
|
|
from .capabilities import CapabilityAnalyzer, pattern_transform_blocker, sketch_ids_required_by_contract
|
|
# Historical private name still imported by the test suite.
|
|
from .executors.primitives import _execute_box # noqa: F401
|
|
from .executors.common import _selector_edges # noqa: F401
|
|
from .extents import (
|
|
_extent_reference,
|
|
_extent_vectors,
|
|
_extent_vectors_from_normal,
|
|
_normal_from_sketch,
|
|
_side_extent_vectors,
|
|
_targeted_extent_vector,
|
|
)
|
|
from .pattern_transform import (
|
|
_box_circular_is_exact,
|
|
_coordinate_axis_direction,
|
|
_mirrored_node,
|
|
_mirrored_sketch,
|
|
_normal_is_coordinate_axis,
|
|
_owner_plane_frame,
|
|
_pattern_operation_node,
|
|
_reflect_point,
|
|
_rotated_node,
|
|
_rotated_point,
|
|
_rotated_sketch,
|
|
_rotated_vector,
|
|
_transformed_loft_profiles,
|
|
_translated_node,
|
|
_translated_sketch,
|
|
)
|
|
from .registry import (
|
|
ALL_ATOMIC_IDS,
|
|
EXECUTORS,
|
|
AtomicExecutor,
|
|
ExecutorFunction,
|
|
atomic_executor,
|
|
execute_node,
|
|
)
|
|
from .runtime_base import ExtentVector, FeatureExecutionError, RuntimeExecutionError
|
|
from .session import ExecutionSession, GeometryAdapter
|
|
from .sketch_solver import CORE_SHAPE_GENERATORS, resolve_required_sketches
|
|
from .specs import (
|
|
AxisSpec, BendSpec, GearSpec, HoleSpec, PlaneSpec, RackSpec, ThreadSpec, Vector3,
|
|
pattern_instance_member_id, transform_copy_member_id,
|
|
vector_add, vector_cross, vector_dot, vector_scale, vector_subtract, vector_unit,
|
|
)
|
|
from .topology import (
|
|
CapabilityResult, FeaturePlanNode, FeatureResult, RuntimeDiagnostic,
|
|
SelectorResolution, TopologyDelta, TopologyDeltaRelation, TopologyRecord, TopologyRegistry,
|
|
)
|
|
|
|
# Historical private imports used by integration tests and local diagnostics.
|
|
_execute_node = execute_node
|
|
|
|
__all__ = [
|
|
"ALL_ATOMIC_IDS",
|
|
"EXECUTORS",
|
|
"ExecutionSession",
|
|
"ExecutorFunction",
|
|
"ExtentVector",
|
|
"FeatureExecutionError",
|
|
"FeaturePlanNode",
|
|
"FeatureResult",
|
|
"GeometryAdapter",
|
|
"RuntimeDiagnostic",
|
|
"RuntimeExecutionError",
|
|
"IncrementalCdslExecution",
|
|
"analyze_cdsl",
|
|
"prepare_cdsl_execution",
|
|
"finalize_cdsl_execution",
|
|
"execute_node",
|
|
"rebuild_cdsl",
|
|
]
|
|
|
|
|
|
def analyze_cdsl(cdsl: dict[str, Any]):
|
|
"""Resolve profiles and return the current runtime capability analysis."""
|
|
sketch_errors: dict[str, str] = {}
|
|
resolved = resolve_required_sketches(
|
|
deepcopy(cdsl), sketch_ids_required_by_contract(cdsl), errors=sketch_errors,
|
|
)
|
|
analyzer = CapabilityAnalyzer(atomic_ids=EXECUTORS, profile_types=CORE_SHAPE_GENERATORS)
|
|
return analyzer.analyze(resolved, sketch_errors=sketch_errors)
|
|
|
|
|
|
def _preflight_error(analysis: Any) -> ValueError | None:
|
|
if analysis.runtime_eligible:
|
|
return None
|
|
first = next((result for result in analysis.feature_results if not result.executable), None)
|
|
if first is None:
|
|
return ValueError(analysis.document_blockers[0].code)
|
|
if any(blocker.code == "unknown_atomic" for blocker in first.blockers):
|
|
return ValueError(f"unsupported atomic_id: {first.atomic_id}")
|
|
detail = "; ".join(blocker.code for blocker in first.blockers)
|
|
return ValueError(f"Feature {first.feature_id} is not runtime eligible: {detail}")
|
|
|
|
|
|
def _execution_diagnostic(
|
|
error: Exception,
|
|
node: FeaturePlanNode,
|
|
session: ExecutionSession,
|
|
) -> RuntimeDiagnostic:
|
|
failed_resolution = next(
|
|
(item for item in reversed(session.selector_resolutions) if item["status"] != "resolved"), None,
|
|
)
|
|
if isinstance(error, FeatureExecutionError):
|
|
return RuntimeDiagnostic(error.code, str(error), feature_id=node.feature_id, detail=error.detail)
|
|
if failed_resolution and failed_resolution.get("diagnostic"):
|
|
diagnostic = failed_resolution["diagnostic"]
|
|
return RuntimeDiagnostic(
|
|
diagnostic["code"], diagnostic["message"], feature_id=node.feature_id,
|
|
detail=diagnostic.get("detail") or {},
|
|
)
|
|
return RuntimeDiagnostic("execution_failed", str(error), feature_id=node.feature_id)
|
|
|
|
|
|
@dataclass
|
|
class IncrementalCdslExecution:
|
|
"""One prepared CDSL replay with feature-at-a-time execution.
|
|
|
|
CADFS selector binding consumes this object before each feature executes.
|
|
It therefore observes the exact session topology snapshots generated by
|
|
the one real kernel replay rather than rebuilding a growing prefix for
|
|
every selector. The generic runtime also uses it through ``rebuild_cdsl``.
|
|
"""
|
|
|
|
resolved_cdsl: dict[str, Any]
|
|
analysis: Any
|
|
session: ExecutionSession
|
|
diagnostics: list[RuntimeDiagnostic] = field(default_factory=list)
|
|
next_index: int = 0
|
|
|
|
def execute_next(self, *, strict: bool = True) -> FeatureResult | None:
|
|
if self.next_index >= len(self.analysis.plan):
|
|
raise ValueError("CDSL execution plan is already complete")
|
|
node = self.analysis.plan[self.next_index]
|
|
preflight = self.analysis.feature_results[self.next_index]
|
|
self.next_index += 1
|
|
if not preflight.executable:
|
|
self.diagnostics.extend(preflight.blockers)
|
|
if strict:
|
|
detail = "; ".join(blocker.code for blocker in preflight.blockers)
|
|
raise ValueError(f"Feature {node.feature_id} is not runtime eligible: {detail}")
|
|
return None
|
|
try:
|
|
return execute_node(node, self.session)
|
|
except Exception as error:
|
|
diagnostic = _execution_diagnostic(error, node, self.session)
|
|
self.diagnostics.append(diagnostic)
|
|
if strict:
|
|
raise RuntimeExecutionError(diagnostic, list(self.session.selector_resolutions)) from error
|
|
return None
|
|
|
|
def execute_all(self, *, strict: bool = True) -> None:
|
|
while self.next_index < len(self.analysis.plan):
|
|
self.execute_next(strict=strict)
|
|
|
|
|
|
def prepare_cdsl_execution(cdsl: dict[str, Any]) -> IncrementalCdslExecution:
|
|
"""Resolve CDSL once and return a reusable sequential execution session."""
|
|
sketch_errors: dict[str, str] = {}
|
|
resolved = resolve_required_sketches(
|
|
deepcopy(cdsl), sketch_ids_required_by_contract(cdsl), errors=sketch_errors,
|
|
)
|
|
analysis = CapabilityAnalyzer(atomic_ids=EXECUTORS, profile_types=CORE_SHAPE_GENERATORS).analyze(
|
|
resolved, sketch_errors=sketch_errors,
|
|
)
|
|
session = ExecutionSession(
|
|
sketches={str(sketch.get("id")): sketch for sketch in (resolved.get("geometry") or {}).get("sketches") or []},
|
|
nodes={node.feature_id: node for node in analysis.plan},
|
|
)
|
|
return IncrementalCdslExecution(resolved, analysis, session)
|
|
|
|
|
|
def finalize_cdsl_execution(execution: IncrementalCdslExecution, out_step: Path) -> dict[str, Any]:
|
|
"""Export the current executable checkpoint from an incremental replay."""
|
|
session = execution.session
|
|
output = session.body
|
|
surface_geometry: dict[str, Any] | None = None
|
|
if output is None:
|
|
if not session.surface_members:
|
|
raise ValueError("CDSL execution produced no body")
|
|
# 纯曲面文档没有 active solid,但依然是可执行的 CAD 结果。只有在
|
|
# 没有实体时才将 surface members 作为 STEP 输出,混合模型继续只导出
|
|
# 实体,避免曲面意外改变既有实体比较和下游消费语义。
|
|
output = session.adapter.combine_surfaces(*session.surface_members.values())
|
|
surface_geometry = session.adapter.surface_geometry(output)
|
|
out_step.parent.mkdir(parents=True, exist_ok=True)
|
|
session.adapter.export(output, str(out_step))
|
|
geometry = session.adapter.body_geometry(session.body) if session.body is not None else surface_geometry
|
|
if geometry is None:
|
|
raise ValueError("CDSL execution produced no exportable geometry")
|
|
bbox = geometry["bbox_mm"]
|
|
return {
|
|
"engine": "cdsl_session_runtime",
|
|
"out_step": str(out_step),
|
|
"volume_mm3": float(geometry.get("volume_mm3") or 0.0),
|
|
"bbox_mm": {"min": bbox[:3], "max": bbox[3:]},
|
|
# #7 multi-body:重建结果里的独立实体数(Compound 成员数),
|
|
# 与 batch 验证的 document_truth.geometry.solid_body_count 对齐。
|
|
"solid_count": len(session.adapter.body_solids(session.body)) if session.body is not None else 0,
|
|
"surface_count": len(session.surface_members),
|
|
"surface_face_count": int(surface_geometry["face_count"]) if surface_geometry is not None else 0,
|
|
"surface_area_mm2": float(surface_geometry["area_mm2"]) if surface_geometry is not None else 0.0,
|
|
"feature_results": [result.as_dict() for result in session.results.values()],
|
|
"runtime_diagnostics": [diagnostic.as_dict() for diagnostic in execution.diagnostics],
|
|
"topology_records": [record.public_dict() for record in session.topology.records()],
|
|
"topology_deltas": list(session.topology.topology_deltas()),
|
|
"selector_resolution": session.selector_resolutions,
|
|
}
|
|
|
|
|
|
def rebuild_cdsl(cdsl: dict[str, Any], out_step: Path, *, strict: bool = True) -> dict[str, Any]:
|
|
"""Rebuild CDSL through session-scoped atomic executors only."""
|
|
execution = prepare_cdsl_execution(cdsl)
|
|
if strict:
|
|
error = _preflight_error(execution.analysis)
|
|
if error is not None:
|
|
raise error
|
|
execution.execute_all(strict=strict)
|
|
return finalize_cdsl_execution(execution, out_step)
|