# -*- coding: utf-8 -*- u"""服务器上挪几颗「戳在内层线上」的过孔(2026-09-11 混合布线收尾):路径不重布,只把孔挪到附近合法的点, 原来接在孔上的线段改从它们各自的远端接到新孔(线宽不变)。 DCDC_ROUTE_SERVER=1 /root/pypy/bin/pypy3 .claude/r2/via_nudge.py NET@x,y [NET@x,y ...] 读 _board_for_nets.json,写 _via_nudge.out.json 候选点:原孔周围 1.5 mm、0.0625 网格。条件:新孔离四层所有异网线 / 孔 / 焊盘 ≥ CLR;改接的每段线在它那一层离异网铜 ≥ CLR; 新孔也不许落进任何贴片焊盘(同网也不行)。取改接线总长最短。 """ import io 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') HERE = os.path.dirname(os.path.abspath(__file__)) CLR = 0.16 # 规则 0.15,留 0.01 VR = 0.305 # 0.61 孔 L = lambda s: math.hypot(s[3] - s[1], s[4] - s[2]) # noqa: E731 def pd(px, py, s): x1, y1, x2, y2 = s[1], s[2], s[3], s[4] dx, dy = x2 - x1, y2 - y1 L2 = dx * dx + dy * dy t = 0 if L2 == 0 else max(0, min(1, ((px - x1) * dx + (py - y1) * dy) / L2)) return math.hypot(px - (x1 + t * dx), py - (y1 + t * dy)) def rd(q, x, y): return math.hypot(max(q[2] - q[4] / 2 - x, x - q[2] - q[4] / 2, 0), max(q[3] - q[5] / 2 - y, y - q[3] - q[5] / 2, 0)) def main(): b = json.load(io.open(os.path.join(HERE, '_board_for_nets.json'), encoding='utf-8')) pads = [p for p in json.load(io.open(os.path.join(HERE, '_base_pads.json'), encoding='utf-8')) if p[4] > 0.05] segs = [list(s[:7]) for s in b['segs']] vias = [list(v[:3]) for v in b['vias']] out = [] for spec in [a for a in sys.argv[1:] if '@' in a]: net, xy = spec.split('@') ox, oy = (float(t) for t in xy.split(',')) old_v = [v for v in vias if v[2] == net and math.hypot(v[0] - ox, v[1] - oy) < 0.05] att = [s for s in segs if s[6] == net and (math.hypot(s[1] - ox, s[2] - oy) < 0.05 or math.hypot(s[3] - ox, s[4] - oy) < 0.05)] if not old_v: print(u'%s:找不到这颗孔' % spec) continue far = [] for s in att: fx, fy = (s[3], s[4]) if math.hypot(s[1] - ox, s[2] - oy) < 0.05 else (s[1], s[2]) far.append((s[0], fx, fy, s[5])) rest_s = [s for s in segs if not any(s is a for a in att)] rest_v = [v for v in vias if not any(v is o for o in old_v)] # 附近的障碍先筛一遍 box = (ox - 5, oy - 5, ox + 5, oy + 5) nb_s = [s for s in rest_s if s[6] != net and not (max(s[1], s[3]) < box[0] or min(s[1], s[3]) > box[2] or max(s[2], s[4]) < box[1] or min(s[2], s[4]) > box[3])] nb_v = [v for v in rest_v if box[0] <= v[0] <= box[2] and box[1] <= v[1] <= box[3]] nb_p = [q for q in pads if box[0] - 2 <= q[2] <= box[2] + 2 and box[1] - 2 <= q[3] <= box[3] + 2] def via_ok(x, y): if any(pd(x, y, s) - VR - s[5] / 2 < CLR for s in nb_s): return False if any(math.hypot(v[0] - x, v[1] - y) < 2 * VR + CLR for v in nb_v if v[2] != net): return False if any(math.hypot(v[0] - x, v[1] - y) < 2 * VR + 0.3 for v in nb_v if v[2] == net): return False # 同网孔也别叠(钻孔间距) for q in nb_p: if q[6] in (1, 2) and rd(q, x, y) < VR + (0.0 if q[7] == net else CLR) + (0.05 if q[7] == net else 0): return False # 贴片焊盘里不落孔(同网也不行) if q[6] == 12 and q[7] != net and rd(q, x, y) < VR + CLR: return False return True def seg_ok(lay, a, c, w): n = max(2, int(math.hypot(c[0] - a[0], c[1] - a[1]) / 0.03)) pts = [(a[0] + (c[0] - a[0]) * k / n, a[1] + (c[1] - a[1]) * k / n) for k in range(n + 1)] for s in nb_s: if s[0] == lay and min(pd(x, y, s) for x, y in pts) - w / 2 - s[5] / 2 < CLR: return False for v in nb_v: if v[2] != net and min(math.hypot(v[0] - x, v[1] - y) for x, y in pts) - w / 2 - VR < CLR: return False for q in nb_p: if q[7] != net and q[6] in (lay, 12) and min(rd(q, x, y) for x, y in pts) - w / 2 < CLR: return False return True best = None for i in range(-24, 25): for j in range(-24, 25): x, y = round(ox + i * 0.0625, 4), round(oy + j * 0.0625, 4) if math.hypot(x - ox, y - oy) > 1.5 or not via_ok(x, y): continue if not all(seg_ok(lay, (fx, fy), (x, y), w) for lay, fx, fy, w in far): continue cost = sum(math.hypot(x - fx, y - fy) for _l, fx, fy, _w in far) if best is None or cost < best[0]: best = (cost, x, y) if best is None: print(u'%s:1.5 mm 内找不到合法的新孔位' % spec) out.append({'spec': spec, 'ok': False}) continue _c, nx, ny = best new_s = [[lay, fx, fy, nx, ny, w, net] for lay, fx, fy, w in far] old_len = sum(L(s) for s in att) print(u'%s:→ (%.4f, %.4f),挪 %.2f mm;改接 %d 段(%.2f → %.2f mm)' % (spec, nx, ny, math.hypot(nx - ox, ny - oy), len(new_s), old_len, sum(L(s) for s in new_s))) out.append({'spec': spec, 'ok': True, 'old_via': old_v[0], 'old_segs': att, 'new_via': [nx, ny, net], 'new_segs': new_s}) # 下一颗要看到这颗的新位置 segs = rest_s + new_s vias = rest_v + [[nx, ny, net]] io.open(os.path.join(HERE, '_via_nudge.out.json'), 'w', encoding='utf-8').write(json.dumps(out, ensure_ascii=False)) if __name__ == '__main__': main()