文件
zhou yang和Claude Opus 5 efcba30a0c R2 底板改 4 层(D15):规则 / 几何 / 背面摆位 / 4 层布线与铺铜,已下发
- 规则:铜层 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
2026-09-10 21:02:10 +08:00

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