"""Transport-free LightBurn MOPA import seam; never authorizes encoding.""" from __future__ import annotations from decimal import Decimal, InvalidOperation import xml.etree.ElementTree as ET UNIT_CONTRACT = "frequency=Hz;QPulseWidth=ns" UNIT_CONTRACT_APP_VERSION = "2.1.03" UNIT_CONTRACT_EVIDENCE = { "status": "operator-verified-ui-project-correlation", "lightburn_app_version": UNIT_CONTRACT_APP_VERSION, "frequency_serialized_unit": "Hz", "frequency_ui_unit": "kHz", "frequency_serialized_units_per_ui_unit": 1000, "q_pulse_width_serialized_unit": "ns", "q_pulse_width_ui_unit": "ns", "analysis_ref": ( "docs/research/lightburn-control-machine-observation-2026-10-24.md" ), } class ProjectImportError(ValueError): """Ambiguous or malformed process metadata.""" def _number(element: ET.Element, field: str) -> str | None: children = element.findall(field) if not children: return None if len(children) != 1 or set(children[0].attrib) != {"Value"}: raise ProjectImportError(f"ambiguous {field}") raw = children[0].get("Value", "") try: value = Decimal(raw) except InvalidOperation as exc: raise ProjectImportError(f"invalid {field}") from exc if not value.is_finite() or value <= 0 or value > Decimal("1e12"): raise ProjectImportError(f"invalid {field}") return raw def _layer_id(element: ET.Element) -> str | None: children = element.findall("index") if not children: return None if len(children) != 1 or set(children[0].attrib) != {"Value"}: raise ProjectImportError("ambiguous layer index") raw = children[0].get("Value", "") if not raw.isdecimal(): raise ProjectImportError("invalid layer index") return str(int(raw)) def import_project_mopa(data: bytes, *, unit_contract: str | None = None) -> dict: """Preserve observed XML fields and optionally normalize declared units. The caller must opt into the independently established LightBurn 2.1.03 serialized-unit contract. No unit is inferred from magnitude. Missing pulse width is never defaulted. Results are offline metadata, not executable Rayforge workflow settings. """ if unit_contract not in (None, UNIT_CONTRACT): raise ProjectImportError("unsupported serialized-unit contract") if len(data) > 64 * 1024 * 1024: raise ProjectImportError("project exceeds offline import size limit") # Reject declarations, including UTF-16/32 encodings, before XML parsing. probe = data.replace(b"\x00", b"").upper() if b" 1 or (modes and set(modes[0].attrib) != {"Value"}): raise ProjectImportError("ambiguous image mode") image_mode = modes[0].get("Value") if modes else None normalized = None if unit_contract and all(value is not None for value in raw.values()): normalized = { "frequency_hz": float(Decimal(raw["frequency"])), "frequency_khz": float(Decimal(raw["frequency"]) / 1000), "pulse_width_ns": float(Decimal(raw["QPulseWidth"])), } layers.append({"element": setting.tag, "type": kind, "source_layer_id": layer_id, "raw": raw, "image_mode": image_mode, "normalized": normalized, "blockers": blockers}) if not layers: raise ProjectImportError("project has no process settings") evidence = dict(UNIT_CONTRACT_EVIDENCE) if unit_contract else None return {"layers": layers, "unit_contract": unit_contract, "unit_contract_evidence": evidence, "lightburn_app_version": app_version, "transport": None, "encoding_allowed": False, "hardware_use_allowed": False, "blockers": ["machine identity, correction and limits unverified", "offline import seam is not wired to host workflow"]}