- 规则:铜层 2→4,差分 0.28/0.18,间距 0.15,过孔 0.3/0.2(rules_base) - 几何:98×98 板框 + 4×M3,槽阵居中,L1 12V 大铜 + 指头(pcb_base) - 摆位:五插槽留 L1,其余 133 件翻到 L4;背面镜像规则实测;Hub 放走廊 2、 东移 2.5 mm;扇出圈 / 小件间距 1.5(去耦例外)/ 9 A 过孔阵禁区(place_base) - 叠层按用户决定改为 L1 槽面 / L2 整层 +3V3 / L3 整层 GND / L4 器件面 - 布线:D± 七对按构造布通(USB3 掉头、USB1/2 北去掉头、USB4/5 东绕、 J23 桥),+3V3 / GND / +12V 平面网每脚一孔,0.125 网格 + PathFinder 开关(route_base、remote_route.ps1);剩 37 个网待手工补 - 铺铜:4 层重写,入口链自南向北切段 + 断面校核(pour_base); 已下发布线 / 缝合孔 / 铜面轮廓,填铜未成功(待查) - 工具:plot_base / plot_routes / shot_base,board_check 通孔两层都算 Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_0168Bu6SEx3cXXCJLkdnE6zB
121 行
5.2 KiB
Python
121 行
5.2 KiB
Python
# -*- 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
|