# -*- coding: utf-8 -*- u"""自建「小米8 电池排线直焊」封装 + 器件(J81~J85,2026-09-06 用户拍板,设计文档 §13.5 末尾)。 排线在保护板焊点处剪断,4 个焊盘直接焊到载板底层: · 用户实测:焊盘 1 × 2 mm,间距 2.0,4 个一排(跨 6 mm 中心距,排线 8 mm 宽); 顺序 P+ / P− / TEMP(10k NTC 对 P−) / ID(≈68k 对 P−)。 · 载板焊盘 1.1 × 4.0,**上下各多伸 1 mm**(排线尾巴剪到留 1 mm 后烙铁从露出的一截把锡吸进去), 上下对称 ⇒ 摆位时 rot 0 / 180 就能翻手性,不用改封装。 · 编号 1 = P+,2 = P−,3 = TEMP,4 = ID,从 −X 到 +X。手性(P+ 在 −u 还是 +u)由 pcb2_place.BAND['j8'] 的 rot 定。 · 符号借用立创 PZ254V-11-04P(1x4 排针)的 4 脚符号(复制进个人库)。 用法: /c/Python314/python .claude/fp_batfpc.py # 只打印几何 /c/Python314/python .claude/fp_batfpc.py --push # 建封装(已存在则重画)+ 校验 /c/Python314/python .claude/fp_batfpc.py --device # 建器件并挂封装,读回核对 """ import json import os import sys HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, HERE) from eda import run LIB_ME = '543725e8f0454017a5691e7e570aeef6' # 个人库 LIB_SYS = '0819f05c4eef4c71ace90d822a990e87' # 立创系统库 FP_NAME = 'FPC-4P-P2.0_MI8-BAT' DEV_NAME = 'MI8-BAT-FPC-4P' SYM_SRC = '350b2c5dc74942068004d202929c383c' # PZ254V-11-04P 的符号(系统库) MIL = 39.3700787401575 PITCH, N = 2.0, 4 PAD_W, PAD_H = 1.1, 4.0 FLEX_W = 8.0 def mil(v): return round(v * MIL, 4) def pads(): x0 = -(N - 1) * PITCH / 2.0 return [(k + 1, x0 + k * PITCH, 0.0, PAD_W, PAD_H) for k in range(N)] 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_fp(retries=6): import time for i in range(retries): u = _find_fp_once() if u: return u time.sleep(2) return None def _find_fp_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 push_fp(): fp = find_fp() if not fp: call("await eda.lib_Footprint.create('%s', '%s', undefined, '小米8 电池排线 4 焊盘直焊,1x2 焊盘 2.0 间距,载板焊盘 1.1x4.0');" "return 1;" % (LIB_ME, FP_NAME)) fp = find_fp() assert fp, u'封装建了但搜不到' print(u'封装已建: %s %s' % (FP_NAME, fp)) else: print(u'封装已存在: %s %s,重画' % (FP_NAME, fp)) body = [] for num, x, y, w, h in pads(): body.append("await eda.pcb_PrimitivePad.create(1,'%d',%s,%s,0,['RECT',%s,%s,0],undefined,null);" % (num, mil(x), mil(y), mil(w), mil(h))) # 丝印:排线 8 mm 宽的两条边(y 从 −3 到 +3),1 脚一侧加一个短横标记 hx = FLEX_W / 2.0 + 0.3 for (x1, y1, x2, y2) in [(-hx, -3.0, -hx, 3.0), (hx, -3.0, hx, 3.0)]: body.append("await eda.pcb_PrimitiveLine.create('',3,%s,%s,%s,%s,6);" % (mil(x1), mil(y1), mil(x2), mil(y2))) body.append("await eda.pcb_PrimitiveLine.create('',3,%s,%s,%s,%s,12);" % (mil(-3.6), mil(2.7), mil(-2.4), mil(2.7))) # 装配层:排线末端投影(尾巴剪到留 1 mm:焊盘 ±1 之外再 1 mm) for (x1, y1, x2, y2) in [(-4.0, -2.0, 4.0, -2.0), (-4.0, 2.0, 4.0, 2.0), (-4.0, -2.0, -4.0, 2.0), (4.0, -2.0, 4.0, 2.0)]: body.append("await eda.pcb_PrimitiveLine.create('',9,%s,%s,%s,%s,4);" % (mil(x1), mil(y1), mil(x2), mil(y2))) r = call("await eda.lib_Footprint.openInEditor('%s','%s');" "await new Promise(r=>setTimeout(r,2000));" "const old = await eda.pcb_PrimitivePad.getAllPrimitiveId(); if (old && old.length) await eda.pcb_PrimitivePad.delete(old);" "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)}));" % (fp, LIB_ME)) got = {int(p['n']): (p['x'] / MIL, p['y'] / MIL, p['layer']) for p in v} bad = [] if sorted(got) != [1, 2, 3, 4]: bad.append(u'编号 %s' % sorted(got)) for n, x, y, w, h in pads(): if n in got and (abs(got[n][0] - x) > 0.002 or abs(got[n][1] - y) > 0.002 or got[n][2] != 1): bad.append(u'pin%d %s ≠ (%.2f, %.2f)' % (n, got[n], x, y)) if bad: sys.exit(u'❌ ' + '; '.join(bad)) print(u'✅ 封装校验过:4 焊盘 1.1×4.0,x = %s' % [round(got[n][0], 2) for n in (1, 2, 3, 4)]) return fp def find_dev(retries=6): import time for i in range(retries): u = _find_dev_once() if u: return u time.sleep(2) return None def _find_dev_once(): r = call("const r = await eda.lib_Device.search('%s', '%s', undefined, undefined, 20, 1);" "return (r||[]).map(d => [d.uuid, d.name, d.libraryUuid]);" % (DEV_NAME, LIB_ME)) for u, n, l in r: if n == DEV_NAME: return u return None def push_dev(): fp = find_fp() assert fp, u'先 --push 建封装' dev = find_dev() if not dev: # ⚠️ create() 返回 undefined 但器件已建(skill §9y),判据是 search call("try { await eda.lib_Device.create('%s', '%s', undefined, {symbol: {uuid: '%s', libraryUuid: '%s'}}, " "'小米8 电池排线(BM3E)在保护板焊点处剪断,4 焊盘直焊载板:1=P+ 2=P- 3=TEMP 4=ID'); } catch (e) {}" "return 1;" % (LIB_ME, DEV_NAME, SYM_SRC, LIB_SYS), timeout=60) dev = find_dev() assert dev, u'器件建了但搜不到' print(u'器件已建: %s %s' % (DEV_NAME, dev)) else: print(u'器件已存在: %s %s' % (DEV_NAME, dev)) prop = {'name': DEV_NAME, 'designator': 'J?', 'addIntoBom': 'yes', 'addIntoPcb': 'yes', 'manufacturer': 'Xiaomi', 'manufacturerId': 'BM3E flex 4-pad', 'otherProperty': {'Supplier Footprint': FP_NAME, 'Note': 'P+/P-/TEMP 10k/ID 68k'}} call("try { await eda.lib_Device.modify('%s', '%s', undefined, undefined, {footprint: {uuid: '%s', libraryUuid: '%s'}}, undefined, %s); } catch (e) {}" "return 1;" % (dev, LIB_ME, fp, LIB_ME, json.dumps(prop, ensure_ascii=False)), timeout=60) g = call("const d = await eda.lib_Device.get('%s', '%s'); return {assoc: d.association, prop: d.property};" % (dev, LIB_ME)) print(json.dumps(g, ensure_ascii=False, indent=1)[:1500]) a, p = g['assoc'], g['prop'] ok = (a.get('footprint') or {}).get('uuid') == fp and a.get('symbol') and p.get('addIntoPcb') == 'yes' print(u'%s 器件 %s:封装 %s / 符号 %s / addIntoPcb %s' % (u'✅' if ok else u'❌', dev, (a.get('footprint') or {}).get('uuid'), (a.get('symbol') or {}).get('uuid'), p.get('addIntoPcb'))) if not ok: sys.exit(1) return dev, (a.get('symbol') or {}).get('uuid') if __name__ == '__main__': if '--push' in sys.argv: push_fp() if '--device' in sys.argv: push_dev() if len(sys.argv) == 1: for n, x, y, w, h in pads(): print('pin %d x=%.2f y=%.2f %.1f×%.1f' % (n, x, y, w, h))