#!/usr/bin/env python3 """解析 EasyEDA 导出的 PADS 网表 -> 规范化的对账基准。 这是转 KiCad 全程的**唯一真值**:生成的 .kicad_sch 必须能被 kicad-cli 编译出 完全一致的 (元件集合, 网络->引脚集合)。网络名本身允许改写(EasyEDA 的匿名网 $1N236 和 KiCad 的自动命名不会一样),所以比对时按「引脚集合」而不是网络名配对。 用法: python k_netlist.py # 打印两块板的统计 python k_netlist.py --board Board1 --dump base.json """ import argparse, json, os, re, sys ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "eda_src") def parse_pads(path): """-> {'parts': {ref: footprint}, 'nets': {net: [ref.pin, ...]}}""" txt = open(path, encoding="utf-8").read() parts, nets = {}, {} section, cur = None, None for raw in txt.splitlines(): line = raw.rstrip() if not line.strip(): continue m = re.match(r"^\*([A-Z]+)\*(.*)$", line) if m: tag, rest = m.group(1), m.group(2).strip() if tag == "SIGNAL": section, cur = "SIGNAL", rest nets[cur] = [] elif tag in ("PART", "NET"): section, cur = tag, None else: # *REMARK* 等一律跳过 section, cur = None, None continue if section == "PART": f = line.split() if len(f) >= 2: parts[f[0]] = f[1] elif f: parts[f[0]] = "" elif section == "SIGNAL" and cur is not None: nets[cur].extend(line.split()) return {"parts": parts, "nets": nets} def stats(nl, label): parts, nets = nl["parts"], nl["nets"] pins = [p for v in nets.values() for p in v] anon = [n for n in nets if n.startswith("$")] # 每个网络的引脚数分布,单引脚网络是可疑项 single = [n for n, v in nets.items() if len(v) < 2] # 出现在网表里但不在 PART 表里的位号 refs = {p.rsplit(".", 1)[0] for p in pins} missing = sorted(refs - set(parts)) print(f"[{label}]") print(f" 元件 {len(parts):>4d} 网络 {len(nets):>4d}(匿名 {len(anon)}) 引脚连接 {len(pins):>5d}") print(f" 单引脚网络 {len(single)} {single[:6]}{' …' if len(single) > 6 else ''}") if missing: print(f" ⚠ 网表引用了 PART 表里没有的位号: {missing[:10]}") dup = [p for p in set(pins) if pins.count(p) > 1] if len(pins) < 20000 else [] if dup: print(f" ⚠ 同一引脚出现在多个网络(短路): {dup[:10]}") return {"parts": len(parts), "nets": len(nets), "pins": len(pins)} def main(): ap = argparse.ArgumentParser() ap.add_argument("--board") ap.add_argument("--dump") a = ap.parse_args() boards = [a.board] if a.board else ["Board1", "Board2"] out = {} for b in boards: p = os.path.join(ROOT, b, "netlist.asc") if not os.path.exists(p): print(f"!! 缺 {p}"); sys.exit(1) nl = parse_pads(p) stats(nl, b) out[b] = nl if a.dump: json.dump(out if not a.board else out[a.board], open(a.dump, "w", encoding="utf-8"), ensure_ascii=False, indent=1) print(f"\n-> {a.dump}") if __name__ == "__main__": main()