R2:底板 / 刀片布局布线脚本、快照、截图与规划文档更新(含外壳、打印件、布线归档)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
@@ -0,0 +1,113 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
u"""按**手定的拓扑**分段 A*:每段 = 一个网从点 A 到点 B(点给坐标 + 层),A* 只管段内细节(2026-09-15)。
|
||||
|
||||
DCDC_ROUTE_SERVER=1 pypy3 .claude/r2/opt_legs.py <spec.json>
|
||||
|
||||
spec:{"base": "_blade_routes.xxx.json", "out": "_blade_routes.legs%d.json",
|
||||
"variants": [[[网, [x, y, 层], [x, y, 层]], ...], ...]}
|
||||
层 1 = L1 顶、2 = L4 底。点通常是已有的同网孔 / 焊盘中心:起止格点取点周围 0.4 mm 内的空格点,
|
||||
再用一段直线从精确点接到格点(净距交给 router_check 复核)。整个网的铜同网可穿,所以后一段可以接在前一段的铜上。
|
||||
为什么不用 route_groups:它按「最近焊盘」贪心连树,SCL/SDA 这种金手指 → 上拉电阻 → U3 → U4 的总线会被连成绕到板西边的树。
|
||||
"""
|
||||
import io
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, os.path.dirname(HERE))
|
||||
sys.path.insert(0, HERE)
|
||||
if not os.environ.get('DCDC_ROUTE_SERVER'):
|
||||
sys.exit(u'⛔ 搜索只在 10.5.5.13 上跑(CLAUDE.md)')
|
||||
import router as RT # noqa: E402
|
||||
|
||||
W = 0.15
|
||||
|
||||
|
||||
def grid_now(pads, segs, vias, halfw):
|
||||
board_vias = [(v[0], v[1], v[2], RT.VIA_D) for v in vias]
|
||||
g = RT.build_grid(pads, halfw, board_vias)
|
||||
for (l, a, b, c, d, ww, nn) in segs:
|
||||
RT.block_track(g, l, a, b, c, d, halfw + ww / 2.0, nn,
|
||||
extra=max(0.0, RT.VIA_D / 2.0 + RT.CLR - halfw), exact_v=True)
|
||||
for (vx, vy, nn) in vias:
|
||||
rv = RT.VIA_D / 2.0 + RT.CLR + halfw
|
||||
ev = max(0.0, RT.VIA_D / 2.0 - halfw)
|
||||
for l in RT.LAY:
|
||||
g.block_both(l, vx - rv, vy - rv, vx + rv, vy + rv, ev, net=nn, exact_v=True)
|
||||
return g
|
||||
|
||||
|
||||
def nodes_near(g, x, y, lay, net, r=0.4, own_r=0.3):
|
||||
u"""⚠ 孔 / 焊盘自己那一圈格点常被判成「两个网共占」(255:旁边别网孔的膨胀框压进来了),A* 起不了步。
|
||||
点本身就是本网的铜 ⇒ 0.3 mm 以内的格点强制记成本网(两张图都改),出了这一圈照常判。"""
|
||||
k_ = g.nid(net)
|
||||
kk = int(math.ceil(own_r / RT.G))
|
||||
for di in range(-kk, kk + 1):
|
||||
for dj in range(-kk, kk + 1):
|
||||
i, j = RT.gx(x) + di, RT.gy(y) + dj
|
||||
if math.hypot(RT.wx(i) - x, RT.wy(j) - y) <= own_r + 1e-9 and 0 <= i < RT.NX and 0 <= j < RT.NY:
|
||||
g.own[lay][g.idx(i, j)] = k_
|
||||
g.ownv[lay][g.idx(i, j)] = k_
|
||||
out = []
|
||||
i0, j0 = RT.gx(x), RT.gy(y)
|
||||
k = int(math.ceil(r / RT.G))
|
||||
for di in range(-k, k + 1):
|
||||
for dj in range(-k, k + 1):
|
||||
i, j = i0 + di, j0 + dj
|
||||
if math.hypot(RT.wx(i) - x, RT.wy(j) - y) <= r + 1e-9 and g.free(lay, i, j, net):
|
||||
out.append((lay, i, j))
|
||||
return out
|
||||
|
||||
|
||||
def main():
|
||||
spec = json.load(io.open(sys.argv[1], encoding='utf-8'))
|
||||
base = json.load(io.open(os.path.join(HERE, spec['base']), encoding='utf-8'))
|
||||
pads = RT.load_pads()
|
||||
halfw = W / 2.0 + RT.CLR
|
||||
for k, legs in enumerate(spec['variants']):
|
||||
segs = [tuple(s) for s in base['segs']]
|
||||
vias = [tuple(v) for v in base['vias']]
|
||||
L, bad = {}, []
|
||||
for net, a, b in legs:
|
||||
g = grid_now(pads, segs, vias, halfw)
|
||||
st = nodes_near(g, a[0], a[1], a[2], net)
|
||||
gl = nodes_near(g, b[0], b[1], b[2], net)
|
||||
path = RT.astar(g, st, gl, net, halfw) if st and gl else None
|
||||
if not path:
|
||||
inv = {v: kk for kk, v in g._id.items()}
|
||||
def who(p):
|
||||
return sorted(set(str(inv.get(g.own[p[2]][g.idx(RT.gx(p[0]) + di, RT.gy(p[1]) + dj)], '·'))
|
||||
for di in (-1, 0, 1) for dj in (-1, 0, 1)))
|
||||
bad.append(u'%s %s→%s(起点 %d %s / 终点 %d %s 个格点)' % (net, a, b, len(st), who(a), len(gl), who(b)))
|
||||
if spec.get('debug'):
|
||||
kn = g.nid(net)
|
||||
for lay in RT.LAY:
|
||||
print(u'--- %s L%d 周围(.=空 o=本网 #=别网,行=y 从大到小)' % (net, lay))
|
||||
for j in range(RT.gy(a[1]) + 14, RT.gy(a[1]) - 8, -1):
|
||||
row = ''
|
||||
for i in range(RT.gx(a[0]) - 16, RT.gx(a[0]) + 16):
|
||||
v = g.own[lay][g.idx(i, j)]
|
||||
vv = g.ownv[lay][g.idx(i, j)]
|
||||
row += '.' if v == 0 else ('o' if v == kn else '#')
|
||||
row += ',' if vv == 0 else ('o' if vv == kn else '#')
|
||||
print('%6.2f %s' % (RT.wy(j), row))
|
||||
continue
|
||||
sg, vv = RT.to_segments(path)
|
||||
new = [(l, x0, y0, x1, y1, W, net) for (l, x0, y0, x1, y1) in sg]
|
||||
p0, p1 = path[0], path[-1]
|
||||
for (pt, nd) in ((a, p0), (b, p1)):
|
||||
if math.hypot(RT.wx(nd[1]) - pt[0], RT.wy(nd[2]) - pt[1]) > 1e-6:
|
||||
new.append((nd[0], pt[0], pt[1], RT.wx(nd[1]), RT.wy(nd[2]), W, net))
|
||||
segs += new
|
||||
vias += [(x, y, net) for (x, y) in vv]
|
||||
L[net] = L.get(net, 0.0) + sum(math.hypot(s[3] - s[1], s[4] - s[2]) for s in new)
|
||||
print(u'[%d] %s 未通 %s' % (k, ' '.join('%s=%.1f' % kv for kv in sorted(L.items())), bad))
|
||||
json.dump({'segs': [list(s) for s in segs], 'vias': [list(v) for v in vias], 'logs': bad},
|
||||
io.open(os.path.join(HERE, spec['out'] % k), 'w', encoding='utf-8'), ensure_ascii=False)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
在新工单中引用
屏蔽一个用户