原理图/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
136 行
5.4 KiB
Python
136 行
5.4 KiB
Python
# -*- coding: utf-8 -*-
|
||
u"""从器件的 **3D Model Transform** 直接读出**本体框** —— 不用再截图量。
|
||
|
||
/c/Python314/python .claude/fp_body.py --scan KF2510 # 按关键词列候选
|
||
/c/Python314/python .claude/fp_body.py --scan XT30 --3p # 只看 3 脚 2.54 的
|
||
/c/Python314/python .claude/fp_body.py --check # 和 pcb1_place.FP_BODY 对账
|
||
|
||
## 为什么能这么读(2026-09-06 验证)
|
||
|
||
§26.6 写过「板边连接器的本体框,除了实测没有别的来源」——
|
||
那一条是**错的**,只是当时没找到路子。`lib_Device.search()` 回来的
|
||
`otherProperty['3D Model Transform']` 是一串逗号分隔的数:
|
||
|
||
v[0], v[1] = 3D 本体的 X / Y 尺寸(mil)
|
||
v[6], v[7] = 本体中心相对**封装原点**的偏移(mil)
|
||
|
||
⇒ 本体框 = (ox − sx/2, oy − sy/2, ox + sx/2, oy + sy/2)。
|
||
|
||
**六个已经用放大截图实测过的封装全部对上**(误差 ≤ 0.31 mm,就是截图标定的精度):
|
||
|
||
| 封装 | 3D 算出的 y 向框 | 截图实测 |
|
||
|---|---|---|
|
||
| `CONN-TH_KF2510-3A-1`(弯针,§26.6 栽过一次)| −0.31 .. +11.70 | −0.20 .. +11.70 |
|
||
| `CONN-TH_P5.08_KF128-5.08-2P` | −5.31 .. +5.38 | −5.20 .. +5.20 |
|
||
| `USB-C_SMD-TYPE-C-31-M-12_1` | −5.05 .. +2.85 | −5.20 .. +3.12 |
|
||
| `WIRELM-SMD_ESP32-S3-WROOM-1` | −9.10 .. +16.40 | −9.35 .. +16.75 |
|
||
| `IND-SMD_L7.1-W6.5` | −3.25 .. +3.25 | −3.35 .. +3.35 |
|
||
| `CAP-TH_BD8.0-P3.50-D0.6-FD` | −4.00 .. +4.00 | −4.10 .. +4.10 |
|
||
|
||
⚠️ 它给的是 **3D 模型**的框,不是丝印框,也不含公差。当成「本体大致占多少」够用
|
||
(这正是摆位要的),要卡到 0.1 mm 的净距还是得看焊盘。**没有 3D 模型的器件读不出来**,
|
||
那种还是只能截图。
|
||
|
||
## 顺带:立式 / 弯针一眼就分得出
|
||
|
||
同一个 3P 2.54 端子座:
|
||
|
||
* **立式(直针)**:本体 ≈ 7.6 × 6,偏移 ≈ 0 —— 本体骑在焊盘上
|
||
* **弯针(90°)**:本体 ≈ 7.6 × 12,偏移 ≈ ±5.7 —— 本体整个甩到一边
|
||
|
||
⚠️ **名字完全不可靠**:`KF2510-3A`(C2940511) 名字像直针,3D 说它是弯针(+5.69);
|
||
`KF2510-3AW`(C457341) 也是弯针,只是甩向 −Y。要立式的得看数,不能看后缀。
|
||
"""
|
||
import json
|
||
import os
|
||
import sys
|
||
|
||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||
sys.path.insert(0, HERE)
|
||
from eda import run # noqa: E402
|
||
|
||
MIL = 39.3700787401575
|
||
|
||
|
||
def parse(tr):
|
||
u"""'3D Model Transform' -> (本体 w, h, 框 x0,y0,x1,y1),读不出来返回 None。"""
|
||
if not tr:
|
||
return None
|
||
try:
|
||
v = [float(x) for x in str(tr).split(',')]
|
||
except ValueError:
|
||
return None
|
||
if len(v) < 8:
|
||
return None
|
||
sx, sy = v[0] / MIL, v[1] / MIL
|
||
ox, oy = v[6] / MIL, v[7] / MIL
|
||
if sx <= 0 or sy <= 0:
|
||
return None
|
||
return (sx, sy, ox - sx / 2, oy - sy / 2, ox + sx / 2, oy + sy / 2)
|
||
|
||
|
||
def search(kw, n=40):
|
||
js = ("const r = await eda.lib_Device.search(%s, undefined, undefined, undefined, %d, 1);"
|
||
"return (r||[]).map(d => [d.name||'', d.footprintName||'', d.supplierId||'',"
|
||
" (d.otherProperty&&d.otherProperty['3D Model Transform'])||'', d.uuid, d.libraryUuid]);"
|
||
% (json.dumps(kw), n))
|
||
r = run(js, timeout=180)
|
||
if not r.get('success'):
|
||
sys.exit(u'搜索失败: ' + json.dumps(r, ensure_ascii=False)[:400])
|
||
return r['result']
|
||
|
||
|
||
def show(rows, only=None):
|
||
print(u'%-30s %-32s %-11s %-15s %s'
|
||
% (u'器件', u'封装', u'立创', u'本体 w x h', u'框 y0..y1 判断'))
|
||
for name, fp, lcsc, tr, _u, _l in rows:
|
||
if only and only.lower() not in (name + fp).lower():
|
||
continue
|
||
b = parse(tr)
|
||
if b is None:
|
||
print(u'%-30s %-32s %-11s —— 没有 3D 模型,读不出来' % (name[:30], fp[:32], lcsc))
|
||
continue
|
||
w, h, x0, y0, x1, y1 = b
|
||
# 立式 = 本体基本骑在焊盘上;弯针 = 整个甩到一边
|
||
kind = u'立式' if abs((y0 + y1) / 2) < h * 0.25 else u'弯针/偏置'
|
||
print(u'%-30s %-32s %-11s %6.2f x %6.2f %+6.2f..%+6.2f %s'
|
||
% (name[:30], fp[:32], lcsc, w, h, y0, y1, kind))
|
||
|
||
|
||
def check():
|
||
u"""拿 `pcb1_place.FP_BODY` 的手工实测值和 3D 模型对账。"""
|
||
import pcb1_place as P
|
||
hit = {}
|
||
for fp in P.FP_BODY:
|
||
kw = fp.split('_')[-1] or fp
|
||
for row in search(kw, 40):
|
||
if row[1] == fp and row[3]:
|
||
hit[fp] = row
|
||
break
|
||
print(u'%-34s %-22s %-22s %s' % (u'封装', u'3D 框 y', u'FP_BODY 框 y', u''))
|
||
bad = 0
|
||
for fp, want in sorted(P.FP_BODY.items()):
|
||
row = hit.get(fp)
|
||
if not row:
|
||
print(u'%-34s —— 没搜到带 3D 的器件,跳过' % fp[:34])
|
||
continue
|
||
b = parse(row[3])
|
||
ok = abs(b[3] - want[1]) < 0.6 and abs(b[5] - want[3]) < 0.6
|
||
bad += 0 if ok else 1
|
||
print(u'%-34s %+7.2f..%+7.2f %+7.2f..%+7.2f %s'
|
||
% (fp[:34], b[3], b[5], want[1], want[3], u'✔' if ok else u'✘ 对不上'))
|
||
print(u'\n对不上的 %d 个' % bad)
|
||
return bad
|
||
|
||
|
||
if __name__ == '__main__':
|
||
argv = sys.argv[1:]
|
||
if '--check' in argv:
|
||
sys.exit(1 if check() else 0)
|
||
if '--scan' in argv:
|
||
kw = argv[argv.index('--scan') + 1]
|
||
only = argv[argv.index('--only') + 1] if '--only' in argv else None
|
||
show(search(kw, 40), only)
|
||
else:
|
||
sys.exit(__doc__)
|