# -*- 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())