R2:底板 / 刀片布局布线脚本、快照、截图与规划文档更新(含外壳、打印件、布线归档)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
zhou yang
2026-10-04 19:24:45 +08:00
共同撰写人 Claude Opus 5.5
父节点 cd01640c40
当前提交 cf7cc9cf90
共修改 759 个文件,包含 76962 行新增和 5299 行删除
+129
查看文件
@@ -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()