131 行
5.6 KiB
Python
131 行
5.6 KiB
Python
# -*- coding: utf-8 -*-
|
|
u"""R900~R909 的地端扇出:每颗一条短线 + 一个过孔落到 Inner2 地平面。
|
|
|
|
/c/Python314/python .claude/pcb2_strap_gnd.py # 只规划
|
|
/c/Python314/python .claude/pcb2_strap_gnd.py --push # 下发 + 存盘
|
|
|
|
为什么不跑 `pcb2_fanout.py`:那个是**整板**扇出,文件头写明「布局一动就要
|
|
`--clean` → 重新 dump → `--push`」三步一起 —— 会把板上 415 个扇出孔全拆了重打,
|
|
而现在只是多了十颗 0402。照 `pcb2_keypads.py --gnd` 的先例做局部扇出(那次是 5 个 GND 盘),
|
|
净距判据直接复用它的 `_clear` / `_seg_clear`,和整板扇出是同一套。
|
|
|
|
⚠️ 元件在**底层**,所以短线画在 layer 2(keypads 那次的盘在顶层,画的是 layer 1)。
|
|
"""
|
|
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
|
|
import pcb2_route_dp as D
|
|
import pcb2_keypads as K
|
|
|
|
PCB = '942ed1560d81c1e7'
|
|
MIL = 39.3700787401575
|
|
LAY = 2 # 底层
|
|
W_STUB = K.W_STUB
|
|
VIA_D, VIA_H = K.VIA_D, K.VIA_H
|
|
REFS = ['R900', 'R901', 'R902', 'R903', 'R904', 'R905', 'R906', 'R907', 'R908', 'R909']
|
|
|
|
|
|
def call(js, timeout=300):
|
|
r = run("await eda.dmt_EditorControl.openDocument('%s');"
|
|
"await new Promise(r=>setTimeout(r,2000));" % PCB + js, timeout=timeout)
|
|
if not r.get('success'):
|
|
sys.exit(u'桥接失败: ' + json.dumps(r, ensure_ascii=False)[:600])
|
|
return r['result']
|
|
|
|
|
|
def gnd_pads():
|
|
u"""读回这十颗电阻的 GND 焊盘中心(mm)。"""
|
|
return call("""
|
|
const name = {};
|
|
for (const c of await eda.pcb_PrimitiveComponent.getAll()) name[c.getState_PrimitiveId()] = c.getState_Designator();
|
|
const ids = Object.keys(name).sort((a,b)=>b.length-a.length);
|
|
const M = %f, out = {};
|
|
for (const p of await eda.pcb_PrimitivePad.getAll()) {
|
|
const pid = p.primitiveId || '';
|
|
let hit = null;
|
|
for (const id of ids) if (pid.length > id.length && pid.indexOf(id) === 0) { hit = name[id]; break; }
|
|
if (!hit || !/^R9[0-9][0-9]$/.test(hit)) continue;
|
|
if (p.getState_Net() !== 'GND') continue;
|
|
out[hit] = [Math.round(p.getState_X()/M*1000)/1000, Math.round(p.getState_Y()/M*1000)/1000];
|
|
}
|
|
return out;""" % MIL)
|
|
|
|
|
|
def plan(pads, vias, lines, gp):
|
|
out, bad = {}, []
|
|
for ref in REFS:
|
|
x, y = gp[ref]
|
|
# ⚠️ 第一版只试 5 个方向 x 5 个距离,四颗找不到位置。载板这一片太密,
|
|
# 候选集必须放开:每 15 度一个方向、0.9~3.5 mm 每 0.1 一档,由近到远。
|
|
cands = []
|
|
for dist in [d / 10.0 for d in range(9, 36)]:
|
|
for a in range(0, 360, 15):
|
|
th = math.radians(a)
|
|
cands.append((round(x + math.cos(th) * dist, 3), round(y + math.sin(th) * dist, 3)))
|
|
chosen, why = None, []
|
|
placed = [v['via'] for v in out.values()]
|
|
for vx, vy in cands:
|
|
# 本轮新打的过孔之间也要保钻孔间距(skill §9v)
|
|
if any(math.hypot(vx - ax, vy - ay) < 0.33 + VIA_H for ax, ay in placed):
|
|
continue
|
|
ok, msg = K._clear(vx, vy, VIA_D / 2, 12, pads, vias, lines, ref)
|
|
if not ok:
|
|
why.append(u'(%.2f,%.2f) 孔撞 %s' % (vx, vy, msg)); continue
|
|
ok, msg = K._seg_clear((x, y), (vx, vy), W_STUB, pads, vias, lines, ref)
|
|
if not ok:
|
|
why.append(u'(%.2f,%.2f) 线撞 %s' % (vx, vy, msg)); continue
|
|
chosen = (vx, vy)
|
|
break
|
|
if chosen is None:
|
|
bad.append((ref, why[:6]))
|
|
continue
|
|
out[ref] = {'pad': (x, y), 'via': chosen}
|
|
print(u' %-5s 盘 (%7.3f,%6.3f) -> 过孔 (%7.3f,%6.3f) 距 %.2f mm'
|
|
% (ref, x, y, chosen[0], chosen[1], math.hypot(chosen[0] - x, chosen[1] - y)))
|
|
# 新打的过孔彼此也要判钻孔间距(skill §9v 补的那一条)
|
|
ks = list(out)
|
|
for i in range(len(ks)):
|
|
for j in range(i + 1, len(ks)):
|
|
a, b = out[ks[i]]['via'], out[ks[j]]['via']
|
|
if math.hypot(a[0] - b[0], a[1] - b[1]) < 0.33 + VIA_H:
|
|
bad.append((ks[i] + '/' + ks[j], [u'两个新过孔钻孔间距不足']))
|
|
if bad:
|
|
for ref, why in bad:
|
|
print(u'!! %s 找不到扇出位置:' % ref)
|
|
for w in why:
|
|
print(u' ' + w)
|
|
sys.exit(1)
|
|
return out
|
|
|
|
|
|
def main():
|
|
D.dump()
|
|
pads, vias, lines = D.geom()
|
|
gp = gnd_pads()
|
|
missing = [r for r in REFS if r not in gp]
|
|
if missing:
|
|
sys.exit(u'板上找不到这些 GND 焊盘: %s' % missing)
|
|
p = plan(pads, vias, lines, gp)
|
|
print(u'十条地端扇出全部找到位置(净距判据同整板扇出)')
|
|
if '--push' not in sys.argv:
|
|
print(u'加 --push 下发')
|
|
return
|
|
segs = [['GND', LAY, v['pad'][0], v['pad'][1], v['via'][0], v['via'][1], W_STUB] for v in p.values()]
|
|
vs = [['GND', v['via'][0], v['via'][1]] for v in p.values()]
|
|
r = call(u"""
|
|
const M = %f, SEGS = %s, VIAS = %s;
|
|
let nl = 0, nv = 0;
|
|
for (const [net, lay, x0, y0, x1, y1, w] of SEGS) { await eda.pcb_PrimitiveLine.create(net, lay, x0*M, y0*M, x1*M, y1*M, w*M); nl++; }
|
|
for (const [net, x, y] of VIAS) { await eda.pcb_PrimitiveVia.create(net, x*M, y*M, %f*M, %f*M); nv++; }
|
|
await eda.pcb_Document.save();
|
|
return {nl, nv};""" % (MIL, json.dumps(segs), json.dumps(vs), VIA_H, VIA_D))
|
|
print(u'已下发:%d 条短线 / %d 个过孔' % (r['nl'], r['nv']))
|
|
|
|
|
|
main()
|