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