# -*- coding: utf-8 -*- u"""**底板**的定点补线器:在板上现有铜之上只布指定的几个网,别的一根线都不动。 # ⛔ A* 只在 10.5.5.13 上跑(CLAUDE.md)。入口: bash .claude/r2/remote_repair.sh FAN_12V_SW FAN_IN_TACH_RAW FAN_OUT_TACH_RAW FAN_PWM_L # 服务器上实际执行的是: DCDC_ROUTE_SERVER=1 pypy3 .claude/r2/repair_base.py <网名...> `repair_route.py` 是**刀片**那块板的同名工具(硬编码 `_blade_*`),底板用不了: 板级常量、障碍(12V 大铜 / 天线净空 / NPTH / 通孔焊盘副本)全在 `route_base` 里。 这里的做法是**直接 import route_base** —— 它模块顶层就把 `router` 配成底板了 (`RT.B / PCB / PADS / CLR / WIDTH / build_grid / pen_pads` …), 而它的 `__main__` 有保护、顶层没有任何动板调用,import 安全。 ## 现有铜从哪来 `_base_fan_input.json`(本机 `remote_repair.sh` 抓板生成,只含 L1/L4 的铜): {"segs": [[layer, x1, y1, x2, y2, w, net], ...], # 板上现有导线 "vias": [[x, y, net], ...], # 板上现有过孔 "board_vias": [[x, y, net, d], ...]} # 同上,另作固定障碍 ⛔ **不读 `_base_routes.json`**。那是布线器自己的缓存,和板上现状能差出几十段 (用户在客户端手工改过线、内层还手布了 126 段)。补线必须以**板上**为准。 ## 内层(L15 / L16)的线不在这里 `router` 只布 L1/L4,内层是地平面。但板上内层有用户手布的线,**新打的过孔会穿过它们**, 而这一层关系 router 不查 ⇒ 跑完必须在本机用 `remote_repair.sh` 的回读一步核 「新孔 vs 内层线」的净距(脚本里已经做了)。 """ import io import json 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'⛔ 补线(A*)只在 10.5.5.13 上跑(CLAUDE.md):bash .claude/r2/remote_repair.sh <网名...>') import router as RT # noqa: E402 import route_base as RB # noqa: E402 if os.environ.get('FAN_W'): _w = float(os.environ['FAN_W']) for _n in ('FAN_12V_SW', 'FAN_IN_TACH_RAW', 'FAN_OUT_TACH_RAW', 'FAN_PWM_L'): RT.WIDTH[_n] = _w _orig_rule_min = RT.rule_min RT.rule_min = lambda net, auto: (_w if net.startswith('FAN_') else _orig_rule_min(net, auto)) print(u'线宽覆盖:FAN_* = %.2f mm' % _w) # ⚠ 自动网名里的 `$` 写成 `@` 传进来(同 `repair_route.py`):网名经 ssh 命令串传到远端, # 远端 shell 会把 `$6N45` 当变量展开成空串 —— 不转义的话补的是一个叫「」的网,还不报错。 NETS = [a.replace('@', '$') for a in sys.argv[1:] if not a.startswith('--')] or \ ['FAN_12V_SW', 'FAN_IN_TACH_RAW', 'FAN_OUT_TACH_RAW', 'FAN_PWM_L'] # `FAN_W=0.15`:把这几个网的线宽压到规则下限再布。0402 的两只脚净距只有 0.30, # 默认宽度下 A* 在 0.25 网格上找不到出口格点 —— 报「没布通」,看着像没地方走,其实是出不来。 # 测速 / PWM 是小信号,0.15 足够;`FAN_12V_SW` 载的是一只风扇的电流(≈0.2 A),也够。 _FW = os.environ.get('FAN_W') if _FW: for _n in NETS: RT_W = float(_FW) globals().setdefault('_W_OVERRIDE', {})[_n] = RT_W SRC = os.path.join(HERE, '_base_fan_input.json') OUT = os.path.join(HERE, '_base_fan_repair.json') cp = json.load(io.open(SRC, encoding='utf-8')) segs = [tuple(s) for s in cp['segs']] vias = [tuple(v) for v in cp['vias']] board_vias = [tuple(v) for v in cp['board_vias']] pads = RB.load_pads() # 参与布线的焊盘:去掉通孔盘的 L1 障碍副本(`route_base.TH_SUFFIX`)和平面网 bypad = {} for p in pads: if not p[7] or p[7] not in NETS or str(p[1]).endswith(RB.TH_SUFFIX): continue bypad.setdefault(p[7], []).append(p) missing = [n for n in NETS if n not in bypad] if missing: sys.exit(u'⛔ 这些网在 _base_pads.json 里一只脚都没有(焊盘缓存过期?): %s' % ' '.join(missing)) for n in NETS: print(u' %-18s %d 只脚: %s' % (n, len(bypad[n]), ' '.join('%s.%s' % (q[0], q[1]) for q in bypad[n]))) print(u'现有铜 %d 段 / %d 孔(固定障碍孔 %d);要补 %s' % (len(segs), len(vias), len(board_vias), ' '.join(NETS))) n0, v0 = len(segs), len(vias) RT.AUTO_CACHE.clear() logs = RT.route_groups(bypad, pads, segs, vias, board_vias, {}, priority=tuple(NETS), only=set(NETS)) # `--repair`:还有没布通的就拆线重布。⛔ 只拆**本次这几个网自己**刚布的线 # (`fixed_s/fixed_v` 把板上原有的铜全部钉死)—— 板上别的铜是用户手工调过的,一根都不许动。 if logs and '--repair' in sys.argv: print(u' 还剩 %d 条没布通,进入拆线重布(只拆本次新布的)' % len(logs)) segs, vias, logs = RT.repair(bypad, pads, segs, vias, logs, board_vias, {}, n0, v0) new_s, new_v = [list(s) for s in segs[n0:]], [list(v) for v in vias[v0:]] print(u'新增 %d 段 / %d 孔' % (len(new_s), len(new_v))) for m in logs: print(u' [!] ' + m) chk = [q for q in RB.check_pads(pads) if q[7] in set(NETS)] left = RT.check_connected(chk, segs, vias) print(u'连通性(布线器口径):%s' % (u'这几个网全通' if not left else left)) json.dump({'nets': NETS, 'segs': new_s, 'vias': new_v, 'logs': list(logs), 'unconnected': list(left)}, io.open(OUT, 'w', encoding='utf-8'), ensure_ascii=False) print(u'已写 %s' % os.path.basename(OUT))