# -*- 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()