Files
cdsl-cad/backend/cdsl_importer/legacy_profile_adapter.py
T
2026-08-25 17:41:24 +08:00

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