文件
phonerail/.claude/r2/loop_check.py
T
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

164 行
6.7 KiB
Python
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# -*- coding: utf-8 -*-
u"""§7.2 四条关键回路的**机器判据**(在真实焊盘坐标上算,不看图)。
/c/Python314/python .claude/r2/loop_check.py
## 为什么要有这个
§7.2 的四条回路(Buck 输入环路 / SW 面积 / FB 远离 SW / Kelvin 取样)
以前只能「布完线截图人眼看」。但**摆位阶段就已经决定了这四条能不能成立** ——
布线器再聪明也救不回「Cin 离 U1 12 mm」。M4c 第一版就是这么过的关:
几何自检(重叠 / 越框 / 禁布区)全绿,四条回路破了三条,**没有任何一条检查报错**。
⇒ 这里把四条写成数:读 `_blade_pads.json`(`pcb_route.py --dump` 抓的真实焊盘),
按网络名找到相关焊盘,算距离 / 面积 / 线段是否交叉。**摆位一改就跑一次。**
⚠ 判据只覆盖**摆位**。布完线还要跑 `pcb_route.py --check`(Kelvin 那条的走线判据)
和 `router_check.py`(净距),三个不互相替代。
"""
import io
import json
import math
import os
import sys
sys.stdout.reconfigure(encoding='utf-8')
HERE = os.path.dirname(os.path.abspath(__file__))
PADS = os.path.join(HERE, '_blade_pads.json')
SW = '$1N137' # U1 的开关节点
FB = '$1N126' # 反馈节点(R1/R2 分压 + U1 p7)
VIN = '+12V_F' # 保险之后的 12V
CIN = ['C4', 'C1', 'C2', 'C3'] # 输入电容,由近到远
def pads():
d = json.load(io.open(PADS, encoding='utf-8'))
out = []
for r in d:
out.append({'ref': r[0], 'pin': str(r[1]), 'x': r[2], 'y': r[3],
'w': r[4], 'h': r[5], 'layer': r[6], 'net': r[7]})
return out
def sel(ps, ref=None, net=None):
return [p for p in ps
if (ref is None or p['ref'] == ref) and (net is None or p['net'] == net)]
def gap(a, b):
u"""两个矩形焊盘的**净距**(边到边,重叠算 0)。"""
dx = max(abs(a['x'] - b['x']) - (a['w'] + b['w']) / 2.0, 0.0)
dy = max(abs(a['y'] - b['y']) - (a['h'] + b['h']) / 2.0, 0.0)
return math.hypot(dx, dy)
def dist(a, b):
return math.hypot(a['x'] - b['x'], a['y'] - b['y'])
def cross(p1, p2, p3, p4):
u"""线段 p1p2 与 p3p4 是否相交。"""
def s(a, b, c):
return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0])
d1, d2 = s(p3, p4, p1), s(p3, p4, p2)
d3, d4 = s(p1, p2, p3), s(p1, p2, p4)
return ((d1 > 0) != (d2 > 0)) and ((d3 > 0) != (d4 > 0))
def main():
ps = pads()
bad, note = [], []
# ── 1. Buck 输入环路:Cin 的两个焊盘各自到 U1 最近的 VIN / GND 脚 ──
u1_vin = sel(ps, 'U1', VIN)
u1_gnd = [p for p in sel(ps, 'U1', 'GND') if p['w'] < 2] # 去掉散热焊盘
for ref in CIN:
hot = sel(ps, ref, VIN)
gnd = sel(ps, ref, 'GND')
if not hot or not gnd:
bad.append(u'%s 找不到 %s/GND 焊盘' % (ref, VIN))
continue
a = min(gap(hot[0], q) for q in u1_vin)
b = min(gap(gnd[0], q) for q in u1_gnd)
note.append(u' 输入环路 %-3s: VIN 边 %.2f + GND 边 %.2f = **%.2f mm**'
% (ref, a, b, a + b))
if ref == CIN[0] and a + b > 3.0:
bad.append(u'最近的输入电容 %s 环路 %.2f mm > 3.0(§7.2 第一条)' % (ref, a + b))
# ── 2. SW 面积:所有 SW 焊盘的外接矩形 ──
sw = sel(ps, net=SW)
if sw:
x0 = min(p['x'] - p['w'] / 2 for p in sw)
x1 = max(p['x'] + p['w'] / 2 for p in sw)
y0 = min(p['y'] - p['h'] / 2 for p in sw)
y1 = max(p['y'] + p['h'] / 2 for p in sw)
area = (x1 - x0) * (y1 - y0)
refs = sorted(set(p['ref'] for p in sw))
note.append(u' SW 节点: %s,外接矩形 %.1f×%.1f = **%.1f mm²**'
% ('/'.join(refs), x1 - x0, y1 - y0, area))
u1sw = sel(ps, 'U1', SW)
l1sw = sel(ps, 'L1', SW)
if u1sw and l1sw:
g = min(gap(a, b) for a in u1sw for b in l1sw)
note.append(u' U1 的 SW 脚到 L1 的 1 脚: 焊盘净距 **%.2f mm**' % g)
if g > 2.0:
bad.append(u'U1→L1 的 SW 焊盘净距 %.2f mm > 2.0(§7.2 第二条)' % g)
if area > 60:
bad.append(u'SW 铜面积 %.1f mm² 偏大(§7.2 第二条)' % area)
# ── 3. FB 远离 SW ──
fb = sel(ps, net=FB)
if fb and sw:
g = min(gap(a, b) for a in fb for b in sw)
who = min(((gap(a, b), a['ref']) for a in fb for b in sw))[1]
note.append(u' FB(%s) 最近的一脚到 SW: **%.2f mm**(%s)' % (FB, g, who))
if g < 2.0:
bad.append(u'FB 离 SW 只有 %.2f mm < 2.0(§7.2 第三条)' % g)
# ── 4. Kelvin:U3 的 9/10 脚 → Rs1 的两端,不许交叉 ──
rs = {p['pin']: p for p in sel(ps, 'Rs1')}
u3 = {p['pin']: p for p in sel(ps, 'U3')}
if '9' in u3 and '10' in u3 and '1' in rs and '2' in rs:
# 9 脚接 VO_P 那端(Rs1.1),10 脚接 $1N165 那端(Rs1.2)
assert u3['9']['net'] == rs['1']['net'], (u3['9']['net'], rs['1']['net'])
assert u3['10']['net'] == rs['2']['net'], (u3['10']['net'], rs['2']['net'])
a = (u3['9']['x'], u3['9']['y'])
b = (rs['1']['x'], rs['1']['y'])
c = (u3['10']['x'], u3['10']['y'])
d = (rs['2']['x'], rs['2']['y'])
la, lb = math.dist(a, b), math.dist(c, d)
note.append(u' Kelvin: U3.9→Rs1.1 %.2f mm / U3.10→Rs1.2 %.2f mm,%s'
% (la, lb, u'交叉' if cross(a, b, c, d) else u'不交叉'))
if cross(a, b, c, d):
bad.append(u'Kelvin 两条取样线交叉 —— 一交叉就得打过孔,§7.2 第四条明令不许')
if max(la, lb) > 8.0:
bad.append(u'Kelvin 取样线 %.2f mm 偏长' % max(la, lb))
if abs(la - lb) > 2.0:
bad.append(u'Kelvin 两条取样线长度差 %.2f mm(应对称)' % abs(la - lb))
# ── 5. 顺带:输出链的两处 4 A 接口 ──
for (r1, n1, r2, n2, lim) in (('L1', 'VO', 'C6', 'VO', 3.0),
('U2', '$1N165', 'Rs1', '$1N165', 4.0)):
a, b = sel(ps, r1, n1), sel(ps, r2, n2)
if a and b:
g = min(gap(p, q) for p in a for q in b)
note.append(u' 4 A 接口 %s(%s)→%s: 焊盘净距 **%.2f mm**' % (r1, n1, r2, g))
if g > lim:
bad.append(u'%s→%s 的 4 A 接口 %.2f mm > %.1f' % (r1, r2, g, lim))
print(u'§7.2 关键回路(摆位阶段判据)')
for n in note:
print(n)
if bad:
print(u'[X] %d 条不过:' % len(bad))
for b in bad:
print(u' ' + b)
return 1
print(u'[OK] 四条关键回路全部达标')
return 0
if __name__ == '__main__':
sys.exit(main())