"""CLI: convert EPRO2 SCH_PAGE docs to KiCad ``.kicad_sch`` files. Usage: uv run python -m tools.epro2.kicad --doc --out uv run python -m tools.epro2.kicad --all-sch --out The ``--all-sch`` form converts every SCH_PAGE in the project, naming each output by its document title (or doc_uuid prefix as fallback). """ from __future__ import annotations import argparse import re import sys from pathlib import Path from ..project_relations import ProjectRelations from ..replay import Project, replay_project from .sch_writer import write_sch_page _SAFE_CHARS = re.compile(r"[^A-Za-z0-9._\-一-鿿]+") def _safe_filename(s: str) -> str: s = _SAFE_CHARS.sub("_", s).strip("_") return s or "untitled" def _convert_one( proj: Project, doc_uuid: str, out_dir: Path, pr: ProjectRelations | None = None, ) -> Path: if doc_uuid not in proj.documents: candidates = [u for u in proj.documents if u.startswith(doc_uuid)] if len(candidates) != 1: raise SystemExit(f"no unique match for {doc_uuid!r} (candidates: {candidates[:5]})") doc_uuid = candidates[0] doc = proj.documents[doc_uuid] if doc.doc_type != "SCH_PAGE": raise SystemExit(f"doc {doc_uuid} is {doc.doc_type!r}, not SCH_PAGE") text = write_sch_page(doc, project_relations=pr) title = (doc.objects.get("META") or {}).get("title") or doc_uuid[:12] out_path = out_dir / f"{_safe_filename(title)}_{doc_uuid[:8]}.kicad_sch" out_path.write_text(text, encoding="utf-8") stats = getattr(write_sch_page, "last_stats", None) if stats: print( f" {out_path.name}: wires={stats.wires} symbols={stats.symbol_placements} " f"text={stats.text} labels={stats.labels} skipped={stats.skipped} " f"lib_emb={stats.lib_symbols_embedded} lib_miss={stats.lib_symbols_missing}" ) return out_path def main(argv: list[str] | None = None) -> int: ap = argparse.ArgumentParser(description="EPRO2 → KiCad schematic exporter (Phase 1)") ap.add_argument("project_dir", type=Path) g = ap.add_mutually_exclusive_group(required=True) g.add_argument("--doc", help="SCH_PAGE doc uuid (or unique prefix) to convert") g.add_argument("--all-sch", action="store_true", help="convert every SCH_PAGE") ap.add_argument("--out", type=Path, default=Path("data/processed/kicad_sch")) ap.add_argument( "--no-lib-symbols", action="store_true", help="skip lib_symbols generation (Phase 1 mode — placements only, red ?)", ) args = ap.parse_args(argv) proj = replay_project(args.project_dir) args.out.mkdir(parents=True, exist_ok=True) pr = None if args.no_lib_symbols else ProjectRelations.build(proj) if args.doc: _convert_one(proj, args.doc, args.out, pr=pr) return 0 targets = [u for u, d in proj.documents.items() if d.doc_type == "SCH_PAGE"] if not targets: print("no SCH_PAGE docs in this project", file=sys.stderr) return 1 print(f"Converting {len(targets)} SCH_PAGE docs → {args.out}") for u in targets: try: _convert_one(proj, u, args.out, pr=pr) except Exception as e: # noqa: BLE001 print(f" FAIL {u[:12]}: {e}", file=sys.stderr) return 0 if __name__ == "__main__": raise SystemExit(main())