文件
phonerail/.claude/pcb2_via_relabel.py
T

135 行
6.3 KiB
Python
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# -*- 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()