# -*- coding: utf-8 -*- u"""P1-INPUT:把 12V 输入座 `J1` 从 XT30PW-M 换成**圆形 DC 电源座** DC-005-5.5*2.5。 /c/Python314/python .claude/p1_dcjack.py # 干跑,只打印计划 /c/Python314/python .claude/p1_dcjack.py --push # 执行 + 存盘 用户 2026-09-08 拍板:换圆座、**不降载**,接受插座超额定运行。 ## ⛔ 这颗件是全板唯一一处「明知超额定还要装」的地方 整机 12V 最坏工况 **9.99 A**(设计文档 §22.6),而 DC-005 家族的额定是 **2~5 A**(立创同族 XKB `DC-005-2.5A-2.5` = C319098 官方标 2.5 A)。 发热点在**插头内芯与插座弹片的接触面**,不在板上的铜 —— 板子这边再加强也救不了那一处。 ⇒ 这不是「算过没问题」,是**已知风险、用户明确接受**。设计文档 §1 / §22.6 要同步写明。 ## 引脚定义(查过三处,两处互相印证) | 脚 | 含义 | 本设计接法 | |---|---|---| | 1 | 中心极,**正** | `VIN_RAW` → F1.1 | | 2 | 负极**静触点**(无插头时与 3 短接)| GND | | 3 | 负极**动触点**(插头外壳压这一片)| GND | **2 和 3 一起接地**,理由两条: 1. **安全**:不同厂家对「哪一片是常驻外壳极」的叫法不一致 —— 有的把 2 当外壳、 有的把 3 当外壳。两个都接地就与这个分歧无关,且**两者都是负极侧**, 无论如何接不出「插拔瞬间把适配器正极对地短路」这种事故。 (反过来,若把 3 当成「开关信号」接到别处,一旦它其实是外壳极,回流路径就断了。) 2. **正好帮到超额定**:两个负极触点**并联分流**,接触电阻减半、发热减半。 这是本轮唯一一处能在板端缓解 10 A 问题的地方。 ⚠️ 装配前用万用表核一次:无插头时 2-3 应导通、1 对 2/3 应断开。 ## 版面(原理图单位 0.01 inch,Y 向上,坐标一律 5 的倍数) 元件放 `(110, 700)` rot 0 —— 该符号 rot 0 时三个脚都在**右侧** `+30`: pin1 (+) (140, 710) → 复用原有导线 [250,710 → 140,710] 到 F1.1 pin3 (−) (140, 700) → [140,700 → 185,700 → 185,665] → 地旗 (185,665) pin2 (−) (140, 690) → [140,690 → 140,655] → 原有地旗 (140,655) ⚠️ 原来那条地线 `[140,700 → 140,655]` **必须删掉**:新符号的 pin2 落在 (140,690) 正好在它中间,而 §8b「导线在元件引脚处被截断」会把它劈成两段, 接出来的东西看着连着、实际是断的。 """ import io import json import os import sys HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, HERE) from eda import run PAGE = '9a6096762e036e23' # P1-INPUT LIB = '0819f05c4eef4c71ace90d822a990e87' DEV = '66ed565d4c984ace901de00bbc83af82' # DC-005-5.5*2.5,立创 C9900017043 CX, CY = 110, 700 # 新 J1 的中心 PIN = {'1': (140, 710), '3': (140, 700), '2': (140, 690)} # rot 0 时的绝对坐标 # 新画的导线(扁平数组,见 §4) WIRES = [ [140, 690, 140, 655], # pin2 -> 原有地旗 [140, 700, 185, 700, 185, 665], # pin3 -> 新地旗 ] # 新建的旗:(kind, net, x, y, rot) FLAGS = [('Ground', 'GND', 185, 665, 0)] def _seg(a, b, c, d): u"""一段导线的规范化键 —— 两端顺序无关(`getState_Line()` 会把两端反过来,§5)。""" return ','.join(str(v) for v in min((a, b, c, d), (c, d, a, b))) def _mine(): u"""本脚本负责的那几段线(把折线拆成段)。""" out = set() for w in WIRES: for i in range(0, len(w) - 3, 2): out.add(_seg(w[i], w[i + 1], w[i + 2], w[i + 3])) return out def call(js, timeout=240): r = run(js, timeout=timeout) if not r.get('success'): raise RuntimeError(json.dumps(r, ensure_ascii=False)[:1200]) return r['result'] def selfcheck(): u"""下发前的本地几何自检(§6d / §8a-2b)。""" for w in WIRES: assert len(w) % 2 == 0 and len(w) >= 4, u'导线点数不对: %s' % (w,) for v in w: assert v % 5 == 0, u'导线坐标不是 5 的倍数: %s' % (w,) for i in range(0, len(w) - 2, 2): ax, ay, bx, by = w[i], w[i + 1], w[i + 2], w[i + 3] assert (ax == bx) != (ay == by), u'不是横平竖直或零长度: %s' % ((ax, ay, bx, by),) for _k, _n, x, y, _r in FLAGS: assert x % 5 == 0 and y % 5 == 0, u'旗坐标不是 5 的倍数' # 每条导线的起点都必须落在某个引脚上,否则就是算错了位置 starts = {(w[0], w[1]) for w in WIRES} assert starts == {PIN['2'], PIN['3']}, u'导线起点和引脚对不上: %s' % (starts,) print(u'本地自检通过:%d 条导线 / %d 个旗,坐标全是 5 的倍数、横平竖直' % (len(WIRES), len(FLAGS))) JS_SURVEY = u""" await eda.dmt_EditorControl.openDocument('%s'); await new Promise(r => setTimeout(r, 1500)); const cur = await eda.dmt_Schematic.getCurrentSchematicPageInfo(); if (cur.name !== 'P1-INPUT') return {err: 'wrong page: ' + cur.name}; const out = {page: cur.name, parts: [], wires: [], flags: []}; for (const c of await eda.sch_PrimitiveComponent.getAll()) { const t = c.getState_ComponentType(); const pid = c.getState_PrimitiveId(); const x = c.getState_X(), y = c.getState_Y(); if (t === 'part') { const nm = (c.getState_Component() || {}).name || ''; if (c.getState_Designator() === 'J1' || nm.indexOf('DC-005') === 0) out.parts.push({pid, des: c.getState_Designator(), nm, x, y, rot: c.getState_Rotation()}); } else if (t === 'netflag' && x < 320 && y > 590 && y < 820) { out.flags.push({pid, net: c.getState_Net(), x, y, rot: c.getState_Rotation()}); } } for (const w of await eda.sch_PrimitiveWire.getAll()) { const pts = w.getState_Line(); const xs = pts.filter((_, i) => i %% 2 === 0), ys = pts.filter((_, i) => i %% 2 === 1); if (Math.min(...xs) > 320 || Math.max(...ys) < 590 || Math.min(...ys) > 820) continue; out.wires.push({pid: w.getState_PrimitiveId(), pts}); } return out; """ % PAGE def survey(): s = call(JS_SURVEY) if s.get('err'): sys.exit(s['err']) return s def main(): push = '--push' in sys.argv selfcheck() s = survey() print(u'\n当前 P1-INPUT 上:') for p in s['parts']: print(u' 元件 %-4s %-18s (%d,%d) rot %s %s' % (p['des'], p['nm'], p['x'], p['y'], p['rot'], p['pid'])) for f in s['flags']: print(u' 旗 %-6s (%d,%d) rot %s %s' % (f['net'], f['x'], f['y'], f['rot'], f['pid'])) for w in s['wires']: print(u' 导线 %s %s' % (w['pid'], w['pts'])) old_j1 = [p for p in s['parts'] if p['des'] == 'J1' and p['nm'].find('DC-005') != 0] dcs = [p for p in s['parts'] if p['nm'].find('DC-005') == 0] # 那条要删的地线:起点/终点里同时出现 (140,700) 和 (140,655) gnd_old = [w for w in s['wires'] if [140, 700] == w['pts'][:2] or [140, 700] == w['pts'][-2:]] print(u'\n计划:') print(u' 删 旧 J1(XT30) %d 个' % len(old_j1)) print(u' 删 旧地线 [140,700→140,655] %d 条' % len(gnd_old)) print(u' 留 现有 DC-005 实例 %d 个(多余的删掉,留一个 rot 0 的改名 J1)' % len(dcs)) print(u' 新建导线 %d 条 / 地旗 %d 个' % (len(WIRES), len(FLAGS))) if not push: print(u'\n加 --push 执行') return keep = None for p in dcs: if p['rot'] == 0 and keep is None: keep = p if keep is None: sys.exit(u'⛔ 页面上没有 rot 0 的 DC-005 实例 —— 先手动 create 一个再跑') drop = [p['pid'] for p in dcs if p['pid'] != keep['pid']] + [p['pid'] for p in old_j1] r = call(u""" await eda.dmt_EditorControl.openDocument('%s'); await new Promise(r => setTimeout(r, 1500)); const cur = await eda.dmt_Schematic.getCurrentSchematicPageInfo(); if (cur.name !== 'P1-INPUT') return {err: 'wrong page: ' + cur.name}; const DROP = %s, WDROP = %s; let nd = 0, nw = 0; for (const id of DROP) { try { await eda.sch_PrimitiveComponent.delete(id); nd++; } catch (e) {} } for (const id of WDROP) { try { await eda.sch_PrimitiveWire.delete(id); nw++; } catch (e) {} } // 留下来的那颗:挪到位、改位号。**只动 x/y,不动 rotation**(§6b) const all = await eda.sch_PrimitiveComponent.getAll(); const k = all.find(c => c.getState_PrimitiveId() === '%s'); if (!k) return {err: 'kept component vanished'}; await k.toAsync().setState_X(%d).setState_Y(%d).setState_Designator('J1').done(); return {nd, nw}; """ % (PAGE, json.dumps(drop), json.dumps([w['pid'] for w in gnd_old]), keep['pid'], CX, CY)) if r.get('err'): sys.exit(r['err']) print(u'\n删元件 %d / 删导线 %d,已把 %s 挪到 (%d,%d) 并改名 J1' % (r['nd'], r['nw'], keep['pid'], CX, CY)) # ⚠️ 挪完必须读回引脚,不能按偏移量推(§6c / §8d) pins = call(u""" await eda.dmt_EditorControl.openDocument('%s'); await new Promise(r => setTimeout(r, 1200)); for (const c of await eda.sch_PrimitiveComponent.getAll()) { if (c.getState_ComponentType() !== 'part') continue; if (c.getState_Designator() !== 'J1') continue; const o = {}; for (const p of await c.getAllPins()) o[p.getState_PinNumber()] = [p.getState_X(), p.getState_Y()]; return {x: c.getState_X(), y: c.getState_Y(), rot: c.getState_Rotation(), nm: (c.getState_Component() || {}).name, pins: o}; } return null;""" % PAGE) print(u'回读 J1:%s (%s,%s) rot %s 引脚 %s' % (pins['nm'], pins['x'], pins['y'], pins['rot'], pins['pins'])) for n, (ex, ey) in PIN.items(): got = pins['pins'].get(n) assert got and got[0] == ex and got[1] == ey, \ u'⛔ J1.%s 落在 %s,期望 (%d,%d) —— 方向或位置不对,导线先别画' % (n, got, ex, ey) print(u' ✔ 三个引脚坐标与计划逐个对上') r2 = call(u""" await eda.dmt_EditorControl.openDocument('%s'); await new Promise(r => setTimeout(r, 1200)); // ⚠️ 幂等:重跑时先删掉本脚本画过的导线和地旗,否则会叠一层 // (§9v:只加不删的脚本不幂等;重叠的细线还会被 EasyEDA 并进旧线里,看着像"没生效") // ⚠️⚠️ 比对必须按**线段集合**,不能按 getState_Line() 的原数组: // 它返回的是端点对(§5),而且两端顺序会反过来 —— 按数组比一条都匹配不上, // 于是"清理"静默落空、每跑一次多画一份。 const W = %s, F = %s; const seg = (a, b, c, d) => [a, b, c, d] < [c, d, a, b] ? [a, b, c, d].join(',') : [c, d, a, b].join(','); const mine = new Set(); for (const w of W) for (let i = 0; i + 3 < w.length; i += 2) mine.add(seg(w[i], w[i+1], w[i+2], w[i+3])); let nrm = 0; for (const w of await eda.sch_PrimitiveWire.getAll()) { const p = w.getState_Line(); let all = p.length >= 4, any = false; for (let i = 0; i + 3 < p.length; i += 4) { // 端点对:每 4 个数一段 const s = seg(p[i], p[i+1], p[i+2], p[i+3]); if (mine.has(s)) any = true; else all = false; } if (all && any) { await eda.sch_PrimitiveWire.delete(w.getState_PrimitiveId()); nrm++; } } for (const c of await eda.sch_PrimitiveComponent.getAll()) if (c.getState_ComponentType() === 'netflag') for (const f of F) if (c.getState_X() === f[2] && c.getState_Y() === f[3]) { await eda.sch_PrimitiveComponent.delete(c.getState_PrimitiveId()); nrm++; } let nw = 0, nf = 0; for (const w of W) { await eda.sch_PrimitiveWire.create(w); nw++; } for (const f of F) { await eda.sch_PrimitiveComponent.createNetFlag(f[0], f[1], f[2], f[3], f[4], false); nf++; } // 原理图存盘是 sch_Document.save(),不是 dmt_EditorControl.save()(后者不存在) const saved = await eda.sch_Document.save(); return {nw, nf, nrm, saved}; """ % (PAGE, json.dumps(WIRES), json.dumps(FLAGS))) print(u'清掉重跑残留 %d 个;新建导线 %d 条 / 地旗 %d 个,save() 返回 %s' % (r2['nrm'], r2['nw'], r2['nf'], r2['saved'])) # §9ad:save() 的返回值不算数,回读一遍才是落盘的证据。 # ⚠️ 按**线段集合**比,不是按数组:getState_Line() 回来的是端点对,顺序还会反(§5)。 back = survey() got = set() for w in back['wires']: p = w['pts'] for i in range(0, len(p) - 3, 4): got.add(_seg(p[i], p[i + 1], p[i + 2], p[i + 3])) dup = sum(1 for w in back['wires'] for i in range(0, len(w['pts']) - 3, 4) if _seg(*w['pts'][i:i + 4]) in _mine()) for s in sorted(_mine()): assert s in got, u'⛔ 导线没落盘: %s' % (s,) assert dup == len(_mine()), u'⛔ 本脚本的线在板上有 %d 段,应该是 %d 段(重复了)' % (dup, len(_mine())) got_f = {(f['net'], f['x'], f['y']) for f in back['flags']} for _k, n, x, y, _r in FLAGS: assert (n, x, y) in got_f, u'⛔ 地旗没落盘: %s (%d,%d)' % (n, x, y) print(u' ✔ 回读确认:%d 段导线(无重复)+ %d 个地旗都在盘上' % (len(_mine()), len(FLAGS))) print(u'\n接着跑:verify_sch.py --nets → port_audit.py --board Board1 → xpage.py') if __name__ == '__main__': main()