# -*- coding: utf-8 -*- """本地布线检查器:在推送到 EasyEDA 之前先验证几何连通性,避免反复返工。""" import json from collections import defaultdict PINS = json.load(open('pins.json')) def pin(d, n): x, y, _ = PINS[d][str(n)] return (x, y) SEGS = [] # [(p1, p2, net)] FLAGS = [] # [(kind, net, x, y, rot)] kind: Power/Ground PORTS = [] # [(dir, net, x, y, rot)] TEXTS = [] # [(s, x, y, size)] def w(net, *pts): """折线:逐点相连,拆成 2 点段。坐标必须是 5 的倍数。""" for p in pts: assert p[0] % 5 == 0 and p[1] % 5 == 0, f'{net}: {p} 不是 5 的倍数' for a, b in zip(pts, pts[1:]): assert a[0] == b[0] or a[1] == b[1], f'{net}: {a}->{b} 不是正交' if a != b: SEGS.append((a, b, net)) def gnd(x, y): FLAGS.append(('Ground', 'GND', x, y, 0)) def pwr(net, x, y): FLAGS.append(('Power', net, x, y, 0)) def port(net, x, y, rot, d='BI'): PORTS.append((d, net, x, y, rot)) def txt(s, x, y, size=12): TEXTS.append((s, x, y, size)) # ---------------- 连通性检查 ---------------- def _on(p, a, b, tol=0.6): (px, py), (ax, ay), (bx, by) = p, a, b if abs(ax - bx) < tol: # 竖直 return abs(px - ax) < tol and min(ay, by) - tol <= py <= max(ay, by) + tol if abs(ay - by) < tol: # 水平 return abs(py - ay) < tol and min(ax, bx) - tol <= px <= max(ax, bx) + tol return False def check(): """按几何反推网络,和声明的 net 名比对。返回 (错误列表, 网络表)。""" errs = [] n = len(SEGS) parent = list(range(n)) def find(i): while parent[i] != i: parent[i] = parent[parent[i]]; i = parent[i] return i def union(i, j): a, b = find(i), find(j) if a != b: parent[a] = b for i in range(n): for j in range(i + 1, n): a1, a2, _ = SEGS[i]; b1, b2, _ = SEGS[j] # 端点落在另一段上(含共享端点)才算连接;纯交叉不算 if _on(a1, b1, b2) or _on(a2, b1, b2) or _on(b1, a1, a2) or _on(b2, a1, a2): union(i, j) groups = defaultdict(list) for i in range(n): groups[find(i)].append(i) nets = {} for root, idxs in groups.items(): names = sorted({SEGS[i][2] for i in idxs}) if len(names) > 1: detail = [] for i in idxs: detail.append('%s%s-%s' % (SEGS[i][2], SEGS[i][0], SEGS[i][1])) errs.append('短路: ' + ' + '.join(names) + ' :: ' + ' | '.join(detail)) nets[names[0]] = idxs # 同名网络被拆成多块 = 漏连 seen = defaultdict(int) for root, idxs in groups.items(): seen[SEGS[idxs[0]][2]] += 1 for name, cnt in seen.items(): if cnt > 1: errs.append(f'网络 {name} 被拆成 {cnt} 块(漏连)') # 每个元件引脚必须落在某条线段上 for d, pd in PINS.items(): for num, (x, y, nm) in pd.items(): if not any(_on((x, y), s[0], s[1]) for s in SEGS): errs.append(f'悬空引脚: {d}.{num} ({nm}) @ ({x},{y})') # 旗/端口引脚也必须落在线段上 for kind, net, x, y, rot in FLAGS: if not any(_on((x, y), s[0], s[1]) for s in SEGS): errs.append(f'悬空旗: {kind} {net} @ ({x},{y})') for d, net, x, y, rot in PORTS: if not any(_on((x, y), s[0], s[1]) for s in SEGS): errs.append(f'悬空端口: {net} @ ({x},{y})') return errs def crossings(): """列出所有交叉点(不相连),供人工确认可读性。""" out = [] for i in range(len(SEGS)): for j in range(i + 1, len(SEGS)): a1, a2, na = SEGS[i]; b1, b2, nb = SEGS[j] if na == nb: continue hz_a = a1[1] == a2[1]; hz_b = b1[1] == b2[1] if hz_a == hz_b: continue h, v = ((a1, a2), (b1, b2)) if hz_a else ((b1, b2), (a1, a2)) y = h[0][1]; x = v[0][0] if min(h[0][0], h[1][0]) < x < max(h[0][0], h[1][0]) and \ min(v[0][1], v[1][1]) < y < max(v[0][1], v[1][1]): out.append((na, nb, x, y)) return out