# -*- coding: utf-8 -*- u"""自建「小米8 侧键触点焊盘」封装(2026-09-06 用户改方案:**顶针取消,改成中框式焊盘**)。 装配方式:螺丝把主板压在载板上(1 mm 间隙),主板自己的侧键弹片直接压在载板焊盘上, 完全复刻中框的接触方式。载板这一侧只要一个裸铜圆盘。 /c/Python314/python .claude/fp_keypad.py # 只打印几何 /c/Python314/python .claude/fp_keypad.py --push # 建封装(已存在则重画)+ 读回校验 ## 为什么是 Ø1.5 四个触点的**实测**最小间距是 **1.78 mm**(中框金焊盘量的,见 pcb2_place.POGO_XY): 净距 0.20 -> 最大 Ø1.58 · 净距 0.25 -> Ø1.53 · 净距 0.30 -> Ø1.48 中框原盘 Ø1.35。取 **Ø1.5**:净距 0.28(好做),且比中框略大一点换位置余量 (按弹片尖 Ø0.5 算,容许 0.50 mm 位置误差)。 ⚠️ 旧的 `POGO-SMD1P_D1.9` 是按「最近一对隔 2.217 mm」缩出来的,而真实是 1.78 —— Ø1.9 在真实间距下是**负净距**,正是 `pcb2_place.py` 那 20 条「重叠(L1)」的来源。 ## 两条工艺要求(生成不了,得在下单时说) 1. **这是接触盘,不上锡膏** —— 钢网必须开口为 0,否则弹片压在锡珠上接触不良。 2. 表面处理走 **ENIG(沉金)**,弹片反复摩擦,HASL 会磨穿。 """ import json import os import sys sys.stdout.reconfigure(encoding='utf-8') HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, HERE) from eda import run import pcb2_place as P LIB_ME = '543725e8f0454017a5691e7e570aeef6' # 个人库 FP_NAME = 'PAD-D1.5_MI8-KEY' MIL = 39.3700787401575 PAD_D = P.POGO_PAD_D # 1.5,单一事实来源在 pcb2_place def mil(v): return round(v * MIL, 4) def call(js, timeout=120): r = run(js, timeout=timeout) if not r.get('success'): raise RuntimeError(json.dumps(r, ensure_ascii=False)[:600]) return r['result'] def _find_once(): r = call("const r = await eda.lib_Footprint.search('%s', '%s', undefined, 20, 1);" "return (r||[]).map(f => [f.uuid, f.name, f.libraryUuid]);" % (FP_NAME, LIB_ME)) for u, n, l in r: if n == FP_NAME: return u return None def find_fp(retries=6): import time for i in range(retries): u = _find_once() if u: return u time.sleep(2) return None def push(): fp = find_fp() if not fp: call("await eda.lib_Footprint.create('%s', '%s', undefined, " "'小米8 侧键触点盘 Ø%.1f,单焊盘顶层裸铜。弹片直压,不上锡膏,ENIG。');return 1;" % (LIB_ME, FP_NAME, PAD_D)) fp = find_fp() assert fp, u'封装建了但搜不到' print(u'封装已建: %s %s' % (FP_NAME, fp)) else: print(u'封装已存在: %s %s,重画' % (FP_NAME, fp)) body = ["await eda.pcb_PrimitivePad.create(1,'1',0,0,0,['ELLIPSE',%s,%s],undefined,null);" % (mil(PAD_D), mil(PAD_D))] # 装配层画一圈,方便摆位时看得见;丝印不画(盘周围没地方,且丝印压不到接触面) n = 24 import math r0 = PAD_D / 2.0 + 0.25 for k in range(n): a1, a2 = 2 * math.pi * k / n, 2 * math.pi * (k + 1) / n body.append("await eda.pcb_PrimitiveLine.create('',9,%s,%s,%s,%s,4);" % (mil(r0 * math.cos(a1)), mil(r0 * math.sin(a1)), mil(r0 * math.cos(a2)), mil(r0 * math.sin(a2)))) r = call("await eda.lib_Footprint.openInEditor('%s','%s');" "await new Promise(r=>setTimeout(r,2000));" "const op = await eda.pcb_PrimitivePad.getAllPrimitiveId(); if (op && op.length) await eda.pcb_PrimitivePad.delete(op);" "const ol = await eda.pcb_PrimitiveLine.getAllPrimitiveId(); if (ol && ol.length) await eda.pcb_PrimitiveLine.delete(ol);" "%s" "await eda.pcb_Document.save();" "return {pads:(await eda.pcb_PrimitivePad.getAllPrimitiveId()).length," " lines:(await eda.pcb_PrimitiveLine.getAllPrimitiveId()).length};" % (fp, LIB_ME, ''.join(body))) print(u'已画: %s' % r) v = call("await eda.lib_Footprint.openInEditor('%s','%s'); await new Promise(r=>setTimeout(r,1500));" "return (await eda.pcb_PrimitivePad.getAll()).map(p=>({n:p.padNumber,x:p.x,y:p.y," "layer:p.layer,pad:JSON.stringify(p.pad),hole:JSON.stringify(p.hole)}));" % (fp, LIB_ME)) if len(v) != 1: sys.exit(u'❌ 焊盘数 %d,应为 1' % len(v)) p0 = v[0] shape = json.loads(p0['pad']) d = round(float(shape[1]) / MIL, 3) bad = [] if p0['layer'] != 1: bad.append(u'层 %s 应为 1(顶层)' % p0['layer']) if abs(d - PAD_D) > 0.01: bad.append(u'直径 %.3f ≠ %.2f' % (d, PAD_D)) if abs(p0['x']) > 0.5 or abs(p0['y']) > 0.5: bad.append(u'不在原点 (%s,%s)' % (p0['x'], p0['y'])) if json.loads(p0['hole']) not in (None, [], ['ROUND', 0]): bad.append(u'不该有孔: %s' % p0['hole']) if bad: sys.exit(u'❌ ' + '; '.join(bad)) print(u'✅ 封装校验过:顶层单焊盘 Ø%.2f,在原点,无孔' % d) return fp if __name__ == '__main__': import itertools, math dmin = min(math.hypot(a[0] - b[0], a[1] - b[1]) for a, b in itertools.combinations(P.POGO_XY, 2)) print(u'四触点实测最小间距 %.2f mm ⇒ Ø%.1f 盘净距 %.2f mm' % (dmin, PAD_D, dmin - PAD_D)) if '--push' in sys.argv: push() else: print(u'(加 --push 才真的建)')