R2:底板 / 刀片布局布线脚本、快照、截图与规划文档更新(含外壳、打印件、布线归档)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
@@ -0,0 +1,130 @@
|
||||
# -*- 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()
|
||||
在新工单中引用
屏蔽一个用户