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
这个提交包含在:
@@ -0,0 +1,178 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
u"""对**布线器算出来的几何**自算净距 —— 不过桥接、不等 DRC。
|
||||
|
||||
/c/Python314/python .claude/r2/router_check.py [--list N]
|
||||
|
||||
## 为什么要有这个
|
||||
|
||||
EasyEDA 的 DRC 一轮要 ~10 分钟(下发 → 重铺 → 读面板),而且**明细行读不回来**
|
||||
(`drc_read.py` 能读到分类计数,读不到每一条的对象和坐标)。
|
||||
靠它迭代等于每改一行代码等十分钟、还看不到是哪两个东西撞了。
|
||||
|
||||
⇒ 这里按**真实几何**(线段-线段 / 线段-矩形 / 圆-圆 …)自算,
|
||||
判据和设计规则一致(净距 `MIN_CLR`)。布线器的占用图是 0.25 mm 网格上的
|
||||
轴对齐盒子,是个**近似**;这个检查器算的是精确距离,两者的差就是 DRC 会报的那些。
|
||||
|
||||
⚠ 判据只覆盖「铜与铜」。**铺铜(pour)与过孔/钻孔的关系不在这里** ——
|
||||
那一支归 EasyEDA 自己按规则裁,DRC 里显示成「到 过孔 / 到 钻孔」。
|
||||
"""
|
||||
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)
|
||||
import pcb_blade as B # noqa: E402
|
||||
import router as R # noqa: E402
|
||||
|
||||
MIN_CLR = 0.15 # 设计规则里的安全间距
|
||||
|
||||
|
||||
def seg_seg(a, b):
|
||||
u"""两条线段的最小距离。"""
|
||||
(x1, y1, x2, y2), (x3, y3, x4, y4) = a, b
|
||||
|
||||
def pt_seg(px, py, ax, ay, bx, by):
|
||||
dx, dy = bx - ax, by - ay
|
||||
L = dx * dx + dy * dy
|
||||
t = 0.0 if L == 0 else max(0.0, min(1.0, ((px - ax) * dx + (py - ay) * dy) / L))
|
||||
return math.hypot(px - (ax + t * dx), py - (ay + t * dy))
|
||||
|
||||
d1, d2 = x2 - x1, y2 - y1
|
||||
d3, d4 = x4 - x3, y4 - y3
|
||||
den = d1 * d4 - d2 * d3
|
||||
if abs(den) > 1e-12:
|
||||
t = ((x3 - x1) * d4 - (y3 - y1) * d3) / den
|
||||
u = ((x3 - x1) * d2 - (y3 - y1) * d1) / den
|
||||
if 0 <= t <= 1 and 0 <= u <= 1:
|
||||
return 0.0
|
||||
return min(pt_seg(x1, y1, x3, y3, x4, y4), pt_seg(x2, y2, x3, y3, x4, y4),
|
||||
pt_seg(x3, y3, x1, y1, x2, y2), pt_seg(x4, y4, x1, y1, x2, y2))
|
||||
|
||||
|
||||
def seg_rect(s, r):
|
||||
u"""线段到轴对齐矩形的最小距离(相交为 0)。"""
|
||||
x1, y1, x2, y2 = s
|
||||
rx0, ry0, rx1, ry1 = r
|
||||
for (px, py) in ((x1, y1), (x2, y2)):
|
||||
if rx0 <= px <= rx1 and ry0 <= py <= ry1:
|
||||
return 0.0
|
||||
e = [(rx0, ry0, rx1, ry0), (rx1, ry0, rx1, ry1),
|
||||
(rx1, ry1, rx0, ry1), (rx0, ry1, rx0, ry0)]
|
||||
return min(seg_seg(s, q) for q in e)
|
||||
|
||||
|
||||
def pt_rect(px, py, r):
|
||||
dx = max(r[0] - px, 0.0, px - r[2])
|
||||
dy = max(r[1] - py, 0.0, py - r[3])
|
||||
return math.hypot(dx, dy)
|
||||
|
||||
|
||||
def main():
|
||||
cache = json.load(io.open(os.path.join(HERE, '_blade_routes.json'), encoding='utf-8'))
|
||||
segs, vias = cache['segs'], cache['vias']
|
||||
pads = R.load_pads()
|
||||
holes = B.phone_holes()
|
||||
# 缝合过孔也算进来
|
||||
import pcb_route as PR
|
||||
stitch = [(x, y, 'GND') for (x, y) in PR.stitch_points()]
|
||||
allv = [(v[0], v[1], v[2]) for v in vias] + stitch
|
||||
|
||||
bad = {}
|
||||
|
||||
def hit(kind, msg):
|
||||
bad.setdefault(kind, []).append(msg)
|
||||
|
||||
# ── 线 ↔ 线(同层异网)──
|
||||
bylay = {1: [], 2: []}
|
||||
for (l, x0, y0, x1, y1, w, net) in segs:
|
||||
if l in bylay:
|
||||
bylay[l].append((x0, y0, x1, y1, w, net))
|
||||
for l, lst in bylay.items():
|
||||
for i in range(len(lst)):
|
||||
a = lst[i]
|
||||
for j in range(i + 1, len(lst)):
|
||||
b = lst[j]
|
||||
if a[5] == b[5]:
|
||||
continue
|
||||
if max(a[0], a[2]) + 3 < min(b[0], b[2]) or max(b[0], b[2]) + 3 < min(a[0], a[2]):
|
||||
continue
|
||||
if max(a[1], a[3]) + 3 < min(b[1], b[3]) or max(b[1], b[3]) + 3 < min(a[1], a[3]):
|
||||
continue
|
||||
d = seg_seg(a[:4], b[:4]) - (a[4] + b[4]) / 2.0
|
||||
if d < MIN_CLR:
|
||||
hit(u'线-线', u'%s / %s 净距 %.3f @ L%s (%.1f,%.1f)'
|
||||
% (a[5], b[5], d, l, a[0], a[1]))
|
||||
|
||||
# ── 线 ↔ 焊盘(同层异网)──
|
||||
for (l, x0, y0, x1, y1, w, net) in segs:
|
||||
for p in pads:
|
||||
if p[6] != l or p[7] == net:
|
||||
continue
|
||||
d = seg_rect((x0, y0, x1, y1), R.pad_rect(p)) - w / 2.0
|
||||
if d < MIN_CLR:
|
||||
hit(u'线-焊盘', u'%s 线 vs %s.%s(%s) 净距 %.3f @ L%s'
|
||||
% (net, p[0], p[1], p[7], d, l))
|
||||
|
||||
# ── 线 ↔ 过孔(异网)──
|
||||
for (l, x0, y0, x1, y1, w, net) in segs:
|
||||
for (vx, vy, vnet) in allv:
|
||||
if vnet == net:
|
||||
continue
|
||||
d = seg_seg((x0, y0, x1, y1), (vx, vy, vx, vy)) - w / 2.0 - R.VIA_D / 2.0
|
||||
if d < MIN_CLR:
|
||||
hit(u'线-过孔', u'%s 线 vs %s 过孔(%.1f,%.1f) 净距 %.3f' % (net, vnet, vx, vy, d))
|
||||
|
||||
# ── 过孔 ↔ 焊盘 / 过孔 ↔ 过孔(异网)──
|
||||
for k, (vx, vy, vnet) in enumerate(allv):
|
||||
for p in pads:
|
||||
if p[7] == vnet:
|
||||
continue
|
||||
d = pt_rect(vx, vy, R.pad_rect(p)) - R.VIA_D / 2.0
|
||||
if d < MIN_CLR:
|
||||
hit(u'过孔-焊盘', u'%s 过孔(%.1f,%.1f) vs %s.%s(%s) 净距 %.3f'
|
||||
% (vnet, vx, vy, p[0], p[1], p[7], d))
|
||||
for (wx_, wy_, wnet) in allv[k + 1:]:
|
||||
if wnet == vnet:
|
||||
continue
|
||||
d = math.hypot(vx - wx_, vy - wy_) - R.VIA_D
|
||||
if d < MIN_CLR:
|
||||
hit(u'过孔-过孔', u'%s vs %s (%.1f,%.1f) 净距 %.3f' % (vnet, wnet, vx, vy, d))
|
||||
|
||||
# ── 定位孔(NPTH)↔ 铜 ──
|
||||
for hname, (hx, hy) in holes.items():
|
||||
hr = B.HOLE_D / 2.0
|
||||
for (l, x0, y0, x1, y1, w, net) in segs:
|
||||
d = seg_seg((x0, y0, x1, y1), (hx, hy, hx, hy)) - w / 2.0 - hr
|
||||
if d < MIN_CLR:
|
||||
hit(u'钻孔-线', u'%s vs %s 线 净距 %.3f' % (hname, net, d))
|
||||
for (vx, vy, vnet) in allv:
|
||||
d = math.hypot(vx - hx, vy - hy) - hr - R.VIA_D / 2.0
|
||||
if d < MIN_CLR:
|
||||
hit(u'钻孔-过孔', u'%s vs %s 过孔(%.1f,%.1f) 净距 %.3f' % (hname, vnet, vx, vy, d))
|
||||
for p in pads:
|
||||
d = pt_rect(hx, hy, R.pad_rect(p)) - hr
|
||||
if d < MIN_CLR:
|
||||
hit(u'钻孔-焊盘', u'%s vs %s.%s 净距 %.3f' % (hname, p[0], p[1], d))
|
||||
|
||||
n = sum(len(v) for v in bad.values())
|
||||
print(u'自算净距(判据 %.2f mm):%d 条' % (MIN_CLR, n))
|
||||
for k in sorted(bad, key=lambda k: -len(bad[k])):
|
||||
print(u' %-8s %d' % (k, len(bad[k])))
|
||||
lim = 8
|
||||
for a in sys.argv:
|
||||
if a.startswith('--list'):
|
||||
lim = int(sys.argv[sys.argv.index(a) + 1])
|
||||
for k in sorted(bad, key=lambda k: -len(bad[k])):
|
||||
print(u'── %s ──' % k)
|
||||
for m in bad[k][:lim]:
|
||||
print(u' ' + m)
|
||||
return n
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(1 if main() else 0)
|
||||
在新工单中引用
屏蔽一个用户