118 lines
4.4 KiB
Python
118 lines
4.4 KiB
Python
"""Explicit import/rebuild compatibility for retired CDSL profile macros.
|
|
|
|
The CDSL runtime accepts only direct geometric profiles. This adapter is for
|
|
historical artifacts and importers: it expands a legacy shorthand exactly once
|
|
into an ``analytic_contours`` profile before generic runtime validation.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import copy
|
|
from typing import Any
|
|
|
|
from .legacy_profiles import LEGACY_PROFILE_GENERATORS
|
|
|
|
|
|
class LegacyProfileError(ValueError):
|
|
"""A legacy profile could not be lowered to generic CDSL geometry."""
|
|
|
|
|
|
def legacy_profile_types() -> tuple[str, ...]:
|
|
return tuple(sorted(LEGACY_PROFILE_GENERATORS))
|
|
|
|
|
|
def _segment_from_edge(edge: dict[str, Any]) -> dict[str, Any]:
|
|
kind = str(edge.get("type") or "")
|
|
if kind == "line":
|
|
return {
|
|
"type": "line",
|
|
"start": list(edge["start_mm"][:2]),
|
|
"end": list(edge["end_mm"][:2]),
|
|
}
|
|
if kind == "arc":
|
|
segment = {
|
|
"type": "arc",
|
|
"start": list(edge["start_mm"][:2]),
|
|
"end": list(edge["end_mm"][:2]),
|
|
"center": list(edge["center_mm"][:2]),
|
|
"radius_mm": float(edge["radius_mm"]),
|
|
}
|
|
if "clockwise" in edge:
|
|
segment["clockwise"] = bool(edge["clockwise"])
|
|
return segment
|
|
raise LegacyProfileError(f"Legacy profile emitted unsupported edge type: {kind}")
|
|
|
|
|
|
def _circle_contour(entity: dict[str, Any]) -> dict[str, Any]:
|
|
radius = entity.get("radius_mm")
|
|
if not isinstance(radius, (int, float)) or float(radius) <= 0:
|
|
raise LegacyProfileError("Legacy circle must have radius_mm > 0")
|
|
center = entity.get("center") or [0.0, 0.0]
|
|
if not isinstance(center, list) or len(center) < 2:
|
|
raise LegacyProfileError("Legacy circle must have a two-dimensional center")
|
|
return {
|
|
"role": "outer",
|
|
"closed": True,
|
|
"segments": [{"type": "circle", "center": [float(center[0]), float(center[1])], "radius_mm": float(radius)}],
|
|
}
|
|
|
|
|
|
def _edge_contour(edges: list[dict[str, Any]], *, role: str) -> dict[str, Any]:
|
|
return {
|
|
"role": role,
|
|
"closed": True,
|
|
"segments": [_segment_from_edge(edge) for edge in edges],
|
|
}
|
|
|
|
|
|
def lower_legacy_profile(profile: dict[str, Any], *, sketch_id: str = "") -> dict[str, Any]:
|
|
"""Expand one named legacy profile to a direct analytic contour profile."""
|
|
kind = str(profile.get("type") or "")
|
|
generator = LEGACY_PROFILE_GENERATORS.get(kind)
|
|
if generator is None:
|
|
raise LegacyProfileError(f"Unsupported legacy profile: {kind!r}")
|
|
meta: dict[str, Any] = {"id": sketch_id, "_entities": None, "_contour": None}
|
|
try:
|
|
entities, contour = generator(copy.deepcopy(profile), meta)
|
|
except (KeyError, TypeError, ValueError) as error:
|
|
raise LegacyProfileError(f"Could not lower legacy profile {kind!r}: {error}") from error
|
|
|
|
contours: list[dict[str, Any]] = []
|
|
if contour:
|
|
contours.append(_edge_contour(contour, role="outer"))
|
|
for region in meta.get("_regions") or []:
|
|
if not isinstance(region, dict):
|
|
continue
|
|
outer = region.get("outer") or []
|
|
if outer:
|
|
contours.append(_edge_contour(outer, role="outer"))
|
|
for hole in region.get("holes") or []:
|
|
if hole:
|
|
contours.append(_edge_contour(hole, role="inner"))
|
|
for entity in entities or []:
|
|
if entity.get("type") == "circle" and not entity.get("construction"):
|
|
contours.append(_circle_contour(entity))
|
|
if not contours:
|
|
raise LegacyProfileError(f"Legacy profile {kind!r} did not produce a closed contour")
|
|
return {"type": "analytic_contours", "contours": contours}
|
|
|
|
|
|
def lower_legacy_profiles(cdsl: dict[str, Any]) -> dict[str, Any]:
|
|
"""Return a copy with every self-contained legacy sketch lowered.
|
|
|
|
This is opt-in. Calling the runtime directly with a legacy profile still
|
|
returns an unsupported-profile diagnostic.
|
|
"""
|
|
lowered = copy.deepcopy(cdsl)
|
|
sketches = ((lowered.get("geometry") or {}).get("sketches") or [])
|
|
for sketch in sketches:
|
|
if not isinstance(sketch, dict):
|
|
continue
|
|
profile = sketch.get("profile")
|
|
if not isinstance(profile, dict):
|
|
continue
|
|
if str(profile.get("type") or "") not in LEGACY_PROFILE_GENERATORS:
|
|
continue
|
|
sketch["profile"] = lower_legacy_profile(profile, sketch_id=str(sketch.get("id") or ""))
|
|
return lowered
|