文件
phonerail/.claude/r2/pour_src_blade1.py
T

130 行
5.6 KiB
Python

# -*- coding: utf-8 -*-
u"""BLADE-PCB_1 的铺铜边框**经文档源码**写入(2026-09-14),替代 `pcb_pour.py --push` 的 API 建法。
/c/Python314/python .claude/r2/pour_src_blade1.py # 只生成记录并打印
/c/Python314/python .claude/r2/pour_src_blade1.py --push # 写入源码 → 开铺铜管理器「重建所有」→ 确认 → 存盘
## 为什么不用 API 建
在 BLADE-PCB_1(工程里另存出来、没挂在「板子」下的 PCB)上实测:
`pcb_PrimitivePour.create()` 建出来的边框 `getAll()` 数得到,但 **`getDocumentSource()` 里一条 POUR 都没有**;
此后 `工具 → 铺铜管理器...` 点了**不弹窗、不报错**,任意**一块**这样的边框都会触发(二分到单块)。
没有边框时管理器正常。`rebuildCopperRegion()` 仍是坏的(抛 reading 'length')。
⇒ 照 `BLADE-PCB` 自己(从出板版备份还原、管理器正常)的 POUR 记录格式直接写源码。
## 源码两个坑(同 `hand_blade1` 所在会话里还原 BLADE-PCB 时踩到的)
- **最后一行不能带结尾的 `|`**,否则 `setDocumentSource` 返回 false、板面一点不变。
- path 的格式是 `[[x, y, "L", x, y, x, y, ...]]`(一个 `L` 后面跟全部后续点,mil),首尾点重复闭合。
"""
import io
import json
import os
import random
import sys
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 # noqa: E402
import pcb_blade as B # noqa: E402
import pcb_pour as P # noqa: E402
PCB = B.PCB
ORDER_ISLAND = {'VO-L3': 2, 'V12-L3': 4, 'V5-L3': 6}
def records():
out = []
per_layer = {}
rnd = random.Random(20260914)
for name, net, layer, rings, _prio in P.plan():
assert len(rings) == 1, name
src = rings[0]
nums = [v for v in src if v != 'L']
path = [nums[0], nums[1], 'L'] + nums[2:]
if name in ORDER_ISLAND:
order = ORDER_ISLAND[name]
else:
k = per_layer.get(layer, 0)
per_layer[layer] = k + 1
order = 10 + k
body = {'partitionId': '', 'groupId': 0, 'netName': net, 'layerId': layer, 'width': 0.2,
'name': name, 'order': order, 'path': [path],
'pourType': {'pourType': 'SOLID', 'fineness': 8}, 'keepIsland': False,
'locked': False, 'zIndex': -1}
pid = '%016x' % rnd.getrandbits(64)
out.append((pid, body))
return out
JS_SET = u"""
await eda.dmt_EditorControl.openDocument('%s');
await new Promise(r => setTimeout(r, 2500));
const c = await eda.dmt_SelectControl.getCurrentDocumentInfo();
if (c.uuid !== '%s') return {err: 'wrong doc ' + c.uuid};
const old = (await eda.pcb_PrimitivePour.getAll()).map(p => p.getState_PrimitiveId());
if (old.length) await eda.pcb_PrimitivePour.delete(old);
await new Promise(r => setTimeout(r, 1000));
let src = await eda.sys_FileManager.getDocumentSource();
const NL = String.fromCharCode(10);
let lines = src.split(NL).filter(l => !l.startsWith('{"type":"POUR"') && !l.startsWith('{"type":"POURED"'));
let maxT = 0;
for (const l of lines) { const m = l.match(/"ticket":(\\d+)/); if (m) maxT = Math.max(maxT, +m[1]); }
const R = %s;
lines = lines.map(l => l.endsWith('|') ? l : l + '|');
for (const [id, body] of R) { maxT++; lines.push('{"type":"POUR","ticket":' + maxT + ',"id":"' + id + '"}||' + JSON.stringify(body) + '|'); }
let s = lines.join(NL);
if (s.endsWith('|')) s = s.slice(0, -1);
const ok = await eda.sys_FileManager.setDocumentSource(s);
await new Promise(r => setTimeout(r, 3000));
const ps = await eda.pcb_PrimitivePour.getAll();
const src2 = await eda.sys_FileManager.getDocumentSource();
return {ok, deletedOld: old.length, pours: ps.length, inSource: src2.split(NL).filter(l => l.startsWith('{"type":"POUR"')).length,
names: ps.map(p => p.getState_PourName())};
"""
def main():
rec = records()
for pid, b in rec:
print(u' %-10s %-5s L%-3d order %-3d %d 点' % (b['name'], b['netName'], b['layerId'], b['order'],
(len(b['path'][0]) - 1) // 2))
if '--push' not in sys.argv:
print(u'(未加 --push,没有写入)')
return
r = run(JS_SET % (PCB, PCB, json.dumps([[pid, b] for pid, b in rec], ensure_ascii=False)), timeout=300)
res = r.get('result') or r
print(json.dumps({k: v for k, v in res.items() if k != 'names'}, ensure_ascii=False))
if not res.get('ok') or res.get('pours') != len(rec) or res.get('inSource') != len(rec):
raise SystemExit(u'!! 边框没按源码写进去')
want = {b['name'].upper() for _, b in rec}
got = {n.upper() for n in res['names']}
if want != got:
raise SystemExit(u'!! 名字对不上: 缺 %s 多 %s' % (want - got, got - want))
before = len(rec)
r = P.call(P.JS_REBUILD, timeout=120)
if r.get('err'):
raise SystemExit(u'[X] 打不开铺铜管理器: %s' % r['err'])
import time
prev, same = -1, 0
for _i in range(40):
time.sleep(5)
n = P.call(P.JS_POLL, timeout=60)['poured']
print(u' 填充 %d / %d' % (n, before))
if n >= before:
break
same = same + 1 if n == prev else 0
prev = n
if same >= 2 and n > 0:
break
fin = P.call(P.JS_DONE, timeout=120)
print(u' 收尾: %s' % fin)
if fin['pour'] < before:
raise SystemExit(u' [X] 铺铜边框从 %d 变成 %d' % (before, fin['pour']))
if __name__ == '__main__':
main()