# -*- 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()