# -*- coding: utf-8 -*- u"""P5-HARNESS 整页模型 + 下发。 这是一页**文档页**:只有【B】手机侧电容组是真电路(C801~C806,离板, addIntoBom=false / addIntoPcb=false),其余全是非电气图元 (sch_PrimitiveRectangle / sch_PrimitivePolygon / sch_PrimitiveText)。 用法: /c/Python314/python .claude/p5_harness.py [--push] 不带 --push 只跑本地几何自检。元件已经放好,本脚本不动元件。 """ import json, os, sys HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, HERE) PAGE = '58bacf3ddd60ed4e' # 元件引脚(getAllPins 读回,勿手改) PINS = { 'C801': {'1': (100, 350), '2': (100, 310)}, # 2200uF/10V 铝电解,pin1=+ 'C802': {'1': (172, 345), '2': (172, 315)}, 'C803': {'1': (244, 345), '2': (244, 315)}, 'C804': {'1': (316, 345), '2': (316, 315)}, 'C805': {'1': (388, 345), '2': (388, 315)}, 'C806': {'1': (460, 350), '2': (460, 310)}, } WIRES, FLAGS, PORTS, TEXTS, POLYS, RECTS = [], [], [], [], [], [] def w(a, b): WIRES.append((a, b)) def gnd(x, y, rot=0): FLAGS.append(('Ground', 'GND', x, y, rot)) def port(net, x, y, rot, d='IN'): PORTS.append((d, net, x, y, rot)) def T(x, y, s, size=10, bold=False, color=None): TEXTS.append((x, y, s, size, bold, color)) def PL(pts, color=None, lw=2, lt=0): POLYS.append((pts, color, lw, lt)) def R(x, y, wd, ht, color='#999999'): RECTS.append((x, y, wd, ht, color)) BLUE, RED, GRAY = '#0055AA', '#CC0000', '#666666' ORANGE, GREEN, DK = '#CC6600', '#009944', '#333333' # ============================ 标题 ============================ T(30, 806, u'【P5-HARNESS】手机侧线束与接点 —— 每台手机一套,五台完全相同', 13, True, '#000000') T(560, 806, u'本页器件全部离板:不进 BOM、不上 PCB(addIntoBom / addIntoPcb 均为 false)', 10, False, RED) # =============== 【A】三条线束(板端 → 手机端)================ BOARD_X, PHONE_X, CABLE_L, CABLE_R = 40, 700, 270, 700 def band(top, height, bhead, brows, phead, prows, cnotes, colors): u"""一条线束:左框(板端) + 右框(手机端) + 中间缆线 + 缆线标注。""" R(BOARD_X, top, 230, height) R(PHONE_X, top, 440, height) T(BOARD_X + 10, top - 13, bhead, 11, True, BLUE) T(PHONE_X + 12, top - 13, phead, 11, True, BLUE) for x, y, s, sz, c in cnotes: T(x, y, s, sz, False, c) y = top - 35 for i, (b, p) in enumerate(zip(brows, prows)): T(BOARD_X + 12, y, b, 10) T(PHONE_X + 12, y, p, 10) PL([CABLE_L, y, CABLE_R, y], colors[i], 3) y -= 20 # 电源线束 band(790, 68, u'【电源】J2 Micro-Fit 3.0 2P', [u'1 VO1_P', u'2 GND'], u'电池排线(原厂 BM3E,保留 NTC / ID)', [u'P+ 焊点 -> J600 pin 7/8 与 G3/G4 (VBATT)', u'P- 焊点 -> J600 pin 1/2 与 G1/G2 (GND)'], [(300, 777, u'18 AWG 双绞 · 不超过 20 cm · 红=正 黑=负', 10, None), (510, 777, u'手机端并接【B】电容组', 10, ORANGE)], [RED, DK]) # 侧键线束 band(712, 112, u'【侧键】J7 1.25 mm 4P', [u'1 K1_PWR', u'2 K1_VUP', u'3 K1_VDN', u'4 GND'], u'侧键触点(主板已串 1 kΩ,对地短接即可)', [u'J603 电源键 PHONE_ON_N', u'J601 音量 + VOL_UP_N', u'J602 音量 − PM_RESIN_N', u'J604 公共地 GND(三键共用)'], [(300, 699, u'AWG 26~28 细线 · 远离电源线', 10, None), (500, 699, u'触点需先用万用表定位(§13.3)', 10, None)], ['#0066CC', '#0066CC', '#0066CC', DK]) # USB 线束 band(592, 112, u'【USB】J17 1.25 mm 4P', [u'1 VBUS1', u'2 USB1_DM', u'3 USB1_DP', u'4 GND'], u'原厂尾插小板 USB-C(CC / ESD / 口温 NTC 都在其上)', [u'USB-C VBUS <- 板上 U24 通断,默认断开', u'USB-C D−', u'USB-C D+', u'USB-C GND'], [(300, 579, u'USB 线改端子 · D± 保持双绞', 10, None), (500, 579, u'上游只接电脑 / Hub,恒 5V', 10, None)], [ORANGE, GREEN, GREEN, DK]) # ========== 【B】手机侧电容组(本页唯一的真电路)========== RAIL_T, RAIL_B, RAIL_L, RAIL_R = 390, 265, 60, 490 T(30, 448, u'【B】手机侧电容组 —— 装在排线 P+ / P− 焊点处', 12, True, BLUE) T(200, 400, u'上母线 = 排线 P+ 焊点(VO1_P) · 下母线 = 排线 P− 焊点(GND)', 9, False, GRAY) T(150, 425, u'图中为 CH1;CH2~CH5 各一套相同、未画出,位号见【C】表', 9, False, GRAY) w((RAIL_L, RAIL_T), (RAIL_R, RAIL_T)) # 上母线 = VO1_P(手机端 P+) w((RAIL_L, RAIL_B), (RAIL_R, RAIL_B)) # 下母线 = GND (手机端 P−) w((RAIL_L, RAIL_T), (RAIL_L, 420)) port('VO1_P', RAIL_L, 420, 180, 'IN') w((RAIL_R, RAIL_B), (RAIL_R, 245)) # 地旗放右端:放左端时 GND 标签会压到【D】标题 gnd(RAIL_R, 245, 0) for _d in ('C801', 'C802', 'C803', 'C804', 'C805', 'C806'): _px, _p1y = PINS[_d]['1'] _p2y = PINS[_d]['2'][1] w((_px, _p1y), (_px, RAIL_T)) w((_px, _p2y), (_px, RAIL_B)) for _x, _s in ((72, u'主储能 / 阻尼'), (158, u'中频'), (230, u'中频'), (302, u'高频'), (374, u'高频'), (440, u'甚高频')): T(_x, 248, _s, 9, False, GRAY) T(30, 232, u'C801 2200µF/10V 铝电解 C49256788(普通铝电解,ESR 数十 mΩ = 阻尼源)', 9) T(30, 216, u'C802/803 100µF/6.3V 1206 C15008 · C804/805 22µF/25V 1206 C12891' u' · C806 100nF 0402 C1525', 9) # ================== 【C】五路对照表 ================== CX = [555, 620, 690, 760, 830, 905, 975] T(550, 448, u'【C】五路对照表', 12, True, BLUE) for _x, _s in zip(CX, [u'CH', u'电源座', u'按键座', u'USB 座', u'输出网络', u'INA226', u'电容组位号']): T(_x, 425, _s, 10, True) PL([550, 413, 1135, 413], '#888888', 1) ROWS = [ (u'CH1', 'J2', 'J7', 'J17', 'VO1_P', '0x40', 'C801~C806'), (u'CH2', 'J3', 'J8', 'J18', 'VO2_P', '0x41', 'C811~C816'), (u'CH3', 'J4', 'J9', 'J19', 'VO3_P', '0x44', 'C821~C826'), (u'CH4', 'J5', 'J10', 'J20', 'VO4_P', '0x45', 'C831~C836'), (u'CH5', 'J6', 'J11', 'J21', 'VO5_P', '0x42', 'C841~C846'), ] _yy = 393 for _row in ROWS: for _x, _cell in zip(CX, _row): T(_x, _yy, _cell, 10) _yy -= 21 PL([550, 285, 1135, 285], '#888888', 1) T(550, 268, u'⚠ J600 的 G3 / G4 固定脚实际是 VBATT,不是地;1/2 + G1/G2 才是 GND。', 10, False, RED) T(550, 250, u'BATT_ID (pin 3) / BATT_THERM (pin 4) 由原厂排线提供,缺任一条可能拒绝开机。', 10) T(550, 232, u'五路的地在手机端各自独立,不要并成一根 —— 会把射频突发窜到其他四台。', 10) T(550, 214, u'INA226 地址不等于通道号,固件必须查表(§8.2)。', 10) # ================== 【D】动手前必读 ================== D = [ (u'【D】动手前必读', 11, True, BLUE), (u'⚠ 电源线焊在排线的 P+ / P−(保护板输出侧),不是 B+ / B−。', 10, False, RED), (u' 焊错常温下完全正常,只有过压时才暴露(保护板抢先动作)。', 10, False, None), (u' 判别:BTB pin 7/8 对候选正极 ≈0Ω 者为 P+;pin 1/2 对候选负极者为 P−。', 10, False, None), (u' 升到 4.40V 时 BTB 座电压必须跟随且不断开 —— 唯一的判定性测试(§13.7)。', 10, False, None), (u'⚠ 大电容必须装在排线端。装在电源板上隔着 300 nH 线缆电感,基本无效。', 10, False, RED), (u'⚠ C801 必须用普通铝电解(ESR 40~100 mΩ)做阻尼。', 10, False, RED), (u' 换成同尺寸的聚合物版(如 C53446724,ESR 12 mΩ),Q 会从 0.19 回到 4.5,', 10, False, None), (u' 线缆 LC 在 6.2 kHz 上振铃。(§6.2)', 10, False, None), (u'⚠ 三条线束的地在手机端不要并成一根,各自回本路的板端地。(§15.4)', 10, False, RED), (u'C801~C806 不进 BOM、不上 PCB;线束与电容组每台一套,五台共 5 套。', 10, False, None), ] _yy = 193 for _s, _sz, _bold, _col in D: T(30, _yy, _s, _sz, _bold, _col) _yy -= 17 # ======================== 本地自检 ======================== def _on(p, a, b, tol=0.6): (px, py), (ax, ay), (bx, by) = p, a, b if abs(ax - bx) < tol: return abs(px - ax) < tol and min(ay, by) - tol <= py <= max(ay, by) + tol if abs(ay - by) < tol: return abs(py - ay) < tol and min(ax, bx) - tol <= px <= max(ax, bx) + tol return False def selfcheck(): errs = [] for a, b in WIRES: if a == b: errs.append(u'零长度导线 %s' % (a,)) marks = [(x, y) for _, _, x, y, _ in FLAGS] + [(x, y) for _, _, x, y, _ in PORTS] for x, y in marks: if x % 5 or y % 5: errs.append(u'旗/端口坐标未对齐到 5 的倍数: (%s,%s)' % (x, y)) pts = [(d + '.' + p, xy) for d, ps in PINS.items() for p, xy in ps.items()] pts += [(u'旗/端口', m) for m in marks] for name, p in pts: if not any(_on(p, a, b) for a, b in WIRES): errs.append(u'悬空: %s @ %s' % (name, p)) # 图元不越界(可用区 x 20~1145 / y 10~812;标题栏 x>455 且 y<192) for x, y, s, sz, _b, _c in TEXTS: wd = sum(11 if ord(ch) > 0x2000 else 5.6 for ch in s) * sz / 11.0 if x < 20 or x + wd > 1145 or y < 10 or y > 812: errs.append(u'文字越界 (%d,%d,+%d): %s' % (x, y, wd, s[:26])) if x + wd > 455 and y < 195: errs.append(u'压标题栏 (%d,%d,+%d): %s' % (x, y, wd, s[:26])) return errs if __name__ == '__main__': _errs = selfcheck() print(u'导线 %d 旗 %d 端口 %d 文字 %d 折线 %d 矩形 %d' % (len(WIRES), len(FLAGS), len(PORTS), len(TEXTS), len(POLYS), len(RECTS))) if _errs: print(u'\n!! %d 个问题:' % len(_errs)) for _e in _errs: print(' ', _e) sys.exit(1) print(u'本地自检通过') if '--push' not in sys.argv: sys.exit(0) from eda import run OPEN = "await eda.dmt_EditorControl.openDocument('%s');" % PAGE def call(body, timeout=180): r = run(OPEN + body, timeout=timeout) if not r.get('success'): print(u'推送失败:', json.dumps(r, ensure_ascii=False)[:500]) sys.exit(1) return r['result'] print(u'\n清空导线/旗/端口/文字/折线/矩形(元件保留)...') print(call(""" for (const x of await eda.sch_PrimitiveWire.getAll()) await eda.sch_PrimitiveWire.delete(x.getState_PrimitiveId()); for (const x of await eda.sch_PrimitiveText.getAll()) await eda.sch_PrimitiveText.delete(x.getState_PrimitiveId()); for (const x of await eda.sch_PrimitivePolygon.getAll()) await eda.sch_PrimitivePolygon.delete(x.getState_PrimitiveId()); for (const x of await eda.sch_PrimitiveRectangle.getAll()) await eda.sch_PrimitiveRectangle.delete(x.getState_PrimitiveId()); for (const c of await eda.sch_PrimitiveComponent.getAll()) if (['netflag','netport'].includes(c.getState_ComponentType())) await eda.sch_PrimitiveComponent.delete(c.getState_PrimitiveId()); return (await eda.dmt_Schematic.getCurrentSchematicPageInfo()).name; """)) def chunk(items, n): for k in range(0, len(items), n): yield items[k:k + n] _n = 0 for _part in chunk([[a[0], a[1], b[0], b[1]] for a, b in WIRES], 40): _n += call("for (const s of %s) await eda.sch_PrimitiveWire.create([s[0],s[1],s[2],s[3]]); return %d;" % (json.dumps(_part), len(_part))) print(u'导线 %d 段' % _n) _n = 0 for _part in chunk([list(f) for f in FLAGS], 25): _n += call("for (const f of %s) await eda.sch_PrimitiveComponent.createNetFlag(f[0],f[1],f[2],f[3],f[4],false); return %d;" % (json.dumps(_part), len(_part))) print(u'旗 %d 个' % _n) _n = 0 for _part in chunk([list(p) for p in PORTS], 25): _n += call("for (const p of %s) await eda.sch_PrimitiveComponent.createNetPort(p[0],p[1],p[2],p[3],p[4],false); return %d;" % (json.dumps(_part), len(_part))) print(u'端口 %d 个' % _n) _n = 0 for _part in chunk([list(r) for r in RECTS], 20): _n += call("for (const r of %s) await eda.sch_PrimitiveRectangle.create(r[0],r[1],r[2],r[3],0,0,r[4],null,1,0,0); return %d;" % (json.dumps(_part), len(_part))) print(u'矩形 %d 个' % _n) _n = 0 for _part in chunk([list(p) for p in POLYS], 20): _n += call("for (const p of %s) await eda.sch_PrimitivePolygon.create(p[0],p[1],null,p[2],p[3]); return %d;" % (json.dumps(_part), len(_part))) print(u'折线 %d 条' % _n) _n = 0 for _part in chunk([list(t) for t in TEXTS], 18): _n += call("for (const t of %s) await eda.sch_PrimitiveText.create(t[0],t[1],t[2],0,t[5],null,t[3],t[4]); return %d;" % (json.dumps(_part), len(_part))) print(u'文字 %d 条' % _n)