# -*- coding: utf-8 -*- u"""按**实际接触的铜**给过孔 / 导线重新标网络(载板 PCB2)。 /c/Python314/python .claude/pcb2_via_relabel.py [--push] 2026-09-06 「595 上载板」那次导入变更把一批旧局部网(`$21N57` 这类)的编号重映射到了新网络, 板上几十个老过孔 / 导线(VOn_P 岛的孔、EDL 局部网的线)被贴上 K1_PWR / VO4_P / +3V3_HUB 之类的错标签。 铜一处没动,只是标签错 —— DRC 里成排的「导线到过孔 0 mil」就是它(线和孔本是同一根铜)。 判据(只看**直接接触**,交替迭代三轮):过孔 = 端点落在它上面的导线 + 盖住它的焊盘;导线 = 两端落在的焊盘 + 两端碰到的过孔。 接触到的网络只有一种、且和现在的标签不同才改;有分歧的不动;什么都不碰的(平面缝合孔等)不动。 (第一版用并查集整块传播,结果 J23 那两个故意短接的小环和 Hub 大焊盘把整板并成一团,废了。) ⚠️ `pcb_PrimitiveVia.modify(id, {net})` / `pcb_PrimitiveLine.modify(id, {net})` **静默无效**(2026-09-06 实测), 只能删掉重建(`create(net, ...)` 带网络名)。 """ import json import math import os import sys HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, HERE) from eda import run # noqa: E402 PCB = '942ed1560d81c1e7' MIL = 39.3700787401575 TOL = 0.05 JS = u""" await eda.dmt_EditorControl.openDocument('%s'); await new Promise(r => setTimeout(r, 1800)); const vias = [], lines = [], pads = []; for (const v of await eda.pcb_PrimitiveVia.getAll()) { let hole = 0; try { hole = v.getState_HoleDiameter ? v.getState_HoleDiameter() : (v.getState_Hole ? v.getState_Hole() : 0); } catch (e) {} vias.push([v.getState_PrimitiveId(), v.getState_Net() || '', v.getState_X(), v.getState_Y(), v.getState_Diameter(), hole]); } for (const l of await eda.pcb_PrimitiveLine.getAll()) { const L = l.getState_Layer(); if (![1, 2, 15, 16].includes(L)) continue; lines.push([l.getState_PrimitiveId(), l.getState_Net() || '', L, l.getState_StartX(), l.getState_StartY(), l.getState_EndX(), l.getState_EndY(), l.getState_LineWidth()]); } for (const p of await eda.pcb_PrimitivePad.getAll()) { let w = 1.0, h = 1.0; try { const s = p.getState_Pad(); if (s && typeof s[1] === 'number') { w = s[1]; h = s[2] || s[1]; } } catch (e) {} pads.push([p.getState_Net() || '', p.getState_Layer(), p.getState_X(), p.getState_Y(), w, h]); } return {vias, lines, pads}; """ JS_FIX = u""" await eda.dmt_EditorControl.openDocument('%s'); await new Promise(r => setTimeout(r, 1500)); const V = %s, L = %s; let nv = 0, nl = 0; for (const [id, net, x, y, dia, hole] of V) { await eda.pcb_PrimitiveVia.delete(id); await eda.pcb_PrimitiveVia.create(net, x, y, hole || 12, dia || 24); nv++; // 读不到孔径就按 12/24 mil(板上过孔全是 0.3048/0.6096) } for (const [id, net, lay, x1, y1, x2, y2, w] of L) { await eda.pcb_PrimitiveLine.delete(id); await eda.pcb_PrimitiveLine.create(net, lay, x1, y1, x2, y2, w); nl++; } await eda.pcb_Document.save(); return {nv, nl}; """ def main(): r = run(JS % PCB, timeout=300) assert r.get('success'), json.dumps(r, ensure_ascii=False)[:500] st = r['result'] vias = [list(v) for v in st['vias']] # [id, net, x, y, dia, hole](mil) lines = [list(l) for l in st['lines']] # [id, net, lay, x1, y1, x2, y2, w] pads = st['pads'] # [net, lay, x, y, w, h] m = lambda v: v / MIL def pad_nets_at(x, y): u"""盖住点 (x,y) 的焊盘的网络(保守:按 min(w,h)/2 的圆判,不管旋转)。""" out = set() for n, L, px, py, w, h in pads: if n and math.hypot(m(px) - m(x), m(py) - m(y)) <= min(m(w), m(h)) / 2.0 + 0.02: out.add(n) return out def near(ax, ay, bx, by): return math.hypot(m(ax) - m(bx), m(ay) - m(by)) < TOL changed_v, changed_l = {}, {} for _round in range(3): n_changed = 0 # 过孔:接触的导线 + 盖住它的焊盘 for v in vias: touch = set() for l in lines: if l[1] and (near(l[3], l[4], v[2], v[3]) or near(l[5], l[6], v[2], v[3])): touch.add(l[1]) touch |= pad_nets_at(v[2], v[3]) if len(touch) == 1: net = next(iter(touch)) if net != v[1]: changed_v[v[0]] = (v[1] if v[0] not in changed_v else changed_v[v[0]][0], net) v[1] = net; n_changed += 1 # 导线:两端落在的焊盘 + 两端碰到的过孔 for l in lines: touch = pad_nets_at(l[3], l[4]) | pad_nets_at(l[5], l[6]) for v in vias: if v[1] and (near(l[3], l[4], v[2], v[3]) or near(l[5], l[6], v[2], v[3])): touch.add(v[1]) if len(touch) == 1: net = next(iter(touch)) if net != l[1]: changed_l[l[0]] = (l[1] if l[0] not in changed_l else changed_l[l[0]][0], net) l[1] = net; n_changed += 1 print(u' 第 %d 轮:改 %d' % (_round + 1, n_changed)) if not n_changed: break by = {} for old, new in changed_v.values(): by[('via', old or "''", new)] = by.get(('via', old or "''", new), 0) + 1 for old, new in changed_l.values(): by[('line', old or "''", new)] = by.get(('line', old or "''", new), 0) + 1 print(u'过孔要改 %d / 导线要改 %d' % (len(changed_v), len(changed_l))) for k in sorted(by): print(u' %-4s %-14s -> %-14s %d' % (k[0], k[1], k[2], by[k])) if '--push' not in sys.argv: print(u'(未加 --push,没有改动)') return V = [[v[0], v[1], v[2], v[3], v[4], v[5]] for v in vias if v[0] in changed_v] L = [[l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7]] for l in lines if l[0] in changed_l] r = run(JS_FIX % (PCB, json.dumps(V), json.dumps(L)), timeout=900) assert r.get('success'), json.dumps(r, ensure_ascii=False)[:500] print(u'已重建:过孔 %d / 导线 %d,已存盘' % (r['result']['nv'], r['result']['nl'])) if __name__ == '__main__': main()