114 行
5.6 KiB
Python
114 行
5.6 KiB
Python
# -*- 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()
|