159 行
6.4 KiB
Python
159 行
6.4 KiB
Python
# -*- 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)
|