写第一版 EPRO2 → .kicad_sch 转换:把 SCH_PAGE Document 的 wires +
COMPONENT placements + TEXT 输出到一个可被 KiCad 7+ 打开的 sch 文件。
不含 symbol 主体(lib_symbols 留空 stub),所以 KiCad 里组件会渲染
成红色 "?" 占位,但布线 + 位置 + Designator/Value 属性都正确。完整
symbol 库导出留 Phase 2。
模块结构:
tools/epro2/kicad/sexpr.py 手写 S-expr emitter,Sym 标记裸符号,
str 自动加引号 + 转义;float 去尾零;
bool→yes/no;NaN/Inf 主动报错
tools/epro2/kicad/_sexpr_reader.py 极简 S-expr parser,仅给 round-trip
测试用(非完整 KiCad reader)
tools/epro2/kicad/sch_writer.py write_sch_page(doc) → str;处理:
LINE → (wire (pts ...) ...)
COMPONENT → (symbol (lib_id facere:<partId>)
(at x y rot) (property Reference ...) ...)
TEXT → (text "..." (at ...))
单位 mil → mm × 0.0254;零长 wire 跳过
tools/epro2/kicad/__main__.py CLI: --doc <uuid> | --all-sch
ESP-VoCat 验证(python -m tools.epro2.kicad <project> --all-sch):
9 SCH_PAGE 全部转换成功
P1_408c9f4f.kicad_sch wires= 6 symbols= 10 text= 0 skipped= 2 (370 lines)
P1_ee409917.kicad_sch wires= 20 symbols= 14 text= 0 skipped= 3
P1_54743d77.kicad_sch wires= 42 symbols= 30 text= 3
Overview_dc13d6d2.kicad_sch wires= 0 symbols= 1 text= 34 (说明页)
MCU_510cff33.kicad_sch wires= 91 symbols= 86 text= 9
Interface_b336a7c7.kicad_sch wires= 99 symbols= 95 text= 6
P1_5c38f45b.kicad_sch wires=179 symbols= 86 text= 9
P1_45092758.kicad_sch wires=187 symbols=138 text= 10 (主图)
codec_0b0163fa.kicad_sch wires=190 symbols=112 text= 10
输出落在 data/processed/kicad_sch/<filename>.kicad_sch(gitignore 内,
可重新生成;不入库)。
测试:6 个 sexpr 测 + 6 个 sch_writer 测,含 round-trip parse 验证。
parser/relations/project_relations 的旧 21 个不动,合计 **33/33 通过**。
下一步:
1. Phase 2 — symbol library 导出 (.kicad_sym),把 SYMBOL doc 的 PIN/RECT/
TEXT primitives 转 KiCad symbol 主体;填 lib_symbols 块让组件渲染
出真正的 schematic 符号
2. footprint library + .kicad_pcb 导出
3. 用 KiCad CLI (kicad-cli sch erc) 跑 ERC 校验
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
84 lines
2.9 KiB
Python
84 lines
2.9 KiB
Python
"""CLI: convert EPRO2 SCH_PAGE docs to KiCad ``.kicad_sch`` files.
|
|
|
|
Usage:
|
|
uv run python -m tools.epro2.kicad <project_dir> --doc <sch_uuid> --out <dir>
|
|
uv run python -m tools.epro2.kicad <project_dir> --all-sch --out <dir>
|
|
|
|
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 ..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) -> 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)
|
|
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} skipped={stats.skipped}"
|
|
)
|
|
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"))
|
|
args = ap.parse_args(argv)
|
|
|
|
proj = replay_project(args.project_dir)
|
|
args.out.mkdir(parents=True, exist_ok=True)
|
|
|
|
if args.doc:
|
|
_convert_one(proj, args.doc, args.out)
|
|
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)
|
|
except Exception as e: # noqa: BLE001
|
|
print(f" FAIL {u[:12]}: {e}", file=sys.stderr)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|