R2:底板 / 刀片布局布线脚本、快照、截图与规划文档更新(含外壳、打印件、布线归档)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
@@ -0,0 +1,89 @@
|
||||
#!/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()
|
||||
在新工单中引用
屏蔽一个用户