R2:底板 / 刀片布局布线脚本、快照、截图与规划文档更新(含外壳、打印件、布线归档)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
@@ -0,0 +1,179 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
u"""D16:拆 U13(INA226)东侧 I²C 三线束重布,连同 CH224A 的 SDA / SCL 一起接上(服务器跑)。
|
||||
|
||||
DCDC_ROUTE_SERVER=1 /root/pypy/bin/pypy3 .claude/r2/d16_rip.py [--extra 结果文件 ...]
|
||||
|
||||
为什么:CH224A 改挂主 I²C 后(用户 2026-09-12),南边唯一的 SDA 是 U13.4 —— 东边 INA_ALERT / SCL 两根线与 SDA
|
||||
0.5 mm 并排走出去、西边本体底下有一段 SCL 把 2、5 脚连起来,这束一直夹着走进北边被圈住的区域,中间放不下一个孔
|
||||
(d16_tap 30 个落点全不通)。用户选「拆这一束重布」,原理图不动。
|
||||
|
||||
做法(**只改数据、不动板**):快照里拿掉拆除区 RIP_BOX 内 RIP_NETS 的外层线 / 孔(KEEP_BOX 里 U13 本体底下那段
|
||||
2—5 脚 SCL 连线留着)→ 逐网把孤立小块接到大块上:小块用自己离大块最近的焊盘,大块用它**已有铜**(线段每 0.5 mm
|
||||
一点 + 孔)上离小块最近的若干点当虚拟焊盘,由近到远逐个试,接上即停;接不上的块跳过、接着试别的。
|
||||
第一阶段先把 U13 的三个网接回北边主干,第二阶段再接 CH224A(U36)的两只脚。
|
||||
|
||||
⚠ 三根线要挤过 U13 东北那条走廊,**先后和线宽决定挤不挤得下**(手猜两轮:SDA 先 → SCL 的 U13.2 接不上;
|
||||
SCL 先 → SDA 回不去)⇒ 6 种先后 × 线宽 0.2 / 0.15 共 12 组变体,每组从同一份拆完的快照起跑,
|
||||
按(断的块数、孔数、线长)取最优。写 _d16_rip.out.json:{'ripped_segs', 'ripped_vias', 'segs', 'vias',
|
||||
'nets', 'logs', 'variant', 'scores'};本机 d16_check 复核后 d16_push 先删后建。
|
||||
"""
|
||||
import io
|
||||
import itertools
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
|
||||
if os.environ.get('DCDC_ROUTE_SERVER') != '1':
|
||||
sys.exit(u'!! 布线运算只在服务器上跑(DCDC_ROUTE_SERVER=1),见 CLAUDE.md')
|
||||
if '--grid' not in sys.argv:
|
||||
sys.argv += ['--grid', '0.125']
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, os.path.dirname(HERE))
|
||||
sys.path.insert(0, HERE)
|
||||
import route_nets as N # noqa: E402
|
||||
|
||||
R = N.R
|
||||
RIP_NETS = ('SDA', 'SCL', 'INA_ALERT')
|
||||
RIP_BOX = (15.0, 38.0, 40.0, 58.0)
|
||||
# U13 本体底下把 2、5 脚(都是 SCL)连起来的那段不拆:5 脚东侧空出来给 CH224A 的 SCL 落脚
|
||||
KEEP_BOX = (19.5, 42.0, 22.5, 44.6)
|
||||
LATE = ('U36',)
|
||||
WIDTHS = (0.2, 0.15)
|
||||
TRIES = 30
|
||||
# 试算开关:--tries N(每块候选落点数)/ --orders 'A,B,C;B,A'(只试这几种先后;不在里面的网拆了不接回)/ --widths 0.15,0.2
|
||||
if '--tries' in sys.argv:
|
||||
TRIES = int(sys.argv[sys.argv.index('--tries') + 1])
|
||||
ORDERS = None
|
||||
if '--orders' in sys.argv:
|
||||
ORDERS = [tuple(o.split(',')) for o in sys.argv[sys.argv.index('--orders') + 1].split(';')]
|
||||
# --steps 'INA_ALERT:1,SDA:1,SDA:2,SCL:1,SCL:2/...':逐步指定「网:阶段」(阶段 2 = 只接 U36 的块),几种排法用 / 隔开
|
||||
# (⚠ 不用分号:remote_d16.sh 把参数不加引号地拼进远程命令,分号会把命令截断)。
|
||||
# 实验 1(2026-09-12):INA→SDA→SCL 三网都接回北边,但 U13 的 SCL 新路从南边兜过 U36,把 U36.3 和 SDA 隔开 ⇒ U36.3 要排在 U13 的 SCL 前面
|
||||
STEPS = None
|
||||
if '--steps' in sys.argv:
|
||||
STEPS = [[(t.split(':')[0], int(t.split(':')[1])) for t in seq.split(',')]
|
||||
for seq in sys.argv[sys.argv.index('--steps') + 1].split('/')]
|
||||
if '--widths' in sys.argv:
|
||||
WIDTHS = tuple(float(x) for x in sys.argv[sys.argv.index('--widths') + 1].split(','))
|
||||
|
||||
|
||||
def inbox(x, y, b):
|
||||
return b[0] <= x <= b[2] and b[1] <= y <= b[3]
|
||||
|
||||
|
||||
def L(s):
|
||||
return math.hypot(s[3] - s[1], s[4] - s[2])
|
||||
|
||||
|
||||
def candidates(net, segs, vias, tx, ty):
|
||||
c = []
|
||||
for s in segs:
|
||||
if s[6] != net or s[0] not in (1, 2):
|
||||
continue
|
||||
n = max(1, int(L(s) / 0.5))
|
||||
for k in range(n + 1):
|
||||
x, y = s[1] + (s[3] - s[1]) * k / n, s[2] + (s[4] - s[2]) * k / n
|
||||
c.append((math.hypot(x - tx, y - ty), s[0], round(x, 3), round(y, 3), max(s[5], 0.2)))
|
||||
for v in vias:
|
||||
if v[2] == net:
|
||||
for l in (1, 2):
|
||||
c.append((math.hypot(v[0] - tx, v[1] - ty), l, round(v[0], 3), round(v[1], 3), 0.6))
|
||||
c.sort()
|
||||
out = []
|
||||
for q in c:
|
||||
if all(math.hypot(q[2] - p[2], q[3] - p[3]) > 1.0 or q[1] != p[1] for p in out):
|
||||
out.append(q)
|
||||
if len(out) >= TRIES:
|
||||
break
|
||||
return out
|
||||
|
||||
|
||||
def run_variant(order, w, pads, base_s, base_v):
|
||||
segs, vias = list(base_s), list(base_v)
|
||||
new_s, new_v, logs, stat = [], [], [], {}
|
||||
|
||||
def connect(net, phase):
|
||||
groups = N.components(net, pads, segs, vias)
|
||||
g0 = len(groups)
|
||||
failed = set()
|
||||
while True:
|
||||
groups.sort(key=len)
|
||||
big = groups[-1]
|
||||
bx = sum(q[2] for q in big) / len(big)
|
||||
by = sum(q[3] for q in big) / len(big)
|
||||
smalls = [g for g in groups[:-1] if (all(q[0] in LATE for q in g)) == (phase == 2)
|
||||
and frozenset((q[0], str(q[1])) for q in g) not in failed]
|
||||
if not smalls:
|
||||
break
|
||||
smalls.sort(key=lambda g: min(math.hypot(q[2] - bx, q[3] - by) for q in g))
|
||||
sm = smalls[0]
|
||||
done = False
|
||||
for sp in sorted(sm, key=lambda q: math.hypot(q[2] - bx, q[3] - by))[:2]:
|
||||
for d, l, x, y, ww in candidates(net, [q for q in segs if q[6] == net], [q for q in vias if q[2] == net],
|
||||
sp[2], sp[3]):
|
||||
vp = ['#tap', '1', x, y, ww, ww, l, net, 'RECT']
|
||||
N.INNER[:] = [q for q in segs if q[0] in (15, 16)]
|
||||
lg = []
|
||||
s2, v2 = R.route_one(net, [sp, vp], w, pads + [vp], [q for q in segs if q[0] in (1, 2)], vias, [], lg)
|
||||
s2 = [tuple(q) for q in s2]
|
||||
v2 = [tuple(q) for q in v2]
|
||||
if len(N.components(net, pads, segs + s2, vias + v2)) < len(groups):
|
||||
segs.extend(s2); vias.extend(v2); new_s.extend(s2); new_v.extend(v2)
|
||||
logs.append(u'%s:%s.%s → L%s (%.2f,%.2f),%d 段 / %d 孔' % (net, sp[0], sp[1], l, x, y, len(s2), len(v2)))
|
||||
done = True
|
||||
break
|
||||
if done:
|
||||
break
|
||||
if not done:
|
||||
failed.add(frozenset((q[0], str(q[1])) for q in sm))
|
||||
logs.append(u'%s: %s 接不上' % (net, ','.join('%s.%s' % (q[0], q[1]) for q in sm)))
|
||||
groups = N.components(net, pads, segs, vias)
|
||||
return g0, len(groups)
|
||||
|
||||
steps = order if (order and isinstance(order[0], tuple)) else [(n, 1) for n in order] + [(n, 2) for n in ('SDA', 'SCL') if n in order]
|
||||
for net, phase in steps:
|
||||
g0, g1 = connect(net, phase)
|
||||
stat.setdefault(net, [g0, g1])[1] = g1
|
||||
broken = sum(v[1] - 1 for v in stat.values())
|
||||
score = (broken, len(new_v), round(sum(L(s) for s in new_s), 1))
|
||||
return score, {'segs': [list(s) for s in new_s], 'vias': [list(v) for v in new_v], 'nets': stat, 'logs': logs}
|
||||
|
||||
|
||||
def main():
|
||||
b = json.load(io.open(os.path.join(HERE, '_board_for_nets.json'), encoding='utf-8'))
|
||||
segs = [tuple(s[:7]) for s in b['segs']]
|
||||
vias = [tuple(v[:3]) for v in b['vias']]
|
||||
for k, a in enumerate(sys.argv):
|
||||
if a == '--extra':
|
||||
e = json.load(io.open(os.path.join(HERE, sys.argv[k + 1]), encoding='utf-8'))
|
||||
segs += [tuple(s[:7]) for s in e['segs']]
|
||||
vias += [tuple(v[:3]) for v in e['vias']]
|
||||
rs = [s for s in segs if s[6] in RIP_NETS and s[0] in (1, 2) and inbox(s[1], s[2], RIP_BOX) and inbox(s[3], s[4], RIP_BOX)
|
||||
and not (s[6] == 'SCL' and inbox(s[1], s[2], KEEP_BOX) and inbox(s[3], s[4], KEEP_BOX))]
|
||||
rv = [v for v in vias if v[2] in RIP_NETS and inbox(v[0], v[1], RIP_BOX)]
|
||||
base_s = [s for s in segs if s not in rs]
|
||||
base_v = [v for v in vias if v not in rv]
|
||||
print(u'拆除区 %s 内拆掉:%s' % (RIP_BOX, {n: (sum(1 for s in rs if s[6] == n), sum(1 for v in rv if v[2] == n))
|
||||
for n in RIP_NETS}))
|
||||
pads = [p for p in R.load_pads() if not str(p[1]).endswith(R.TH_SUFFIX)]
|
||||
best, scores = None, []
|
||||
for order in (STEPS or ORDERS or list(itertools.permutations(RIP_NETS))):
|
||||
for w in WIDTHS:
|
||||
sc, res = run_variant(order, w, pads, base_s, base_v)
|
||||
name = '→'.join(('%s:%d' % t) if isinstance(t, tuple) else t for t in order)
|
||||
scores.append([name, w, list(sc)])
|
||||
print(u' %-44s 线宽 %.2f:断 %d 块 / %d 孔 / %.1f mm' % (name, w, sc[0], sc[1], sc[2]))
|
||||
if best is None or sc < best[0]:
|
||||
best = (sc, res, name, w)
|
||||
sc, res, order, w = best
|
||||
print(u'最优:%s 线宽 %.2f —— 断 %d 块 / %d 孔 / %.1f mm' % (order, w, sc[0], sc[1], sc[2]))
|
||||
for m in res['logs']:
|
||||
print(u' ' + m)
|
||||
res.update({'ripped_segs': [list(s) for s in rs], 'ripped_vias': [list(v) for v in rv],
|
||||
'variant': {'order': order, 'w': w}, 'scores': scores, 'pd_refs': ['U36', 'U13']})
|
||||
io.open(os.path.join(HERE, '_d16_rip.out.json'), 'w', encoding='utf-8').write(json.dumps(res, ensure_ascii=False))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
在新工单中引用
屏蔽一个用户