文件
phonerail/.claude/r2/d16_rip.py
T

180 行
9.3 KiB
Python
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# -*- 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()