文件
phonerail/.claude/k_geonet.py

124 行
5.3 KiB
Python

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
#!/usr/bin/env python3
"""对账 2:从几何重建网表,和 EasyEDA 权威网表逐网络比对。
为什么必须做这一步:KiCad 的连接性同样是从几何算出来的。只要「按同样坐标摆符号 +
画同样导线」重建出的网表能和 EasyEDA 编译结果对上,生成的 .kicad_sch 就能对上;
对不上就说明我对几何的理解有洞,必须先补。
规则(都来自 easyeda-sch-geometry skill 的实测):
- getState_Line() 是**端点对**:每 4 个数一段,不是折线路径。当折线解析会凭空造斜线。
- 坐标单位 0.01 inch,元件都吸附在 5 的倍数上;TOL=0.6 足够宽松又不会误连。
- netflag / netport 各带一个引脚,其 Net 字段就是网络名;**跨页靠同名 netport/netflag 合并**。
"""
import json, os, sys, collections
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from k_netlist import parse_pads
ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "eda_src")
TOL = 0.6
class DSU:
def __init__(self): self.p = {}
def find(self, x):
self.p.setdefault(x, x)
while self.p[x] != x:
self.p[x] = self.p[self.p[x]]; x = self.p[x]
return x
def union(self, a, b):
ra, rb = self.find(a), self.find(b)
if ra != rb: self.p[ra] = rb
def key(x, y):
"""吸附到 0.5 网格再取整 —— 坐标里有 539.9999999999999 这种浮点毛刺。"""
return (round(x * 2) / 2, round(y * 2) / 2)
def segments(line):
"""端点对 -> [((x1,y1),(x2,y2)), ...]"""
out = []
for i in range(0, len(line) - 3, 4):
a, b = key(line[i], line[i + 1]), key(line[i + 2], line[i + 3])
if a != b: # 零长度段丢掉(skill §8a 的坑,连接性上无意义)
out.append((a, b))
return out
def build(board):
tree = json.load(open(os.path.join(ROOT, "_tree.json"), encoding="utf-8"))
pages = next(x for x in tree["boards"] if x["board"] == board)["pages"]
dsu = DSU()
pinat = collections.defaultdict(list) # 坐标 -> ["R1.2", ...]
named = collections.defaultdict(list) # 网络名 -> [坐标, ...]
for p in pages:
g = json.load(open(os.path.join(ROOT, board, p["name"] + ".geom.json"), encoding="utf-8"))
pfx = p["name"]
for w in (g["prims"]["Wire"] or []):
for a, b in segments(w.get("Line") or []):
dsu.union((pfx, a), (pfx, b))
for c in (g["prims"]["Component"] or []):
t = c.get("ComponentType")
if t == "sheet":
continue
for q in (c.get("__pins") or []):
k = (pfx, key(q["X"], q["Y"]))
dsu.find(k)
if t == "part":
pinat[k].append(f'{c["Designator"]}.{q["PinNumber"]}')
else: # netflag / netport
n = (c.get("Net") or "").strip()
if n:
named[n].append(k)
# 同名 netflag/netport 跨页合并
for n, ks in named.items():
for k in ks[1:]:
dsu.union(ks[0], k)
nets = collections.defaultdict(set)
for k, refs in pinat.items():
nets[dsu.find(k)].update(refs)
label = {}
for n, ks in named.items():
label.setdefault(dsu.find(ks[0]), n)
# 单引脚的不算网络 —— 那是未连接引脚,EasyEDA 网表也不收录(KiCad 侧要靠 NC 标记处理)
out, nc = {}, []
for i, (r, v) in enumerate(nets.items()):
if len(v) >= 2:
out[label.get(r, f"@{i}")] = sorted(v)
elif v:
nc.extend(v)
return out, sorted(nc)
def compare(board):
mine, nc = build(board)
base = parse_pads(os.path.join(ROOT, board, "netlist.asc"))["nets"]
# 按引脚集合配对,网络名不参与(EasyEDA 的 $1N236 是它自己编的)
mset = {frozenset(v): k for k, v in mine.items()}
bset = {frozenset(v): k for k, v in base.items()}
only_b = [bset[s] for s in bset.keys() - mset.keys()]
only_m = [mset[s] for s in mset.keys() - bset.keys()]
print(f"[{board}] 重建 {len(mine)} 网 / 基准 {len(base)} 网;引脚集合完全一致的 {len(mset.keys() & bset.keys())}")
if not only_b and not only_m:
print(" ✔ 网表逐网络完全一致")
# 命名一致性另报(不影响连接性)
diff = [(bset[s], mset[s]) for s in bset.keys() & mset.keys()
if not mset[s].startswith("@") and bset[s] != mset[s] and not bset[s].startswith("$")]
if diff:
print(f" · 网络名不同但连接相同 {len(diff)}: {diff[:5]}")
print(f" · 未连接引脚 {len(nc)} 个(EasyEDA 网表不收录,KiCad 侧需打 NC): {nc[:6]}{' …' if len(nc)>6 else ''}")
return True
for nm in sorted(only_b)[:8]:
print(f" ⚠ 基准有、重建没有: {nm} = {base[nm]}")
for nm in sorted(only_m)[:8]:
print(f" ⚠ 重建有、基准没有: {nm} = {mine[nm]}")
print(f" (只在基准 {len(only_b)},只在重建 {len(only_m)})")
return False
if __name__ == "__main__":
ok = all([compare(b) for b in (sys.argv[1:] or ["Board1", "Board2"])])
sys.exit(0 if ok else 1)