# -*- coding: utf-8 -*- u"""自建 BM24-50DS/2-0.35V(51) 封装(方案 D 的载板座子 J71~J75)。 为什么要自建:立创的 `CONN-SMD_BBM24-50DS-2-0.35V51` 用的是 **DIP 绕圈编号** (1..25 一排、26..50 反着排另一排),而小米8 J1401 / Hirose 用的是**奇偶行编号** (1,3,5..49 一排 / 2,4,6..50 另一排)。照搬会把 50 个脚全接错。见设计文档 §13.4 末尾。 ───────────────────────────────────────────────────────────────────── 几何来源:**Hirose BM24 系列官方样本**(2021.12③ / Aug.1.2026 版, D47680_en,"Receptacle → Recommended PCB layout")。 不是外推、不是抄立创 —— 数值是从样本 PDF 的矢量图里逐段反算的: · 标定:图上 P=0.35mm 对应 6.706 pt、B=3.15mm 对应 60.355 pt → 19.1603 pt/mm(两处自洽) · 图画的是 20 芯那一款(C=7.3 / B=3.15 都对得上表) 尺寸表(50 芯):A=12.25 B=8.4 C=12.55 反算出的真实含义(样本里没写全,是量出来的): · B = 同一排最外侧两个信号焊盘的**中心距** = 24 × 0.35 = 8.4 ✔ · C = 两个电源焊盘的**外缘到外缘** = 12.55 → 电源焊盘中心 x = ±(12.55−0.49)/2 = ±6.03 · 0.49±0.02 是**电源焊盘的宽(x)**,不是信号焊盘长度(尺寸线量的是 95.44→104.83 那一段) · 信号焊盘长(y) = (2.6 − 1.64)/2 = 0.48 —— 样本没直接标,由外缘 2.6 与内缘 1.64 得出 · 两排中心距 = (2.6+1.64)/2 = 2.12 → 每排 y = ±1.06(和 Header 图上标的 1.06 对得上) · 禁止布线区 = 两个电源焊盘内缘之间 × 两排焊盘内缘之间 = ±5.785 × ±0.82 · 0.14MAX = 中线上允许穿过禁布区的那一条细缝 —— **我们不用**(G1 是地、G2 是 VBUS, 这两个电源触点绝不能连起来) ───────────────────────────────────────────────────────────────────── ✅ 编号约定(曾经唯一没有实证的一位,**2026-09-05 已实测确认,见设计文档 §24.3**): ODD_ROW_TOP = True → 奇数脚 1,3,5…49 在 **+Y 那一排**,2,4,6…50 在 −Y 排 1 脚在 **−X 端**(x = −4.20),49/50 在 +X 端 pin 51 = 1/2 脚那一端的电源触点(x = −6.03) pin 52 = 49/50 脚那一端的电源触点(x = +6.03) 符号侧(`c5_j1401.py` 的 PWR)已经定死 52 = GND、51 = VBUS。 用法: /c/Python314/python .claude/fp_bm24.py # 只打印将要下发的几何 /c/Python314/python .claude/fp_bm24.py --push # 真的画进封装 /c/Python314/python .claude/fp_bm24.py --verify # 读回校验 """ import json import os import sys HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, HERE) from eda import run LIB_PERSONAL = '543725e8f0454017a5691e7e570aeef6' FP_UUID = '940f093c1578426aa81e91ebf0bfbd76' # BM24-50DS_HIROSE-PIN FP_NAME = 'BM24-50DS_HIROSE-PIN' MIL = 39.3700787401575 # 1 mm = 39.37 mil;PCB 域的单位是 1 mil # ---- Hirose 推荐 PCB 图(50 芯),单位 mm ---- PITCH = 0.35 NCOL = 25 X0 = -(NCOL - 1) * PITCH / 2.0 # = -4.20 ROW_Y = 1.06 SIG_W, SIG_H = 0.18, 0.48 PWR_X = 6.03 PWR_W, PWR_H = 0.49, 0.76 BODY_L, BODY_W = 12.25, 2.30 # A × 2.3,装配层用 PROHIB_X, PROHIB_Y = 5.785, 0.82 # ✅ 2026-09-05 实测定案(设计文档 §24.3):手机 J1401 上**靠板边那一排是奇数排**, # 而排线是从板边直出、1:1 内侧↔内侧,载板这颗座子的 +Y 排(朝手机那一排)因此也是奇数排。 # ⇒ True 是对的。翻这一位就能整颗镜像重生成(1 脚丝印点会跟着走,见 DOT)。 ODD_ROW_TOP = True SILK_X, SILK_Y = 6.60, 1.45 # 丝印框(在所有焊盘之外:电源焊盘外缘 6.275、信号焊盘上缘 1.30) SILK_W = 6.0 # mil # 1 脚标记:**必须跟着 ODD_ROW_TOP 走** —— 1 脚在 −X 端的「奇数排」那一侧。 # 写死成 +1.75 的话,翻镜像位之后丝印上的 1 脚点会指着 2 脚, # 而几何验证与 DRC 都看不出来(丝印不参与任何检查)。 DOT = (-7.05, 1.75 if ODD_ROW_TOP else -1.75) def mil(v): return round(v * MIL, 4) def pads(): u"""返回 [(padNumber, x_mm, y_mm, w_mm, h_mm), ...]""" out = [] top_y = ROW_Y if ODD_ROW_TOP else -ROW_Y bot_y = -top_y for k in range(1, NCOL + 1): x = X0 + (k - 1) * PITCH out.append((2 * k - 1, x, top_y, SIG_W, SIG_H)) out.append((2 * k, x, bot_y, SIG_W, SIG_H)) out.append((51, -PWR_X, 0.0, PWR_W, PWR_H)) out.append((52, +PWR_X, 0.0, PWR_W, PWR_H)) return out def build_js(): P = pads() body = [] for num, x, y, w, h in P: 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))) # 丝印框(4 条,全在焊盘之外) for (x1, y1, x2, y2) in [(-SILK_X, SILK_Y, SILK_X, SILK_Y), (-SILK_X, -SILK_Y, SILK_X, -SILK_Y), (-SILK_X, -SILK_Y, -SILK_X, SILK_Y), (SILK_X, -SILK_Y, SILK_X, SILK_Y)]: body.append("await eda.pcb_PrimitiveLine.create('',3,%s,%s,%s,%s,%s);" % (mil(x1), mil(y1), mil(x2), mil(y2), SILK_W)) # 1 脚标记:一个小方块(用短线画的十字,最省事且不依赖 Circle API) dx, dy = DOT body.append("await eda.pcb_PrimitiveLine.create('',3,%s,%s,%s,%s,%s);" % (mil(dx - 0.15), mil(dy), mil(dx + 0.15), mil(dy), 12.0)) # 装配层:本体外形 A × 2.3 hx, hy = BODY_L / 2.0, BODY_W / 2.0 for (x1, y1, x2, y2) in [(-hx, hy, hx, hy), (-hx, -hy, hx, -hy), (-hx, -hy, -hx, hy), (hx, -hy, hx, hy)]: body.append("await eda.pcb_PrimitiveLine.create('',9,%s,%s,%s,%s,%s);" % (mil(x1), mil(y1), mil(x2), mil(y2), 4.0)) # 文档层:Hirose 的「禁止布线区」,提醒布板时别把铜灌进去 for (x1, y1, x2, y2) in [(-PROHIB_X, PROHIB_Y, PROHIB_X, PROHIB_Y), (-PROHIB_X, -PROHIB_Y, PROHIB_X, -PROHIB_Y), (-PROHIB_X, -PROHIB_Y, -PROHIB_X, PROHIB_Y), (PROHIB_X, -PROHIB_Y, PROHIB_X, PROHIB_Y)]: body.append("await eda.pcb_PrimitiveLine.create('',13,%s,%s,%s,%s,%s);" % (mil(x1), mil(y1), mil(x2), mil(y2), 4.0)) return ("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" "return {pads:(await eda.pcb_PrimitivePad.getAllPrimitiveId()).length," " lines:(await eda.pcb_PrimitiveLine.getAllPrimitiveId()).length};" % (FP_UUID, LIB_PERSONAL, ''.join(body))) VERIFY_JS = """ await eda.lib_Footprint.openInEditor('%s','%s'); await new Promise(r=>setTimeout(r,2000)); const ps = await eda.pcb_PrimitivePad.getAll(); const out = ps.map(p=>({n:p.padNumber, x:p.x, y:p.y, layer:p.layer, shape:JSON.stringify(p.pad)})); const ls = (await eda.pcb_PrimitiveLine.getAll()).map(l=>({layer:l.layer, s:[l.startX,l.startY], e:[l.endX,l.endY], w:l.lineWidth})); return {nPad:out.length, pads:out, nLine:ls.length, lines:ls}; """ % (FP_UUID, LIB_PERSONAL) def check(res): u"""读回校验:编号齐全、坐标与理论值一致、同排间距严格 0.35。""" got = {} for p in res['pads']: got[int(p['n'])] = (p['x'] / MIL, p['y'] / MIL, p['layer']) want = {n: (x, y) for n, x, y, _, _ in pads()} bad = [] if sorted(got) != list(range(1, 53)): bad.append(u'焊盘编号不是 1..52:缺 %s / 多 %s' % (sorted(set(range(1, 53)) - set(got)), sorted(set(got) - set(range(1, 53))))) for n, (wx, wy) in want.items(): if n not in got: continue gx, gy, lay = got[n] if abs(gx - wx) > 0.002 or abs(gy - wy) > 0.002: bad.append(u'pin%d 位置 (%.4f, %.4f) ≠ 理论 (%.4f, %.4f)' % (n, gx, gy, wx, wy)) if lay != 1: bad.append(u'pin%d 不在顶层(layer=%s)' % (n, lay)) # 同排相邻间距 for row in (1, 2): xs = sorted(got[n][0] for n in range(row, 51, 2)) for a, b in zip(xs, xs[1:]): if abs((b - a) - PITCH) > 0.002: bad.append(u'第 %d 排相邻间距 %.4f ≠ 0.35' % (row, b - a)) # 奇偶分排 ys_odd = set(round(got[n][1], 3) for n in range(1, 51, 2)) ys_even = set(round(got[n][1], 3) for n in range(2, 51, 2)) if len(ys_odd) != 1 or len(ys_even) != 1 or ys_odd == ys_even: bad.append(u'奇偶脚没有分成两排:odd y=%s even y=%s' % (ys_odd, ys_even)) # 电源触点 if abs(got[51][0] + PWR_X) > 0.002 or abs(got[52][0] - PWR_X) > 0.002: bad.append(u'51/52 电源触点位置不对:%.3f / %.3f' % (got[51][0], got[52][0])) # 焊盘与丝印不能打架 for l in res['lines']: if l['layer'] != 3: continue for ax in (l['s'][0], l['e'][0]): pass return bad if __name__ == '__main__': if '--push' in sys.argv: r = run(build_js()) assert r.get('success'), r print(u'已下发:', r['result']) if '--push' in sys.argv or '--verify' in sys.argv: r = run(VERIFY_JS) assert r.get('success'), r bad = check(r['result']) print(u'读回 %d 焊盘 / %d 线' % (r['result']['nPad'], r['result']['nLine'])) if bad: print(u'❌ 校验不过:') for b in bad: print(' -', b) sys.exit(1) print(u'✅ 六项几何校验全过(编号 1..52 / 坐标 / 层 / 间距 0.35 / 奇偶分排 / 电源触点)') if '--push' not in sys.argv and '--verify' not in sys.argv: for n, x, y, w, h in pads(): print('pin %2d x=%8.4f y=%7.4f %.2f×%.2f mm (%.3f, %.3f mil)' % (n, x, y, w, h, mil(x), mil(y)))