# -*- coding: utf-8 -*- u"""重载 EasyEDA 客户端页面(底板版,照 `.claude/pcb2_reload.py`,skill §9k / §9ae)。 /c/Python314/python .claude/r2/reload_base.py 为什么要重载:API 下发两千段导线之后 `getCalculatingRatlineStatus()` 一直是 `active`, 铺铜「重建所有」永远填 0 块(§9ae-4);`stopCalculatingRatline()` 解不开,只能重载。 DRC 的连通性索引也要重载之后才认 API 新建的铜(§9aa)。 顺序:关对话框 → 数图元(基线)→ 删铺铜边框(重载反正会丢)→ save → reload → 等 windowId 变掉 → **再数一遍,必须与基线逐项相等**(比 pcb2 版的「不为零」更严)。不过关就停手,后面任何会 save() 的脚本都不许跑。 重载之后接 `pour_base.py --push`(重建铜面 + 重建所有)。 """ import json import os import sys import time import urllib.request sys.stdout.reconfigure(encoding='utf-8') HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, os.path.dirname(HERE)) sys.path.insert(0, HERE) from eda import run, port # noqa: E402 # `--blade` 对刀片做同一件事(2026-09-11:刀片 90×68 下发之后也要重载再 DRC / 铺铜,skill §9aa) if '--blade' in sys.argv: import pcb_blade as B # noqa: E402 else: import pcb_base as B # noqa: E402 OPEN = ("await eda.dmt_EditorControl.openDocument('%s');" "await new Promise(r=>setTimeout(r,%d));") COUNT_JS = u""" const n = async k => ((await eda[k].getAll()) || []).length; return {doc: (await eda.dmt_Pcb.getCurrentPcbInfo() || {}).name, comps: await n('pcb_PrimitiveComponent'), lines: await n('pcb_PrimitiveLine'), vias: await n('pcb_PrimitiveVia'), pads: await n('pcb_PrimitivePad')}; """ RELOAD_JS = u""" const sleep = ms => new Promise(r => setTimeout(r, ms)); let n = 0; for (const o of await eda.pcb_PrimitivePour.getAll()) { await eda.pcb_PrimitivePour.delete(o.primitiveId); n++; } await eda.pcb_Document.save(); await sleep(1500); window.onbeforeunload = null; setTimeout(() => location.reload(true), 300); return {deletedPours: n}; """ CANCEL_JS = u""" const sleep = ms => new Promise(r => setTimeout(r, ms)); const nat = b => { const o={bubbles:true,cancelable:true,view:window,button:0}; for (const t of ['pointerdown','mousedown','pointerup','mouseup','click']) b.dispatchEvent(new (t.startsWith('pointer')?PointerEvent:MouseEvent)(t,o)); }; let k = 0; for (const b of document.querySelectorAll('button')) if ((b.innerText||'').trim()==='取消' && b.offsetParent!==null) { nat(b); k++; await sleep(800); } return {cancelled: k}; """ def wid(): try: with urllib.request.urlopen('http://127.0.0.1:%d/health' % port(), timeout=3) as r: return json.loads(r.read()).get('activeWindowId') except Exception: return None def count(wait_ms): r = run(OPEN % (B.PCB, wait_ms) + COUNT_JS, timeout=180) if not r.get('success'): return None return r['result'] KEYS = ('comps', 'lines', 'vias', 'pads') def main(): u"""关对话框 → 数基线 → 删铺铜边框 → save → reload → 再数一遍,必须与基线逐项相等。 ⚠ 2026-09-11 以前这段是**模块顶层代码**:别的脚本 `import reload_base` 想借 `CANCEL_JS` 关个对话框, 结果整套重载直接跑了(删了 119 块铺铜边框、存盘、重载)。现在 import 只拿常量 / 函数。""" run(CANCEL_JS, timeout=60) base = count(1500) if not base or not all(base.get(k) for k in KEYS): sys.exit(u'!! 重载前数图元就不对,不重载: %s' % base) print(u'重载前:%s 元件 %d / 导线 %d / 过孔 %d / 焊盘 %d' % (base['doc'], base['comps'], base['lines'], base['vias'], base['pads'])) w0 = wid() r = run(OPEN % (B.PCB, 1500) + RELOAD_JS, timeout=120) if not r.get('success'): sys.exit(u'!! 删铜面 / 存盘失败,没有重载: %s' % json.dumps(r, ensure_ascii=False)[:400]) print(u'重载前 windowId %s,删铺铜边框 %s' % (w0, r['result'].get('deletedPours'))) for i in range(36): time.sleep(5) w = wid() if w and w != w0: print(u'已重载,新 windowId %s(%d s)' % (w, (i + 1) * 5)) time.sleep(10) now = count(9000) if not now: sys.exit(u'!! 重载后数图元失败 —— **不要再跑任何会 save() 的脚本**(§9ae)') print(u'重载后:%s 元件 %d / 导线 %d / 过孔 %d / 焊盘 %d' % (now['doc'], now['comps'], now['lines'], now['vias'], now['pads'])) # 元件 / 过孔 / 焊盘必须逐项相等;导线允许**少**不超过 1%(2026-09-10 实测 2222 → 2220: # 客户端载入时把重合段并掉,`board_check` 37 个网逐条不变)。失同步的样子是整层读回 0,照样拦得住。 diff = [k for k in KEYS if k != 'lines' and now.get(k) != base.get(k)] nl, bl = now.get('lines') or 0, base['lines'] if not (bl * 0.99 <= nl <= bl): diff.append('lines') elif nl != bl: print(u' (导线少了 %d 段,≤1%%,按重合段被合并处理)' % (bl - nl)) if diff: sys.exit(u'!! 重载后与基线不符(%s)—— 桥接与文档失同步(§9ae)。\n' u' **立刻停手,不要 save(),不要跑 pour_base.py** —— 那一步会把错的状态写回去。\n' u' 先 API Gateway → 重新连接,不行就重启客户端,再数一遍。' % ', '.join(diff)) rat = run(OPEN % (B.PCB, 500) + u"return await eda.pcb_Document.getCalculatingRatlineStatus();", timeout=60).get('result') print(u'[OK] 图元与基线逐项相等;飞线计算状态 %s' % rat) sys.exit(0) sys.exit(u'!! 180 s 内 windowId 没变,重载没成功(可能被「未保存改动」挡掉,§9k)') if __name__ == '__main__': main()