127 行
6.4 KiB
Python
127 行
6.4 KiB
Python
# -*- coding: utf-8 -*-
|
||
u"""在现有铜之上**只重布几根绕远的慢速信号**(2026-09-15,BLADE-PCB_1 新摆位)。
|
||
|
||
# 本机:先把要重布的网从缓存里拆掉(保留 ESC 逃逸孔 / 金手指引出),写成 _blade_routes.json
|
||
# 服务器(搜索只在服务器跑):
|
||
DCDC_ROUTE_SERVER=1 pypy3 .claude/r2/opt_blade1.py <网,网,...> [<网,网,...> ...]
|
||
# 每个参数 = 一种布线顺序,结果写 _blade_routes.opt<k>.json(合并后的完整缓存)
|
||
|
||
和 `repair_route.py` 的区别:
|
||
· 起止点用 **ESC 逃逸孔**(router.ESC_VIAS)当虚拟焊盘,不从被围住的原焊盘起步(repair 在 U3/U4 上一直「没布通」);
|
||
· 线宽强制 0.15(规则默认值;repair 按 rule_width 走出了 0.30,同样的走廊少一半通道)。
|
||
"""
|
||
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
|
||
import pcb_rules as R # noqa: E402
|
||
|
||
W = 0.15
|
||
|
||
|
||
def main():
|
||
base = json.load(io.open(os.path.join(HERE, '_blade_routes.json'), encoding='utf-8'))
|
||
pads = RT.load_pads()
|
||
auto = R.resolve_auto()
|
||
_fs, _fv, launch = RT.finger_escape()
|
||
esc = {k: v for k, v in RT.ESC_VIAS.items()}
|
||
vp = os.path.join(HERE, '_opt_virt.json')
|
||
VIRT = json.load(io.open(vp, encoding='utf-8')) if os.path.exists(vp) else {}
|
||
for k_, order in enumerate(sys.argv[1:]):
|
||
nets = [n.replace('@', '$') for n in order.split(',') if n]
|
||
segs = [tuple(s) for s in base['segs']]
|
||
vias = [tuple(v) for v in base['vias']]
|
||
board_vias = [(v[0], v[1], v[2], RT.VIA_D) for v in vias]
|
||
# U2 的 QFN 预扇出(被拆掉了就补回来):扇出段落铜,焊盘换成扇出端点的虚拟焊盘
|
||
qs, qv, qvirt = RT.qfn_fan(pads)
|
||
have = set(tuple(round(c, 3) for c in s[1:5]) for s in segs)
|
||
qmap = {}
|
||
for sg in qs:
|
||
if sg[6] in nets and tuple(round(c, 3) for c in sg[1:5]) not in have:
|
||
segs.append(tuple(sg))
|
||
for (x, y, n) in qv:
|
||
if n in nets:
|
||
vias.append((x, y, n))
|
||
for p, (ex, ey), lay in qvirt:
|
||
qmap[(p[0], p[1])] = (ex, ey, lay)
|
||
# 手算过的出口(在 BLADE-PCB_1 定稿缓存上核过净距):U2.2 扇出端点 → R15.1;R40.1 西出打孔
|
||
# 2026-09-15 晚:上拉电阻 R13/R15/R40/R41 挪到 U4 东南之后,原来的 R15.1 / R40.1 / R41.1 手算出口作废
|
||
FIX = {}
|
||
fixdrop, fixvirt = set(), {}
|
||
for net, (fs_, fv_, fd_, fvp) in FIX.items():
|
||
if net in nets:
|
||
segs += [tuple(x) for x in fs_]
|
||
vias += [tuple(x) for x in fv_]
|
||
fixdrop |= set(fd_)
|
||
if fvp:
|
||
fixvirt[net] = fvp
|
||
_es, _ev, _evirt, edrop = RT.esc_fan(pads)
|
||
have = set(tuple(round(c, 3) for c in sg[1:5]) for sg in segs)
|
||
for sg in _es: # ESC_PATHS 的折线(被拆掉了就补回来)
|
||
if sg[6] in nets and tuple(round(c, 3) for c in sg[1:5]) not in have:
|
||
segs.append(tuple(sg))
|
||
for p, (ex, ey), lay in _evirt:
|
||
if (p[0], p[1]) in RT.ESC_PATHS:
|
||
qmap[(p[0], p[1])] = (ex, ey, lay)
|
||
dropk = set((p[0], p[1]) for p in edrop) | fixdrop
|
||
bypad = {}
|
||
for net in nets:
|
||
ps = []
|
||
for p in pads:
|
||
if p[7] != net or p[0] == 'J1':
|
||
continue
|
||
if (p[0], p[1]) in dropk:
|
||
continue
|
||
if (p[0], p[1]) in qmap:
|
||
ex, ey, lay = qmap[(p[0], p[1])]
|
||
ps.append([p[0], 'qfan:%s' % p[1], ex, ey, 0.3, 0.3, lay, net, 'RECT'])
|
||
continue
|
||
e = esc.get((p[0], p[1]))
|
||
if e:
|
||
lay = RT.LAY[1] if p[6] == RT.LAY[0] else RT.LAY[0]
|
||
ps.append([p[0], 'qfan:%s' % p[1], e[0], e[1], 0.3, 0.3, lay, net, 'RECT'])
|
||
else:
|
||
ps.append(p)
|
||
for (lay, x, y) in launch.get(net, []):
|
||
ps.append(['J1', 'launch', x, y, 0.3, 0.3, lay, net, 'RECT'])
|
||
if net in fixvirt:
|
||
ps.append(fixvirt[net])
|
||
# `_opt_virt.json`(可选):{网: {"drop": [[位号, 脚], ...], "add": [[x, y, 层], ...]}}
|
||
# drop = 已由手定短线接好的焊盘;add = 那些短线的端点,当虚拟焊盘
|
||
if net in VIRT:
|
||
dk = set(tuple(x) for x in VIRT[net].get('drop', []))
|
||
ps = [q for q in ps if (q[0], str(q[1]).split(':')[-1]) not in dk]
|
||
ps += [['VIRT', 'v%d' % n_, x, y, 0.3, 0.3, l, net, 'RECT'] for n_, (x, y, l) in enumerate(VIRT[net].get('add', []))]
|
||
bypad[net] = ps
|
||
RT.rule_width = lambda net, a, _n=set(nets): W if net in _n else RT.W_DEFAULT
|
||
RT.rule_min = lambda net, a: W
|
||
n0, v0 = len(segs), len(vias)
|
||
# 逐网调用 route_groups:顺序真正按参数走(一次传全部时它自己按「出口数 / 跨度」重排,顺序参数不起作用)
|
||
logs = []
|
||
for net in nets:
|
||
logs += RT.route_groups({net: bypad[net]}, pads, segs, vias, board_vias, auto,
|
||
priority=(net,), only={net})
|
||
board_vias = [(v[0], v[1], v[2], RT.VIA_D) for v in vias]
|
||
L = {}
|
||
for s in segs[n0:]:
|
||
L[s[6]] = L.get(s[6], 0.0) + math.hypot(s[3] - s[1], s[4] - s[2])
|
||
print(u'[%d] %s:新增 %d 段 / %d 孔,总长 %.1f,长度 %s,未通 %s'
|
||
% (k_, order, len(segs) - n0, len(vias) - v0, sum(L.values()),
|
||
' '.join('%s=%.1f' % kv for kv in sorted(L.items())), logs))
|
||
json.dump({'segs': [list(s) for s in segs], 'vias': [list(v) for v in vias], 'logs': logs},
|
||
io.open(os.path.join(HERE, '_blade_routes.opt%d.json' % k_), 'w', encoding='utf-8'),
|
||
ensure_ascii=False)
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main()
|