# -*- coding: utf-8 -*- u"""手工补线的净距校验:给一组 (layer, x0,y0,x1,y1, w, net) 段和 (x,y,net) 孔, 按 `_m6_board_copper.json` + `_blade_pads.json` 算精确净距(判据 0.15)。 from hand_check import check; check(segs, vias) """ import io, json, math, os HERE = os.path.dirname(os.path.abspath(__file__)) CLR = 0.15 VIA_R = 0.3 PADS = json.load(io.open(os.path.join(HERE, '_blade_pads.json'), encoding='utf-8')) CP = json.load(io.open(os.path.join(HERE, '_m6_board_copper.json'), encoding='utf-8')) SEGS = [tuple(s) for s in CP['segs']] VIAS = [tuple(v) for v in CP['vias']] def d_pt_seg(px, py, x0, y0, x1, y1): dx, dy = x1 - x0, y1 - y0 L2 = dx * dx + dy * dy t = 0 if L2 == 0 else max(0, min(1, ((px - x0) * dx + (py - y0) * dy) / L2)) return math.hypot(px - (x0 + dx * t), py - (y0 + dy * t)) def d_seg_seg(a, b): (x0, y0, x1, y1), (u0, v0, u1, v1) = a, b # 相交? def cross(ax, ay, bx, by): return ax * by - ay * bx r = (x1 - x0, y1 - y0); s = (u1 - u0, v1 - v0) den = cross(r[0], r[1], s[0], s[1]) if abs(den) > 1e-12: t = cross(u0 - x0, v0 - y0, s[0], s[1]) / den u = cross(u0 - x0, v0 - y0, r[0], r[1]) / den if 0 <= t <= 1 and 0 <= u <= 1: return 0.0 return min(d_pt_seg(x0, y0, *b), d_pt_seg(x1, y1, *b), d_pt_seg(u0, v0, *a), d_pt_seg(u1, v1, *a)) def d_seg_rect(seg, rc): u"""线段到轴对齐矩形的距离(近似:矩形四边)。""" x0, y0, x1, y1 = rc if d_pt_seg((x0 + x1) / 2, (y0 + y1) / 2, *seg) < 1e-9: return 0.0 edges = [(x0, y0, x1, y0), (x1, y0, x1, y1), (x1, y1, x0, y1), (x0, y1, x0, y0)] d = min(d_seg_seg(seg, e) for e in edges) # 线段端点在矩形内? for (px, py) in ((seg[0], seg[1]), (seg[2], seg[3])): if x0 <= px <= x1 and y0 <= py <= y1: return 0.0 return d def pad_rect(p): return (p[2] - p[4] / 2, p[3] - p[5] / 2, p[2] + p[4] / 2, p[3] + p[5] / 2) def check(new_segs, new_vias, extra_segs=(), extra_vias=(), ignore_segs=()): u"""返回违规清单。extra_* 是同批次里别的新线(互相也要查);ignore_segs 是准备删掉的旧线(按坐标匹配)。""" bad = [] old_segs = [s for s in SEGS if not any(abs(s[1]-g[1])<1e-3 and abs(s[2]-g[2])<1e-3 and abs(s[3]-g[3])<1e-3 and abs(s[4]-g[4])<1e-3 for g in ignore_segs)] old_vias = list(VIAS) allsegs = old_segs + list(extra_segs) allvias = old_vias + list(extra_vias) for ns in new_segs: l, x0, y0, x1, y1, w, net = ns seg = (x0, y0, x1, y1) for s in allsegs: if s[0] != l or s[6] == net: continue d = d_seg_seg(seg, s[1:5]) - w / 2 - s[5] / 2 if d < CLR - 1e-6: bad.append(u'段 %s L%d 与线 %s (%.2f,%.2f)-(%.2f,%.2f) 净距 %.3f' % (net, l, s[6], s[1], s[2], s[3], s[4], d)) for v in allvias: if v[2] == net: continue d = d_pt_seg(v[0], v[1], *seg) - VIA_R - w / 2 if d < CLR - 1e-6: bad.append(u'段 %s L%d 与孔 %s (%.2f,%.2f) 净距 %.3f' % (net, l, v[2], v[0], v[1], d)) for p in PADS: if p[6] != l or p[7] == net: continue d = d_seg_rect(seg, pad_rect(p)) - w / 2 if d < CLR - 1e-6: bad.append(u'段 %s L%d 与焊盘 %s.%s(%s) 净距 %.3f' % (net, l, p[0], p[1], p[7], d)) for nv in new_vias: vx, vy, net = nv for s in allsegs: if s[6] == net: continue d = d_pt_seg(vx, vy, *s[1:5]) - VIA_R - s[5] / 2 if d < CLR - 1e-6: bad.append(u'孔 %s (%.2f,%.2f) 与线 %s L%d (%.2f,%.2f)-(%.2f,%.2f) 净距 %.3f' % (net, vx, vy, s[6], s[0], s[1], s[2], s[3], s[4], d)) for v in allvias: if v[2] == net and abs(v[0]-vx) < 1e-6 and abs(v[1]-vy) < 1e-6: continue d = math.hypot(v[0] - vx, v[1] - vy) - 2 * VIA_R if d < CLR - 1e-6: bad.append(u'孔 %s (%.2f,%.2f) 与孔 %s (%.2f,%.2f) 净距 %.3f' % (net, vx, vy, v[2], v[0], v[1], d)) for p in PADS: if p[7] == net: continue r = pad_rect(p) d = math.hypot(max(r[0] - vx, 0, vx - r[2]), max(r[1] - vy, 0, vy - r[3])) - VIA_R if d < CLR - 1e-6: bad.append(u'孔 %s (%.2f,%.2f) 与焊盘 %s.%s(%s) L%d 净距 %.3f' % (net, vx, vy, p[0], p[1], p[7], p[6], d)) return bad def corridor(l, x0, y0, x1, y1, w, net, margin=1.0): u"""列出一条候选线周围 margin 内的一切(帮忙找路)。""" seg = (x0, y0, x1, y1) out = [] for s in SEGS: if s[0] == l and s[6] != net and d_seg_seg(seg, s[1:5]) < margin: out.append(u'线 %s (%.2f,%.2f)-(%.2f,%.2f) w%.2f' % (s[6], s[1], s[2], s[3], s[4], s[5])) for v in VIAS: if v[2] != net and d_pt_seg(v[0], v[1], *seg) < margin: out.append(u'孔 %s (%.2f,%.2f)' % (v[2], v[0], v[1])) for p in PADS: if p[6] == l and p[7] != net and d_seg_rect(seg, pad_rect(p)) < margin: out.append(u'盘 %s.%s(%s) (%.2f,%.2f) %.2fx%.2f' % (p[0], p[1], p[7], p[2], p[3], p[4], p[5])) return out