# -*- coding: utf-8 -*- u"""下板 PCB1:把 `J1` 从 XT30PW-M 换成圆形 DC 电源座 DC-005-5.5*2.5。**不走导入对话框。** /c/Python314/python .claude/pcb1_dcjack.py # 干跑 /c/Python314/python .claude/pcb1_dcjack.py --push # 换件 + 接网络 + 存盘 + 回查 换完再跑 `pcb1_place.py --push` 把它摆到位(本脚本只负责"换成哪个件、接哪几个网")。 ## ⛔ 为什么绕开「从原理图导入变更」 2026-09-08 实测:那个对话框对下板已经**不能用**了。它提出的变更是一大串风扇块 重映射(`J27→J26`、`Q10→Q9`、`R753→R752`、`Q7` 换成 4 脚座封装…), 而下板和原理图**按四项逐个对过、全部一致**: | 对账项 | 结果 | |---|---| | 位号(301 个)| 全对 | | 封装名 | 只有 J1 一处不同(就是本脚本要改的)| | 器件值(风扇块 23 件)| 全对 | | 焊盘网络(风扇块)| 全对,且自洽(J26→`FAN_IN_TACH_RAW`→R750/751/752→Q9→`FAN_TACH_IN`)| 根因是 skill §9o:**P6-FAN-EDL 页整页重画过,通道 ID 全变了**, EasyEDA 按内部 ID 重新配对,配错了一格。应用下去会把一个本来正确的风扇块打乱。 ⇒ 这块板上「改一颗件的封装」只能定点做。代价是这颗件与原理图的内部 ID 链接断了, 以后导入对话框会报它 —— 但那个对话框对这块板本来就已经不可用。 ## 为什么不能用 `pcb_PrimitiveComponent.modify()` 读过它的实现:只处理 `layer / x / y / rotation / primitiveLock / otherProperty` 和位号那一组属性,**没有 `setState_Footprint`**。换封装只能删了重建。 ## `pcb_PrimitiveComponent.create()` 的签名(实测) ``` create(器件{libraryUuid,uuid}, layer, x, y, rotation, ?) ``` ⚠️ **第二个参数是层,不是 x** —— 按 `(dev, x, y, rot, layer)` 传的话, x 会被当成层号写进去(实测读回 `layer: 10236.22`),而元件落在 (0,0) 附近, `getAll()` 数量对得上、看着像成功了。 ## 引脚与接法(依据见 `p1_dcjack.py` 文件头) pin1 = 中心正极 → 原 J1 的 `+` 那个网络;pin2 / pin3 = 两个负极触点 → 都接 GND。 **网络名不写死**,从被删掉的那颗 J1 身上读,避免自动命名网改名之后接错。 """ 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 PCB = '7e9415a16a909c51' MIL = 39.3700787 # 工程库里的 DC-005-5.5*2.5(原理图放上去时 EasyEDA 自己复制进来的) DEV = {'libraryUuid': '1e5745f4caff4b15a921694d2fd912e4', 'uuid': '82b5a3c78e8ab633'} FP_NAME = 'DC-IN-TH_DC-005-2' # 摆位交给 pcb1_place.py,这里只要一个不碍事的落脚点(板外) DROP_X, DROP_Y = 240.0, 40.0 # 旧 J1(XT30)的引脚 → 新 J1(DC-005)的引脚 # 旧 pin2 = '+' → 新 pin1(中心正极) # 旧 pin1 = '−' → 新 pin2 + pin3(两个负极触点并联,见 p1_dcjack.py) # 旧 pin3/pin4 是 XT30 的固定脚,DC-005 没有对应物 MAP = {'1': '2', '2': '1', '3': '1'} # 新引脚号 -> 从旧的哪个引脚取网络 def call(js, timeout=300): r = run(js, timeout=timeout) if not r.get('success'): raise RuntimeError(json.dumps(r, ensure_ascii=False)[:1200]) return r['result'] JS_SURVEY = u""" await eda.dmt_EditorControl.openDocument('%s'); await new Promise(r => setTimeout(r, 2000)); const MIL = %f; const comps = await eda.pcb_PrimitiveComponent.getAll(); const out = {n: comps.length, j1: null, strays: []}; for (const c of comps) { const d = c.getState_Designator(); const fp = (c.getState_Footprint() || {}).name; if (d === 'J1') { const pins = {}; for (const p of await c.getAllPins()) pins[p.getState_PadNumber()] = {net: p.getState_Net(), pid: p.getState_PrimitiveId()}; out.j1 = {pid: c.getState_PrimitiveId(), fp, dev: (c.getState_Component() || {}).name, x: c.getState_X() / MIL, y: c.getState_Y() / MIL, rot: c.getState_Rotation(), pins}; } else if (fp === '%s') { // 试签名时留下的临时件(位号会是 DC1 之类) out.strays.push({pid: c.getState_PrimitiveId(), des: d, x: c.getState_X() / MIL, y: c.getState_Y() / MIL}); } } return out; """ % (PCB, MIL, FP_NAME) def main(): push = '--push' in sys.argv s = call(JS_SURVEY) print(u'板上元件 %d 个' % s['n']) j1 = s['j1'] if not j1: sys.exit(u'⛔ 板上没有 J1') print(u'J1 %s / %s (%.2f, %.2f) rot %s' % (j1['dev'], j1['fp'], j1['x'], j1['y'], j1['rot'])) for n in sorted(j1['pins']): print(u' pin%-2s net=%s' % (n, j1['pins'][n]['net'] or '(空)')) if s['strays']: print(u'临时残留 DC-005 %d 个(会一并删掉):%s' % (len(s['strays']), ', '.join('%s@(%.0f,%.0f)' % (t['des'], t['x'], t['y']) for t in s['strays']))) if j1['fp'] == FP_NAME: print(u'\nJ1 已经是 %s,无需再换。' % FP_NAME) return want = {} for new_pin, old_pin in sorted(MAP.items()): net = (j1['pins'].get(old_pin) or {}).get('net') or '' if not net: sys.exit(u'⛔ 旧 J1 的 pin%s 没有网络 —— 先查清楚再换' % old_pin) want[new_pin] = net print(u'\n新 J1 的接法(网络名从旧件读,不写死):') for n in sorted(want): print(u' pin%-2s <- 旧 pin%-2s net=%s' % (n, MAP[n], want[n])) assert want['2'] == want['3'], u'⛔ 两个负极触点应该同网(都是旧件的 −),实得 %s / %s' % (want['2'], want['3']) if not push: print(u'\n加 --push 执行') return r = call(u""" await eda.dmt_EditorControl.openDocument('%s'); await new Promise(r => setTimeout(r, 2000)); const MIL = %f, DEV = %s, WANT = %s; const before = (await eda.pcb_PrimitiveComponent.getAll()).length; // 1) 删掉旧 J1 和试签名留下的临时件 for (const id of %s) await eda.pcb_PrimitiveComponent.delete(id); // 2) 建新件。⚠️ 签名是 (器件, layer, x, y, rotation, ?) const c = await eda.pcb_PrimitiveComponent.create(DEV, 1, %f * MIL, %f * MIL, 0, undefined); const pid = c.getState_PrimitiveId(); await eda.pcb_PrimitiveComponent.modify(pid, {}); // 拿一个同步句柄 const k = (await eda.pcb_PrimitiveComponent.getAll()).find(x => x.getState_PrimitiveId() === pid); k.toAsync().setState_Designator('J1').done(); // 3) 接网络 const done = {}; for (const p of await k.getAllPins()) { const n = p.getState_PadNumber(); if (WANT[n] === undefined) continue; p.toAsync().setState_Net(WANT[n]).done(); done[n] = WANT[n]; } const after = (await eda.pcb_PrimitiveComponent.getAll()).length; return {before, after, pid, done}; """ % (PCB, MIL, json.dumps(DEV), json.dumps(want), json.dumps([j1['pid']] + [t['pid'] for t in s['strays']]), DROP_X, DROP_Y)) print(u'\n元件数 %d -> %d,新 J1 = %s,接网络 %s' % (r['before'], r['after'], r['pid'], r['done'])) call(u"await eda.dmt_EditorControl.openDocument('%s'); await new Promise(r=>setTimeout(r,1500));" u"return await eda.pcb_Document.save();" % PCB) # §9ad:save() 说成功不算数,重新读一遍才算 b = call(JS_SURVEY) nj = b['j1'] if not nj: sys.exit(u'⛔ 回查读不到 J1') print(u'\n回查 J1:%s / %s (%.2f, %.2f) rot %s' % (nj['dev'], nj['fp'], nj['x'], nj['y'], nj['rot'])) assert nj['fp'] == FP_NAME, u'⛔ 封装没换成: %s' % nj['fp'] for n in sorted(want): got = (nj['pins'].get(n) or {}).get('net') print(u' pin%-2s net=%s' % (n, got or '(空)')) assert got == want[n], u'⛔ pin%s 网络是 %s,应该是 %s' % (n, got, want[n]) assert b['n'] == s['n'] - len(s['strays']), \ u'⛔ 元件数 %d,应该是 %d' % (b['n'], s['n'] - len(s['strays'])) assert not b['strays'], u'⛔ 还有临时件没删干净' print(u' ✔ 封装 / 三个引脚网络 / 元件数 全部回查通过') print(u'\n接着跑:pcb1_place.py --push(把它摆到西板边)') if __name__ == '__main__': main()