R2:底板 / 刀片布局布线脚本、快照、截图与规划文档更新(含外壳、打印件、布线归档)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
@@ -0,0 +1,129 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
u"""U32 十个 strap 脚 -> R900~R909 的连线:**手写短路径**,不用迷宫布线器。
|
||||
|
||||
/c/Python314/python .claude/pcb2_strap_route.py # 只算 + 校核
|
||||
/c/Python314/python .claude/pcb2_strap_route.py --push # 下发
|
||||
|
||||
## 为什么手写
|
||||
|
||||
`pcb2_route_sig.py` 对这十条**两次都只布通 2~3 条**,连 R904 那条 0.9 mm 的都报布不通
|
||||
(`(103.25,73.00) -> (103.89,73.63)`)。原因不是距离,是它从 QFN 焊盘选的**逃逸方向**
|
||||
不对:pin 49 在南边,它却往东逃(沿着 0.5 mm 间距的焊盘排走),当然出不去。
|
||||
|
||||
QFN-64 9×9 是 0.5 mm 间距、焊盘间只有 ~0.2 mm 缝 —— **焊盘之间根本穿不过 0.20 mm 的线**,
|
||||
唯一的出路是**垂直于所在边直着出来**。这十条又都只有 1~5 mm,属于「几何设计」而不是
|
||||
「找路」,所以照差分对的办法手写坐标(`pcb2_route_dp.py` 文件头那段的理由同样适用)。
|
||||
|
||||
校核用的是**同一套** `pcb2_route_dp.check()`,和差分对、和布线器一个判据。
|
||||
"""
|
||||
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
|
||||
|
||||
PCB = '942ed1560d81c1e7'
|
||||
MIL = 39.3700787401575
|
||||
LAY = D.L_BOT # 元件都在底层
|
||||
WID = 0.20 # strap 是 DC 采样脚,最细线宽就够
|
||||
|
||||
# 位号 -> (U32 脚号, 该脚在 QFN 的哪条边) N/S/E/W = 出线法线方向
|
||||
MAP = [('R906', '15', 'W'), ('R905', '17', 'N'), ('R904', '31', 'N'), ('R903', '33', 'E'),
|
||||
('R909', '34', 'E'), ('R902', '47', 'E'), ('R908', '48', 'E'), ('R901', '49', 'S'),
|
||||
('R907', '50', 'S'), ('R900', '51', 'S')]
|
||||
NORM = {'N': (0.0, 1.0), 'S': (0.0, -1.0), 'E': (1.0, 0.0), 'W': (-1.0, 0.0)}
|
||||
|
||||
|
||||
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 endpoints():
|
||||
u"""读回 U32 十个 strap 焊盘中心 + 十颗电阻的 strap 端焊盘中心(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, u32 = {}, res = {};
|
||||
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) continue;
|
||||
const xy = [Math.round(p.getState_X()/M*1000)/1000, Math.round(p.getState_Y()/M*1000)/1000, p.getState_Net()];
|
||||
if (hit === 'U32') u32[String(p.getState_PadNumber ? p.getState_PadNumber() : p.number)] = xy;
|
||||
else if (/^R9[0-9][0-9]$/.test(hit) && p.getState_Net() !== 'GND') res[hit] = xy;
|
||||
}
|
||||
return {u32, res};""" % MIL)
|
||||
|
||||
|
||||
def cands(a, b, nd):
|
||||
u"""从焊盘 a 垂直出边 d,再走 L 形到 b。两种拐序 + 45 度斜接,由短到长试。"""
|
||||
ax, ay = a
|
||||
bx, by = b
|
||||
nx, ny = nd
|
||||
out = []
|
||||
for d in [v / 100.0 for v in range(30, 161, 10)]:
|
||||
mx, my = round(ax + nx * d, 3), round(ay + ny * d, 3)
|
||||
for order in (0, 1):
|
||||
if order == 0:
|
||||
pts = [(ax, ay), (mx, my), (round(bx, 3), round(my, 3)), (bx, by)]
|
||||
else:
|
||||
pts = [(ax, ay), (mx, my), (round(mx, 3), round(by, 3)), (bx, by)]
|
||||
clean = [pts[0]]
|
||||
for p in pts[1:]:
|
||||
if abs(p[0] - clean[-1][0]) > 1e-6 or abs(p[1] - clean[-1][1]) > 1e-6:
|
||||
clean.append(p)
|
||||
if len(clean) >= 2:
|
||||
out.append(clean)
|
||||
# 直接斜着接过去(出边之后一段 45°)
|
||||
out.append([(ax, ay), (mx, my), (bx, by)])
|
||||
return out
|
||||
|
||||
|
||||
def main():
|
||||
if '--nodump' not in sys.argv:
|
||||
D.dump()
|
||||
pads, vias, lines = D.geom()
|
||||
ep = endpoints()
|
||||
routes, fail = {}, []
|
||||
for des, pin, side in MAP:
|
||||
a = ep['u32'][pin][:2]
|
||||
b = ep['res'][des][:2]
|
||||
net = ep['u32'][pin][2]
|
||||
assert net == ep['res'][des][2], u'%s 与 U32.%s 网络不一致' % (des, pin)
|
||||
got = None
|
||||
for pts in cands(a, b, NORM[side]):
|
||||
path = [(x, y, LAY, WID) for x, y in pts]
|
||||
trial = dict(routes)
|
||||
trial[net] = path
|
||||
if not D.check(trial, pads, vias, lines, verbose=False):
|
||||
got = path
|
||||
break
|
||||
if got is None:
|
||||
fail.append((des, pin, a, b))
|
||||
continue
|
||||
routes[net] = got
|
||||
L = sum(math.hypot(got[i + 1][0] - got[i][0], got[i + 1][1] - got[i][1]) for i in range(len(got) - 1))
|
||||
print(u' %-5s U32.%-3s %s 边 %d 拐点 %.2f mm' % (des, pin, side, len(got) - 2, L))
|
||||
if fail:
|
||||
for des, pin, a, b in fail:
|
||||
print(u'!! %s (U32.%s) 手写路径也过不了校核:%s -> %s' % (des, pin, a, b))
|
||||
sys.exit(1)
|
||||
bad = D.check(routes, pads, vias, lines, verbose=True)
|
||||
if bad:
|
||||
sys.exit(u'净距校核未过:%d 条' % len(bad))
|
||||
print(u'十条全部找到路径,净距校核(0.20 mm)全过')
|
||||
if '--push' in sys.argv:
|
||||
D.push(routes)
|
||||
|
||||
|
||||
main()
|
||||
在新工单中引用
屏蔽一个用户