初始导入: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
这个提交包含在:
+173
@@ -0,0 +1,173 @@
|
||||
# -*- 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))
|
||||
在新工单中引用
屏蔽一个用户