"""dump_kicad_vec.py — 把 KiCad 板的离散几何导成 JSON,供 cmp_vec.py 比对。必须用 KiCad python 跑。 输出(单位一律 mm,KiCad 原生坐标系:y 向下): vias.json [{x, y, drill, top, bot, net}] pads.json [{x, y, layer, net, ref, pad, drill}] tracks.json [{x1, y1, x2, y2, width, layer, net}] meta.json {counts, layers} 用法(KiCad python 路径见 ~/skills_env.md 的 KICAD_PYTHON): D:\\KiCad\\10.0\\bin\\python.exe dump_kicad_vec.py \\ --board E:\\Kicad_design\\FOO\\FOO.kicad_pcb \\ --out E:\\Kicad_design\\FOO\\_migration\\gerber\\kicad_vec """ from __future__ import annotations import argparse import json import sys from pathlib import Path def main() -> int: ap = argparse.ArgumentParser(description="Dump KiCad board discrete geometry to JSON (mm)") ap.add_argument("--board", required=True) ap.add_argument("--out", required=True) args = ap.parse_args() try: import pcbnew except ImportError: print("[ERROR] 导不到 pcbnew —— 用 KiCad 自带 python 跑(~\\skills_env.md 的 KICAD_PYTHON)。") return 1 out = Path(args.out) out.mkdir(parents=True, exist_ok=True) board = pcbnew.LoadBoard(args.board) mm = pcbnew.ToMM def via_size(v) -> float: """KiCad 10 起 PCB_VIA::GetWidth() 必须带层参数,不带会断言中止进程。 老版本又只接受无参形式,所以两种都试。size 只是给人看的,取不到就算了。""" for call in (lambda: v.GetWidth(v.TopLayer()), lambda: v.GetWidth()): try: return mm(call()) except Exception: continue return 0.0 vias, tracks = [], [] for t in board.GetTracks(): if isinstance(t, pcbnew.PCB_VIA): vias.append({ "x": mm(t.GetPosition().x), "y": mm(t.GetPosition().y), "drill": mm(t.GetDrill()), "size": via_size(t), "top": board.GetLayerName(t.TopLayer()), "bot": board.GetLayerName(t.BottomLayer()), "net": t.GetNetname(), }) else: tracks.append({ "x1": mm(t.GetStart().x), "y1": mm(t.GetStart().y), "x2": mm(t.GetEnd().x), "y2": mm(t.GetEnd().y), "width": mm(t.GetWidth()), "layer": board.GetLayerName(t.GetLayer()), "net": t.GetNetname(), }) pads = [] for fp in board.GetFootprints(): ref = fp.GetReference() for p in fp.Pads(): pads.append({ "x": mm(p.GetPosition().x), "y": mm(p.GetPosition().y), "layer": board.GetLayerName(p.GetLayer()), "net": p.GetNetname(), "ref": ref, "pad": p.GetNumber(), "drill": mm(p.GetDrillSizeX()), }) layers = sorted({t["layer"] for t in tracks}) for name, data in (("vias", vias), ("tracks", tracks), ("pads", pads)): (out / f"{name}.json").write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8") (out / "meta.json").write_text(json.dumps({ "units": "mm", "y_axis": "down (KiCad native)", "counts": {"vias": len(vias), "tracks": len(tracks), "pads": len(pads)}, "copper_layers_with_tracks": layers, }, ensure_ascii=False, indent=2), encoding="utf-8") print(f"[dump] vias={len(vias)} tracks={len(tracks)} pads={len(pads)} -> {out}") print(f"[layers] 有走线的铜层:{', '.join(layers)}") print("[next] Altium 侧用 eda-agent 导同名 JSON 到 altium_vec/(格式见 " "references\\vector-compare.md),然后跑 cmp_vec.py。") return 0 if __name__ == "__main__": try: sys.stdout.reconfigure(encoding="utf-8") except Exception: pass sys.exit(main())