Files
FacereDataset/tools/epro2/std/sch_writer.py
Knowit 3866e24189 tools/epro2/std: rewrite to Option 2 (objects dump) per downstream spec
Downstream came back with concrete requirements: don't pre-compute Std
shape[] tilde strings, just dump the raw EPRO2 `objects: {id: payload}`
dict and they'll write a ~100-LoC adapter on their side. Pulling the
tilde-mapping work back saves us from second-guessing positional fields
without their parser to verify against, and shortens our pcb_writer
from ~500 lines to ~40.

Output shape (Std envelope intact, just no `shape[]`):

    {
      "success": true, "code": 0,
      "result": {
        "uuid", "puuid", "title",
        "docType": 3 | 1,
        "components": {},
        "dataStr": {
          "head": {
            "docType": "3" | "1",
            "editorVersion": "facere-epro2/0.1 (epro2 <X.Y.Z>)",
            "units": "mil",
            "epro2_doc_uuid": ...,
            "epro2_editor_version": ...,
          },
          "BBox": {x, y, width, height},   # mil
          "layers": [...],                  # Std layer-string array
          "objects": dict(doc.objects),     # raw EPRO2, 1:1
          "preference": {}, "netColors": [], "DRCRULE": {},
        }
      }
    }

Per-doc spec downstream gave us:
  - shape[] dropped (empty placeholder misleads adapter)
  - all units mil (no mm conversion — Std canvas already declares mil)
  - head.units="mil" so adapter doesn't have to guess
  - BBox min/max across known x/y/startX/endX/centerX fields; adapter
    can refine by walking path arrays itself
  - layers[] keeps Std's 17-line default + inner SIGNAL layers actually
    used (21~Inner1.., 22~Inner2..)
  - empty stubs preference/netColors/DRCRULE for grep-based triage

New: docs/sources/epro2_to_std_mapping.md with the full EPRO2 OPTYPE →
Std verb table that downstream's adapter authors will copy from. Tables
include the layer-id remapping (the 5↔7 paste/mask flip, 11→10 outline,
12→11 multi, SIGNAL 15+→21+), PCB op mappings, SCH op mappings (marked
best-effort: no Std SCH samples in our corpus), and the 5-Voltage
placeholder COMPONENT → extra net flag trick. Extracted from the
previous Option-3 writer (commit fe6971f) so adapter writers don't
have to reverse-engineer it from source.

ESP-VoCat smoke: 6 PCB + 9 SCH = 15 JSON files, head.units=mil
preserved, no shape[] field present. 82 → 84 unit tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 01:41:12 +08:00

95 lines
2.7 KiB
Python

"""Convert one EPRO2 SCH_PAGE Document → an EasyEDA Std-shaped JSON file.
Same Option-2 contract as ``pcb_writer.py``: hand the raw EPRO2
``objects`` dict to a downstream adapter; don't pre-compute Std
``shape[]`` strings ourselves. docType=1, layers omitted (schematic
has no copper stack-up), BBox in mil, ``head.units = "mil"``.
"""
from __future__ import annotations
from dataclasses import dataclass
from ..replay import Document
@dataclass
class WriteStats:
objects: int = 0
bbox_x: float = 0.0
bbox_y: float = 0.0
bbox_w: float = 0.0
bbox_h: float = 0.0
_BBOX_POINT_FIELDS: list[tuple[str, str]] = [
("x", "y"),
("startX", "startY"),
("endX", "endY"),
("centerX", "centerY"),
]
def _gather_bbox_points(doc: Document) -> tuple[float, float, float, float]:
xs: list[float] = []
ys: list[float] = []
for obj in doc.objects.values():
for fx, fy in _BBOX_POINT_FIELDS:
x = obj.get(fx)
y = obj.get(fy)
if x is None or y is None:
continue
try:
xs.append(float(x))
ys.append(float(y))
except (TypeError, ValueError):
pass
if not xs:
return (0.0, 0.0, 0.0, 0.0)
return (min(xs), min(ys), max(xs) - min(xs), max(ys) - min(ys))
def write_sch_std(doc: Document) -> dict:
if doc.doc_type != "SCH_PAGE":
raise ValueError(f"expected SCH_PAGE doc, got {doc.doc_type!r}")
bbox_x, bbox_y, bbox_w, bbox_h = _gather_bbox_points(doc)
epro2_editor = (doc.head or {}).get("editVersion", "")
title = (doc.objects.get("META") or {}).get("title") or doc.doc_uuid[:12]
result = {
"uuid": doc.doc_uuid,
"puuid": "",
"title": title,
"description": "",
"docType": 1,
"components": {},
"dataStr": {
"head": {
"docType": "1",
"editorVersion": f"facere-epro2/0.1 (epro2 {epro2_editor})",
"units": "mil",
"epro2_doc_uuid": doc.doc_uuid,
"epro2_editor_version": epro2_editor,
},
"BBox": {
"x": bbox_x,
"y": bbox_y,
"width": bbox_w,
"height": bbox_h,
},
"layers": [], # schematic has no copper stack-up
"objects": dict(doc.objects),
"preference": {},
"netColors": [],
"DRCRULE": {},
},
}
stats = WriteStats(
objects=len(doc.objects),
bbox_x=bbox_x, bbox_y=bbox_y,
bbox_w=bbox_w, bbox_h=bbox_h,
)
write_sch_std.last_stats = stats # type: ignore[attr-defined]
return {"success": True, "code": 0, "result": result}