# -*- coding: utf-8 -*- u"""两处接口换件(2026-09-06 用户拍板): 1. **J1 12V 输入**:`KF128-5.08-2P` 螺钉端子 → **`XT30PW-M`**(C53373776,90° 板装公头)。 理由:用户要插拔式。⚠️ 但 **12V 输入是 8 A**(五路 4A×4.2V ÷ 0.92 ÷ 12V ≈ 7.6 A, 加风扇 0.7 A),而「3.5 mm DC」那一类(DC-002,外径 3.5 / 内径 1.35)**额定只有 0.5~1 A**, 差了近一个数量级 —— 接上去会烧插座。XT30 主脚额定 **20 A**(温升 <85 °C),余量充足。 选 2 脚版而不是 (2+2):(2+2) 多出来的两脚是 **1 A 信号脚**,我们用不上, 而且它的 pin 编号在库里读不出语义;2 脚版的符号**引脚名直接写着 `+` / `−`**,没有歧义。 2. **J26~J30 风扇座**:`KF2510-3A`(C2940511)→ **`2510-3A(直针)`**(C2803349)。 现在这颗名字像直针,**实测是弯针**:本体 7.62 × 12.01,整个甩到 +Y 一侧,进深 11.7 mm (§26.6 当年是拿放大截图量出来的)。换成真直针之后本体 7.62 × **5.81**、基本骑在焊盘上, **每个座省 6.2 mm 进深**,五个座(左板边 2 / 右板边 2 / 顶板边 1)一起把板边让出来。 ## 为什么这一轮**一根导线都不用动** | | 旧件引脚(rot 0)| 新件引脚(rot 0)| 做法 | |---|---|---|---| | J1 | p1(−20,+5) p2(−20,−5) | p1"−"(−20,+5) p2"+"(−20,−5) | 原位 (120,705) rot 180,**p1/p2 位置完全重合** | | J26~J30 | p1(−15,+10) p2(−15,0) p3(−15,−10) | p1(−20,+10) p2(−20,0) p3(−20,−10) | 元件**东移 5** 到 x=1085 ⇒ 引脚绝对坐标不变 | 极性也天然对上:板上实测 **J1.1 = GND、J1.2 = 12V 进线**,而新符号正好 p1 = `−`、p2 = `+`。 ⚠️ **J1 新件多出 p3/p4 两个固定脚**(rot 0 时在 (+5,+25) / (+5,−25),rot 180 后落在 (115,680) / (115,730))。它们是机械固定孔,**不接网络** —— 下发后要把它们加进 NC 清单, 否则 `verify_sch` 会报未连接。 ## 按 skill 的四条硬规矩 * **先 create 后 delete**(§9b):create 参数写错时删除已经生效,页面会凭空少件。 * **坐标 5 的倍数**(§8a-2b);1085 / 705 都是。 * **引脚跨距读回来断言**(§8d),对不上就停手 —— 这一版的判据更硬: 新件的每一个引脚都必须**落在旧件某个引脚的同一坐标上**(J1 的 p3/p4 除外)。 * 换件不动版面:导线是独立图元,旧件删掉之后新件引脚落在同一坐标就自动接上。 用法: /c/Python314/python .claude/p1p6_conn.py [--push] 下发完必跑:verify_sch.py --nets(两页)/ port_audit.py / xpage.py 再 pcb1_import.py --bom → --verify """ 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 # noqa: E402 LIB = '0819f05c4eef4c71ace90d822a990e87' P1 = '9a6096762e036e23' # P1-INPUT P6 = 'fd00f13e59336392' # P6-FAN-EDL # ── 新器件(按立创编号在库里当场查 uuid,不硬编码)────────────────────── NEW_J1 = ('XT30PW-M', 'C53373776') # fp CONN-TH_XT30PW-M 本体 13.30 x 13.95 NEW_FAN = ('2510-3A(直针)', 'C2803349') # fp CONN-TH_2510-3A 本体 7.62 x 5.81 # ── 旧件(primitiveId 是 2026-09-06 从页上读回来的,下发前逐个核存在)── OLD = { 'J1': ('163ed00ced8619b9', P1, (120, 705), 180), 'J26': ('adc7843724c9f781', P6, (1080, 700), 0), 'J27': ('4a1abe4be255fb93', P6, (1080, 620), 0), 'J28': ('1d15c744cd52913d', P6, (1080, 540), 0), 'J29': ('1202c39a84e9c4fb', P6, (1080, 460), 0), 'J30': ('68ab7d950e494c11', P6, (1080, 380), 0), } # 新件放哪(东移 5 抵消引脚偏移;J1 原位不动) NEW_XY = { 'J1': (120, 705, 180), 'J26': (1085, 700, 0), 'J27': (1085, 620, 0), 'J28': (1085, 540, 0), 'J29': (1085, 460, 0), 'J30': (1085, 380, 0), } # 旧件引脚的绝对坐标(p6-pins.json + 本轮从 P1 页读回来的) OLD_PINS = { 'J1': {'1': (140, 700), '2': (140, 710)}, 'J26': {'1': (1065, 710), '2': (1065, 700), '3': (1065, 690)}, 'J27': {'1': (1065, 630), '2': (1065, 620), '3': (1065, 610)}, 'J28': {'1': (1065, 550), '2': (1065, 540), '3': (1065, 530)}, 'J29': {'1': (1065, 470), '2': (1065, 460), '3': (1065, 450)}, 'J30': {'1': (1065, 390), '2': (1065, 380), '3': (1065, 370)}, } # J1 新件多出来的固定脚,不接网络 J1_NC = ('3', '4') def selfcheck(): bad = [] for des, (x, y, rot) in NEW_XY.items(): if x % 5 or y % 5: bad.append(u'%s 的坐标 (%d,%d) 不是 5 的倍数' % (des, x, y)) if rot not in (0, 90, 180, 270): bad.append(u'%s 的旋转角 %s 不合法' % (des, rot)) if set(NEW_XY) != set(OLD) or set(OLD_PINS) != set(OLD): bad.append(u'三张表的位号对不齐') return bad def dev(name, lcsc): r = run("const r = await eda.lib_Device.search(%s, undefined, undefined, undefined, 40, 1);" "return (r||[]).map(d => [d.name, d.supplierId||'', d.uuid, d.libraryUuid," " d.footprintName||'']);" % json.dumps(name), timeout=180) if not r.get('success'): sys.exit(u'查器件失败: ' + json.dumps(r, ensure_ascii=False)[:300]) for n, s, u, l, fp in r['result']: if s == lcsc: print(u' 器件 %-16s %-11s uuid=%s fp=%s' % (n, s, u, fp)) return u, l, fp sys.exit(u'库里找不到 %s (%s)' % (name, lcsc)) JS_CREATE = u""" await eda.dmt_EditorControl.openDocument('%(page)s'); await new Promise(r => setTimeout(r, 1800)); const cur = (await eda.dmt_Schematic.getCurrentSchematicPageInfo()).name; if (cur !== '%(pname)s') return {err: '打错页了: ' + cur}; // 要删的旧件必须都还在 —— 不在就说明这个脚本跑过一次了 const have = new Set(); for (const c of await eda.sch_PrimitiveComponent.getAll()) have.add(c.getState_PrimitiveId()); const missing = %(need)s.filter(id => !have.has(id)); if (missing.length) return {err: '要删的旧件不在页上(已经跑过一次?): ' + missing.join(' ')}; const made = []; for (const [des, uuid, lib, x, y, rot] of %(plan)s) { const c = await eda.sch_PrimitiveComponent.create({libraryUuid: lib, uuid: uuid}, x, y, undefined, rot, false); const pins = []; for (const p of await c.getAllPins()) pins.push([String(p.pinNumber||''), String(p.pinName||''), +p.x, +p.y]); made.push({des: des, id: c.getState_PrimitiveId(), pins: pins}); } return {made: made}; """ JS_FINISH = u""" await eda.dmt_EditorControl.openDocument('%(page)s'); await new Promise(r => setTimeout(r, 1800)); const cur = (await eda.dmt_Schematic.getCurrentSchematicPageInfo()).name; if (cur !== '%(pname)s') return {err: '打错页了: ' + cur}; const out = {deleted: [], named: []}; for (const id of %(dele)s) { await eda.sch_PrimitiveComponent.delete(id); out.deleted.push(id); } const byId = {}; for (const c of await eda.sch_PrimitiveComponent.getAll()) byId[c.getState_PrimitiveId()] = c; for (const [id, des] of %(names)s) { const c = byId[id]; if (!c) { out.err = '新建的元件不见了: ' + des; return out; } await c.toAsync().setState_Designator(des).done(); out.named.push(des); } return out; """ JS_DROP = u""" await eda.dmt_EditorControl.openDocument('%(page)s'); await new Promise(r => setTimeout(r, 1500)); const cur = (await eda.dmt_Schematic.getCurrentSchematicPageInfo()).name; if (cur !== '%(pname)s') return {err: '打错页了: ' + cur}; let n = 0; for (const id of %(ids)s) { await eda.sch_PrimitiveComponent.delete(id); n++; } return {n: n}; """ def do_page(page, pname, deslist, devmap, push): plan, need, dele, expect = [], [], [], {} for des in deslist: x, y, rot = NEW_XY[des] u, l, _fp = devmap[des] plan.append([des, u, l, x, y, rot]) need.append(OLD[des][0]) dele.append(OLD[des][0]) expect[des] = OLD_PINS[des] r = run(JS_CREATE % dict(page=page, pname=pname, need=json.dumps(need), plan=json.dumps(plan)), timeout=400) if not r.get('success'): sys.exit(u'建新件失败: ' + json.dumps(r, ensure_ascii=False)[:500]) d = r['result'] if d.get('err'): sys.exit(u'⛔ ' + d['err']) # ── 硬断言:新件每个引脚都要落在旧件同号引脚的坐标上 ── bad, names = [], [] for m in d['made']: des = m['des'] got = {p[0]: (p[2], p[3]) for p in m['pins']} for num, xy in expect[des].items(): if num not in got: bad.append(u'%s 少了 p%s' % (des, num)) elif got[num] != tuple(xy): bad.append(u'%s p%s 在 %s,旧件在 %s' % (des, num, got[num], tuple(xy))) extra = sorted(set(got) - set(expect[des])) if des == 'J1': if extra != list(J1_NC): bad.append(u'J1 多出来的脚是 %s,预期 %s' % (extra, list(J1_NC))) elif extra: bad.append(u'%s 多出来的脚 %s(旧件没有)' % (des, extra)) names.append([m['id'], des]) for m in d['made']: print(u' 新件 %-4s id=%s %s' % (m['des'], m['id'], ' '.join(u'p%s"%s"@%d,%d' % tuple(p) for p in m['pins']))) if bad: print(u'\n⛔ 引脚对不上,**新件已经建出来了但没删旧件** —— 请手工撤销:') for b in bad: print(u' ' + b) sys.exit(1) print(u' ✔ %d 个新件的引脚逐个落在旧件同号引脚的坐标上' % len(d['made'])) if not push: # ⚠️ **试跑必须自己收尾。** 「先 create 后 delete」(skill §9b)意味着 # 断言那一步已经把新件建到页上了 —— 不删掉的话,一次「只看看」的试跑 # 就在页面上留下 6 个和旧件完全重叠的元件,而且位号还是自动分配的, # 下一次跑又会再建一批。2026-09-06 第一次跑就是这么留下 6 个的。 r3 = run(JS_DROP % dict(page=page, pname=pname, ids=json.dumps([m['id'] for m in d['made']])), timeout=300) if not r3.get('success'): sys.exit(u'⛔ 试跑之后没能删掉试建的新件,页面现在是重叠状态,手工清理: ' + json.dumps(r3, ensure_ascii=False)[:400]) print(u' (试跑:断言过了,刚建的 %d 个新件已经删回去,页面原样未动)' % r3['result'].get('n', 0)) return d r2 = run(JS_FINISH % dict(page=page, pname=pname, dele=json.dumps(dele), names=json.dumps(names)), timeout=400) if not r2.get('success'): sys.exit(u'删旧件/改位号失败: ' + json.dumps(r2, ensure_ascii=False)[:500]) x = r2['result'] if x.get('err'): sys.exit(u'⛔ ' + x['err']) print(u' 删掉旧件 %d 个,位号设好 %s' % (len(x['deleted']), ' '.join(x['named']))) return d def main(): push = '--push' in sys.argv[1:] bad = selfcheck() if bad: for b in bad: print(u'⛔ ' + b) sys.exit(1) print(u'两处接口换件:J1 → XT30PW-M(20 A) / J26~J30 → 2510-3A 直针座') u1 = dev(*NEW_J1) u6 = dev(*NEW_FAN) devmap = {'J1': u1} for d in ('J26', 'J27', 'J28', 'J29', 'J30'): devmap[d] = u6 print(u'\n[P1-INPUT] J1') do_page(P1, 'P1-INPUT', ['J1'], devmap, push) print(u'\n[P6-FAN-EDL] J26~J30') do_page(P6, 'P6-FAN-EDL', ['J26', 'J27', 'J28', 'J29', 'J30'], devmap, push) if push: print(u'\n下一步(缺一不可):') print(u' 1. J1 的 p3/p4 是机械固定脚,加进 P1-INPUT 的 NC 清单') print(u' 2. verify_sch.py --nets(两页)/ port_audit.py / xpage.py') print(u' 3. pcb1_import.py --bom → --verify(封装换了,pcb1-parts.json 必须重抓)') print(u' 4. pcb1_place.FP_BODY / H_FP 加两条新封装,再重推摆位') return 0 if __name__ == '__main__': sys.exit(main())