038d38ed98
- 新增 loft、双向切除、through-all/up-to-next 等 CADFS lowering 与 engine 支持 - 支持多种 reference plane、B-spline profile 和 circular pattern replay - 保留 transform 历史,并烘焙安全的单源平移/旋转变换 - 改进 selector 绑定、拓扑快照和 pattern 变换处理 - 建立 17 个代表样本的转换、重建与比较回归工具链 - 补充 schema、author guidance、运行时和几何回归测试
106 lines
4.3 KiB
Python
106 lines
4.3 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 cdsl_importer.legacy_profile_adapter import lower_legacy_profiles
|
|
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", "extrude_cut_two_sided", "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", "polygon"})
|
|
# 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", "extrude_cut_two_sided", "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 []) >= 1 for region in sketch.get("contour_regions_mm") or () if isinstance(region, dict))
|
|
or len(sketch.get("contour_edges_mm") or ()) >= 1
|
|
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}")
|
|
# Corpus pools retain historical profiles on disk but evaluate them after
|
|
# the importer compatibility lowering used by the batch path.
|
|
cdsl = lower_legacy_profiles(cdsl)
|
|
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 CDSL file-stem selectors for one static phase pool.
|
|
|
|
Batch rebuild selection is keyed by the immutable filename rather than the
|
|
document's display ``part_id``. Imports retain the latter as source
|
|
provenance and it may differ only by case or normalisation, which makes it
|
|
unsuitable as a unique filesystem selector.
|
|
"""
|
|
selectors: 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):
|
|
selectors.append(path.name.removesuffix(".cdsl.json"))
|
|
return selectors
|
|
|
|
|
|
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")
|