Files
cdsl-cad/backend/engine/cdsl_engine/phase_pools.py
T
2026-08-24 10:01:21 +08:00

96 lines
3.7 KiB
Python

"""Deterministic phase-pool selection for CDSL runtime baselines.
Pool membership is intentionally input-based. It does not claim a part is
truth-verified; that remains the responsibility of ``batch_rebuild --build``.
"""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any
from .capabilities import sketch_ids_required_by_contract
from .semantic_validation import validate_semantic_cdsl
from .sketch_solver import resolve_required_sketches
P3_ATOMIC_IDS = frozenset({
"reference_plane", "reference_axis", "extrude_add_blind", "extrude_add_two_sided",
"extrude_cut_blind", "revolve_add", "revolve_cut",
})
P4_ATOMIC_IDS = P3_ATOMIC_IDS | frozenset({"hole_wizard"})
P6_ATOMIC_IDS = P4_ATOMIC_IDS | frozenset({"pattern_linear", "pattern_mirror"})
P3_PROFILE_TYPES = frozenset({"analytic_contours", "circle", "circles", "annulus"})
# Static pool membership asks whether the exported history is an extrude/
# revolve history. Whether a first cut has a preceding active body remains a
# runtime preflight question, not a reason to erase it from the input pool.
_P3_PRIMARY_ATOMICS = frozenset({
"extrude_add_blind", "extrude_add_two_sided", "extrude_cut_blind", "revolve_add", "revolve_cut",
})
def _p3_profile_ready(cdsl: dict[str, Any]) -> bool:
required_ids = sketch_ids_required_by_contract(cdsl)
errors: dict[str, str] = {}
try:
resolved = resolve_required_sketches(cdsl, required_ids, errors=errors)
except ValueError:
return False
if errors:
return False
sketches = {str(sketch.get("id")): sketch for sketch in (resolved.get("geometry") or {}).get("sketches") or ()}
for sketch_id in required_ids:
sketch = sketches.get(sketch_id) or {}
profile = sketch.get("profile") or {}
if profile.get("type") not in P3_PROFILE_TYPES:
return False
if not (
any(len(region.get("outer") or []) >= 2 for region in sketch.get("contour_regions_mm") or () if isinstance(region, dict))
or len(sketch.get("contour_edges_mm") or ()) >= 2
or any(
entity.get("type") == "circle" and not entity.get("construction") and float(entity.get("radius_mm") or 0.0) > 0
for entity in sketch.get("entities") or ()
if isinstance(entity, dict)
)
):
return False
return True
_PHASE_ATOMIC_IDS = {"p3": P3_ATOMIC_IDS, "p4": P4_ATOMIC_IDS, "p6": P6_ATOMIC_IDS}
def is_static_phase_ready(cdsl: dict[str, Any], phase: str) -> bool:
"""Return whether CDSL belongs to a documented static phase input pool."""
allowed = _PHASE_ATOMIC_IDS.get(phase)
if allowed is None:
raise ValueError(f"Unknown CDSL runtime phase {phase!r}")
semantic = validate_semantic_cdsl(cdsl)
if semantic["unresolved"]:
return False
atoms = {str(feature.get("atomic_id") or "") for feature in cdsl.get("features") or ()}
if not atoms <= allowed:
return False
if not any(atom in _P3_PRIMARY_ATOMICS for atom in atoms):
return False
return _p3_profile_ready(cdsl)
def select_static_phase_pool(cdsl_dir: Path, phase: str) -> list[str]:
"""Return sorted part ids in one documented static phase input pool."""
part_ids: list[str] = []
for path in sorted(cdsl_dir.glob("*.cdsl.json")):
document: dict[str, Any] = json.loads(path.read_text(encoding="utf-8"))
if is_static_phase_ready(document, phase):
part_ids.append(str(document.get("part_id") or path.name.removesuffix(".cdsl.json")))
return part_ids
def is_p3_static_ready(cdsl: dict[str, Any]) -> bool:
return is_static_phase_ready(cdsl, "p3")
def select_p3_static_pool(cdsl_dir: Path) -> list[str]:
return select_static_phase_pool(cdsl_dir, "p3")