"""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