# -*- coding: utf-8 -*- u"""给 DRC 报「断开」的 GND 焊盘找一段引出线接到 L2:优先接已有 GND 孔,不行再新打孔(2026-09-14,BLADE-PCB_1)。 MODE=gndtie bash .claude/r2/remote_blade.sh U3.1 U3.2 ... # 服务器上跑(落点是搜索,CLAUDE.md) # 服务器上实际执行:DCDC_ROUTE_SERVER=1 pypy3 .claude/r2/gndtie_blade1.py U3.1 U3.2 ... `router.gnd_vias()` 的候选只在焊盘出口格点附近,这几只脚它都「没地方打孔」。这里放宽成: 焊盘周围 R_MAX 内 0.05 mm 格点(新孔)+ 全部已有 GND 孔(不加孔),引出线 = 焊盘上离目标最近的点 → 目标中心, 按精确几何核:引出线对本层别网的线 / 焊盘 / 全部别网孔,新孔对 L1/L4 别网的线 / 焊盘 / 全部孔 / 定位孔 / 板框。 选总长最短的;逐只脚串行,前一只放下的铜算进后一只的障碍。结果写 `_blade_routes.gndtie.json`(只含新增)。 """ 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 上算:MODE=gndtie bash .claude/r2/remote_blade.sh <脚...>') import hand_blade1 as H # noqa: E402 import pcb_blade as B # noqa: E402 CLR = 0.16 VD = 0.6 W = 0.2 R_MAX = 2.5 STEP = 0.05 cache = json.load(io.open(os.path.join(HERE, '_blade_routes.json'), encoding='utf-8')) segs = [list(s) for s in cache['segs']] vias = [list(v) for v in cache['vias']] vias += [[x, y, 'GND'] for x, y in json.load(io.open(os.path.join(HERE, '_blade_stitch_pts.json'), encoding='utf-8'))] pads = H.PADS holes = [(p[2], p[3], max(p[4], p[5]) / 2.0) for p in pads if p[6] == 12 and p[0].startswith('H')] poly = B.outline_polygon() def rect_dist_pt(x, y, p): return H._pt_rect(x, y, p) def seg_rect_dist(ax, ay, bx, by, p): x0, x1 = p[2] - p[4] / 2.0, p[2] + p[4] / 2.0 y0, y1 = p[3] - p[5] / 2.0, p[3] + p[5] / 2.0 if min(ax, bx) <= x1 and max(ax, bx) >= x0 and min(ay, by) <= y1 and max(ay, by) >= y0: for t in range(21): x, y = ax + (bx - ax) * t / 20.0, ay + (by - ay) * t / 20.0 if x0 <= x <= x1 and y0 <= y <= y1: return 0.0 d = min(H._pt_seg(cx, cy, ax, ay, bx, by) for cx, cy in ((x0, y0), (x0, y1), (x1, y0), (x1, y1))) return min(d, rect_dist_pt(ax, ay, p), rect_dist_pt(bx, by, p)) def edge_dist(x, y): return min(H._pt_seg(x, y, a[0], a[1], b[0], b[1]) for a, b in zip(poly, poly[1:])) def stub_ok(layer, ax, ay, bx, by, own_pad): for s in segs: if s[6] == 'GND' or s[0] != layer: continue if H._seg_seg(s, [layer, ax, ay, bx, by, W, 'GND']) < (s[5] + W) / 2.0 + CLR: return False for v in vias: if v[2] == 'GND': continue if H._pt_seg(v[0], v[1], ax, ay, bx, by) < VD / 2.0 + W / 2.0 + CLR: return False for p in pads: if p is own_pad or p[7] == 'GND' or (p[6] != layer and p[6] != 12): continue if seg_rect_dist(ax, ay, bx, by, p) < W / 2.0 + CLR: return False return True def via_ok(x, y): if edge_dist(x, y) < VD / 2.0 + 0.3: return False for hx, hy, hr in holes: if math.hypot(x - hx, y - hy) < hr + VD / 2.0 + 0.3: return False for v in vias: dmin = VD + (0.3 if v[2] == 'GND' else CLR) # 同网也要守孔距 if math.hypot(x - v[0], y - v[1]) < dmin: return False for s in segs: if s[6] == 'GND': continue if H._pt_seg(x, y, s[1], s[2], s[3], s[4]) < VD / 2.0 + s[5] / 2.0 + CLR: return False for p in pads: if p[7] == 'GND' or p[6] not in (1, 2, 12): continue if rect_dist_pt(x, y, p) < VD / 2.0 + CLR: return False return True def near_pt(p, x, y): return (min(max(x, p[2] - p[4] / 2.0), p[2] + p[4] / 2.0), min(max(y, p[3] - p[5] / 2.0), p[3] + p[5] / 2.0)) def solve(p): best = None for v in vias: # ① 接已有 GND 孔 if v[2] != 'GND': continue ax, ay = near_pt(p, v[0], v[1]) L = math.hypot(v[0] - ax, v[1] - ay) if L > R_MAX or (best and L >= best[0]): continue if stub_ok(p[6], ax, ay, v[0], v[1], p): best = (L, ax, ay, v[0], v[1], False) n = int(R_MAX / STEP) for i in range(-n, n + 1): # ② 新打孔 for j in range(-n, n + 1): x, y = round(p[2] + i * STEP, 3), round(p[3] + j * STEP, 3) ax, ay = near_pt(p, x, y) L = math.hypot(x - ax, y - ay) + 0.3 # 新孔略贵 if L > R_MAX or (best and L >= best[0]): continue # 不做盘中孔(细间距脚上吸锡、要塞孔加钱):本脚和别的 GND 脚都算 —— 否则给 U4.9 打的孔会落进隔壁 U4.10 的盘里 if any(q[7] == 'GND' and q[6] in (1, 2) and rect_dist_pt(x, y, q) < VD / 2.0 + 0.05 for q in pads): continue if via_ok(x, y) and stub_ok(p[6], ax, ay, x, y, p): best = (L, ax, ay, x, y, True) return best out_s, out_v, logs = [], [], [] for tag in [a for a in sys.argv[1:] if not a.startswith('--')]: ref, pin = tag.split('.') ps = [q for q in pads if q[0] == ref and q[1] == pin and q[7] == 'GND'] if not ps: logs.append(u'%s 找不到 GND 焊盘' % tag) continue p = ps[0] b = solve(p) if not b: logs.append(u'%s 2.5 mm 内找不到' % tag) continue L, ax, ay, x, y, newv = b s = [p[6], round(p[2], 3), round(p[3], 3), round(x, 3), round(y, 3), W, 'GND'] segs.append(s) out_s.append(s) if newv: vias.append([x, y, 'GND']) out_v.append([x, y, 'GND']) print(u'%-6s → (%.3f, %.3f) %s,长 %.2f' % (tag, x, y, u'新孔' if newv else u'接已有孔', L)) for m in logs: print(u' [!] ' + m) json.dump({'segs': out_s, 'vias': out_v, 'logs': logs}, io.open(os.path.join(HERE, '_blade_routes.gndtie.json'), 'w', encoding='utf-8'), ensure_ascii=False)