文件
phonerail/.claude/fp_batfpc.py
T
zhou yang和Claude Opus 5 2bfe308b30 初始导入:PhoneRail 五路手机直供电源板
原理图/PCB 已在 EasyEDA 里做到 12 页跨页 0 漏接,固件刚完成骨架包 A1。
这次是把已有的一切一次性纳入版本控制,没有改动任何设计内容。

纳入:
- 设计与需求文档、PROGRESS.md、小米8 参考原理图 PDF 与追脚脚本
- .claude/ 下的 EasyEDA 桥接工具链与几何/网表缓存(eda-parts.json、
  pcb-parts.json、pcb-pads.json、p2-ch1.json 等,是重画各页的唯一事实来源)
- 117 张验证截图(本项目所有方向问题都是靠人眼看截图发现的,属于证据不是垃圾)
- firmware/ 的 A1 骨架与 site/ 文档站源码

.gitignore 排除:构建产物(firmware/build、host/build、sdkconfig)、
site/node_modules、__pycache__、桥接日志 _bridge.log、脚本自标的 *.stale。

.gitattributes 默认 `* -text`:现有 .py/.json 大多是 CRLF,原样存,
不让 git 重写行尾。唯一例外是 *.sh 强制 LF —— 带 \r 的 shell 脚本 bash 跑不了。

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QYuExfgyYa6DzRRcUso8M8
2026-09-06 18:18:14 +08:00

174 行
8.0 KiB
Python
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# -*- 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))