135 行
6.3 KiB
Python
135 行
6.3 KiB
Python
# -*- 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()
|