文件
phonerail/.claude/edge_access.py
T

130 行
5.7 KiB
Python
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# -*- coding: utf-8 -*-
u"""板边可达性检查:每个接插件离**真板框**有多远?
/c/Python314/python .claude/edge_access.py # 下板 PCB1
/c/Python314/python .claude/edge_access.py --board2 # 载板 PCB2
## 为什么需要这条判据
摆位脚本自带的六项检查(净距 / 重叠 / 越框 / 定位孔 / 高度 / 锚点 / 去耦贴身)
**全是「元件之间」和「元件对板框」的检查,没有一条问「插头插得进去吗」。**
2026-09-08 实测后果:下板的板框常量从 160×125 改成真值 220×135 / 左下角 (−30,−5) 之后,
四个需要板边通道的接插件**全部悬在板子内部 30 mm**,而上方 13 mm 处压着载板:
| 位号 | 用途 | 离旧假框 | 离真板边 |
|---|---|---:|---:|
| `J1` | XT30 12V 进线,90° 板装 | 0.5 | **30.5** ⛔ |
| `J22` | 调试 USB-C | 0.4 | **30.4** ⛔ |
| `J26`/`J27` | 风扇 4P 直针座 | −29.0 | **1.2 / 9.0** ⛔ |
**六项检查全过、DRC 也全过 —— 因为它们确实没越框、也没压着谁。**
这类错误只有「离真板边多远」这一条判据抓得到。
⇒ 判据:凡是**需要从板外插入或引出线缆**的器件(连接器、USB、电源座、风扇座、
测试口),其本体包围盒到最近板框边的距离必须 ≤ `NEED_EDGE_MAX`。
板对板排针(与另一块板垂直对插)不在此列 —— 用 `VERTICAL_MATE` 排除。
⚠️ 顺带一条容易搞混的:**直针座(垂直插拔)也需要板边**。本项目的 4P 风扇座是直针,
插拔方向是 +Z,但两板间隙只有 13 mm 而座壳就 11.5 mm,插上线之后**线缆只能贴着板面
横向爬出去** —— 所以板边的意义是「线缆走得掉」,不是「插得进」。
"""
import io
import json
import os
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
NEED_EDGE_MAX = 3.0 # 需要板边通道的器件,本体离板框最多这么远(mm)
#: **免检名单**:不是“从板外插线”的接插件。分两类:
#: ① 与另一块 PCB **垂直对插**(板对板排针 / 排母)
#: ② 与**放在载板上面的手机**对接(侧键中框式焊盘、尾插座、电池座)
#: 这两类都是 +Z 方向对接,板边对它们没有意义。
#: —— 第一版只写了①,载板上直接误报 30 条(J41~J65 / J81~J85 全是手机侧接口)。
VERTICAL_MATE = (
{'J24', 'J25', 'J31', 'J32'} # 两板之间的排针/排母
| {'J7%d' % n for n in range(1, 6)} # BM24 板对板
| {'J%d' % n for n in range(41, 66)} # 侧键焊盘 J41~J60 + 尾插 J61~J65
| {'J8%d' % n for n in range(1, 6)} # 手机位相关
)
def load(board2):
u"""从摆位脚本里取 POS / abs_box / 板框。两块板的摆位脚本结构一样。"""
fn = 'pcb2_place.py' if board2 else 'pcb1_place.py'
p = os.path.join(HERE, fn)
src = io.open(p, encoding='utf-8').read().replace('\nmain()\n', '\n')
g = {'__name__': 'm', '__file__': p}
exec(compile(src, fn, 'exec'), g)
if board2:
bx0, by0, bx1, by1 = 0.0, 0.0, g['BW'], g['BH']
else:
bx0, by0, bx1, by1 = g['BX0'], g['BY0'], g['BX1'], g['BY1']
return g, (bx0, by0, bx1, by1)
def _pcb2_adapter(g):
u"""载板 `pcb2_place.py` 的数据结构和下板不一样:摆位表由 `plan()` 现算、
包围盒是 `box(d, rot, bb, layer)`(相对元件原点)而不是 `abs_box(d)`。
这里把它包装成和下板一致的 `(POS, abs_box, fp)` 三件套。"""
pos = g['plan']()
bb = g['load_bb']() # {位号: 相对元件原点的焊盘包围盒}
def abs_box(d):
x, y, r, lay = pos[d]
b = g['box'](d, r, bb, lay)
return (x + b[0], y + b[1], x + b[2], y + b[3])
import json as _json, os as _os
_fp = {}
_f = _os.path.join(HERE, 'pcb2-parts.json')
if _os.path.exists(_f):
for c in _json.load(io.open(_f, encoding='utf-8')):
_fp[c['d']] = c.get('fp', '')
def fp(d):
return _fp.get(d, '')
return {k: v for k, v in pos.items()}, abs_box, fp
def main():
board2 = '--board2' in sys.argv
g, (BX0, BY0, BX1, BY1) = load(board2)
if board2:
POS, abs_box, fp = _pcb2_adapter(g)
else:
POS, abs_box, fp = g['POS'], g['abs_box'], g['fp']
print(u'%s 板框 x %.1f~%.1f y %.1f~%.1f' % (u'载板' if board2 else u'下板', BX0, BX1, BY0, BY1))
rows, bad = [], []
for d in sorted(POS):
f = (fp(d) or '')
if not (d[0] == 'J' or 'CONN' in f.upper() or 'USB' in f.upper() or 'HDR' in f.upper()):
continue
b = abs_box(d)
dist = min(b[0] - BX0, BX1 - b[2], b[1] - BY0, BY1 - b[3])
rows.append((dist, d, f, b))
if dist > NEED_EDGE_MAX and d not in VERTICAL_MATE:
bad.append((dist, d, f, b))
rows.sort()
print(u'\n%-7s %-36s %9s %s' % (u'位号', u'封装', u'离板边', u'本体包围盒'))
for dist, d, f, b in rows:
mark = u''
if d in VERTICAL_MATE:
mark = u' (垂直对插,免检)'
elif dist > NEED_EDGE_MAX:
mark = u' ⛔ 插头/线缆够不着'
print(u'%-7s %-36s %9.1f x %7.1f~%-7.1f y %7.1f~%-7.1f%s'
% (d, f[:36], dist, b[0], b[2], b[1], b[3], mark))
print()
if bad:
print(u'⛔ %d 个接插件离真板边超过 %.1f mm:%s'
% (len(bad), NEED_EDGE_MAX, u', '.join(x[1] for x in bad)))
sys.exit(1)
print(u'✅ 所有需要板边通道的接插件都在板边 %.1f mm 以内' % NEED_EDGE_MAX)
main()