171 行
7.2 KiB
Python
171 行
7.2 KiB
Python
# -*- coding: utf-8 -*-
|
||
u"""R2 底板:把 PCB 元件的 uniqueId 按位号对齐到原理图(skill §9o-2,照搬 `.claude/pcb1_uid_sync.py`)。
|
||
|
||
/c/Python314/python .claude/r2/uid_sync_base.py # 只对账,不动板
|
||
/c/Python314/python .claude/r2/uid_sync_base.py --push # 两阶段写回 + 存盘 + 回查
|
||
|
||
为什么要有:A4-HUB 页整页重画(`a4_hub.py --push`)后原理图元件 uuid / uniqueId 全新,
|
||
「从原理图导入变更」按 uniqueId 配对 ⇒ 那一页 31 件全变成「移除 + 增加」(2026-09-10 实测),
|
||
应用下去整窝 Hub 会被删掉重建、摆位归零。对齐之后对话框只剩真实差异(新增件 + 改网络)。
|
||
|
||
写之前把 PCB 侧旧 uniqueId 存到 `_base_uid_pre_sync.json`(只改这一个字段,这份就是快照)。
|
||
⚠ DRC / 几何 / 连通性都看不出这一步错没错,唯一验收是重开导入对话框看剩几条。
|
||
"""
|
||
import io
|
||
import json
|
||
import os
|
||
import sys
|
||
|
||
sys.stdout.reconfigure(encoding='utf-8')
|
||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||
sys.path.insert(0, os.path.dirname(HERE))
|
||
from eda import run # noqa: E402
|
||
|
||
U = json.load(io.open(os.path.join(HERE, 'r2-uuids.json'), encoding='utf-8'))
|
||
# `--blade` 对刀片做同一件事(2026-09-11:B4-EDGE / B3B-USB 整页重画后,导入对话框同样错开一格)
|
||
BOARD = 'BLADE' if '--blade' in sys.argv else 'BASE'
|
||
PCB = U['boards'][BOARD]['pcb']['uuid']
|
||
PAGES = U['boards'][BOARD]['pages']
|
||
SNAP = os.path.join(HERE, '_%s_uid_pre_sync.json' % BOARD.lower())
|
||
|
||
# 导入对话框若还开着(`pcb_import.py --probe` 不关它),先点「取消」再动板。
|
||
CLOSE_DLG = u"""
|
||
for (const b of document.querySelectorAll('button'))
|
||
if (b.offsetParent !== null && (b.innerText||'').trim() === '取消') {
|
||
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));
|
||
await new Promise(r => setTimeout(r, 800));
|
||
}
|
||
"""
|
||
|
||
JS_PCB = CLOSE_DLG + u"""
|
||
await eda.dmt_EditorControl.openDocument('%s');
|
||
await new Promise(r => setTimeout(r, 2000));
|
||
const o = {};
|
||
for (const c of await eda.pcb_PrimitiveComponent.getAll())
|
||
o[c.getState_Designator()] = c.getState_UniqueId() || '';
|
||
return o;
|
||
""" % PCB
|
||
|
||
JS_SCH = u"""
|
||
await eda.dmt_EditorControl.openDocument('%s');
|
||
await new Promise(r => setTimeout(r, 1500));
|
||
const cur = await eda.dmt_Schematic.getCurrentSchematicPageInfo();
|
||
const o = {};
|
||
for (const c of await eda.sch_PrimitiveComponent.getAll()) {
|
||
if (c.getState_ComponentType() !== 'part') continue;
|
||
if (c.getState_AddIntoPcb && c.getState_AddIntoPcb() === false) continue;
|
||
o[c.getState_Designator()] = c.getState_UniqueId() || '';
|
||
}
|
||
return {page: cur.name, uids: o};
|
||
"""
|
||
|
||
|
||
def call(js, timeout=300):
|
||
r = run(js, timeout=timeout)
|
||
if not r.get('success'):
|
||
raise RuntimeError(json.dumps(r, ensure_ascii=False)[:1200])
|
||
return r['result']
|
||
|
||
|
||
def collect():
|
||
pcb = call(JS_PCB)
|
||
sch = {}
|
||
for name, uuid in sorted(PAGES.items()):
|
||
r = call(JS_SCH % uuid)
|
||
if str(r['page']).upper() != name.upper():
|
||
raise RuntimeError(u'openDocument 打到了 %s(要 %s)' % (r['page'], name))
|
||
for d, u in r['uids'].items():
|
||
if d in sch:
|
||
print(u' ⚠️ 位号 %s 在多页出现(%s)' % (d, r['page']))
|
||
sch[d] = u
|
||
return pcb, sch
|
||
|
||
|
||
def main():
|
||
push = '--push' in sys.argv
|
||
pcb, sch = collect()
|
||
print(u'PCB %d 件 / 原理图 %d 件' % (len(pcb), len(sch)))
|
||
only_pcb = sorted(set(pcb) - set(sch))
|
||
only_sch = sorted(set(sch) - set(pcb))
|
||
if only_pcb:
|
||
print(u'⚠️ PCB 有原理图没有 %d: %s' % (len(only_pcb), only_pcb))
|
||
if only_sch:
|
||
print(u' 原理图有 PCB 没有 %d(等导入新增): %s' % (len(only_sch), ' '.join(only_sch)))
|
||
|
||
diff = {d: (pcb[d], sch[d]) for d in sorted(set(pcb) & set(sch)) if pcb[d] != sch[d]}
|
||
print(u'\n通道ID 对不上的:%d / %d' % (len(diff), len(set(pcb) & set(sch))))
|
||
for d, (a, b) in diff.items():
|
||
print(u' %-6s PCB=%-10s 原理图=%-10s' % (d, a or '(空)', b or '(空)'))
|
||
if not diff:
|
||
print(u'\n✅ 全部一致,导入对话框应当只报真实差异。')
|
||
return
|
||
tgt = {}
|
||
for d, (_a, b) in diff.items():
|
||
tgt.setdefault(b, []).append(d)
|
||
dup = {k: v for k, v in tgt.items() if len(v) > 1 or not k}
|
||
if dup:
|
||
sys.exit(u'⛔ 目标 uniqueId 撞车 / 为空,不能自动修:%s' % dup)
|
||
# 目标值不能和板上**不改的**件撞号。
|
||
# ⚠ 「PCB 有、原理图没有」的件(导入时要删的,如 2026-09-11 刀片的 J2)不算:它们占着的号
|
||
# 正是错位那一串要用的(J2 = gge74 = J4 的目标)。同一次写入里把它们挪到 `zzdel_<位号>`,
|
||
# 原理图里不可能有这个号,导入对话框照样把它们报成「移除元件」。
|
||
keep = {pcb[d]: d for d in pcb if d not in diff and d not in only_pcb and pcb[d]}
|
||
clash = {b: (d, keep[b]) for d, (_a, b) in diff.items() if b in keep}
|
||
if clash:
|
||
sys.exit(u'⛔ 目标 uniqueId 与板上不改的件撞号:%s' % clash)
|
||
|
||
if not push:
|
||
print(u'\n加 --push 写回(两阶段:先临时 ID 再目标 ID,避免中途重号)')
|
||
return
|
||
|
||
io.open(SNAP, 'w', encoding='utf-8').write(json.dumps(pcb, ensure_ascii=False, indent=0))
|
||
print(u'已存旧 uniqueId 快照 %s' % os.path.basename(SNAP))
|
||
plan = [[d, b] for d, (_a, b) in sorted(diff.items())]
|
||
plan += [[d, 'zzdel_' + d] for d in only_pcb]
|
||
r = call(CLOSE_DLG + u"""
|
||
await eda.dmt_EditorControl.openDocument('%s');
|
||
await new Promise(r => setTimeout(r, 2000));
|
||
const PLAN = %s;
|
||
const by = {};
|
||
for (const c of await eda.pcb_PrimitiveComponent.getAll()) by[c.getState_Designator()] = c;
|
||
let n1 = 0, n2 = 0;
|
||
for (let i = 0; i < PLAN.length; i++) {
|
||
const c = by[PLAN[i][0]];
|
||
if (!c) continue;
|
||
c.toAsync().setState_UniqueId('zzsync' + i).done();
|
||
n1++;
|
||
}
|
||
for (const [des, uid] of PLAN) {
|
||
const c = by[des];
|
||
if (!c) continue;
|
||
c.toAsync().setState_UniqueId(uid).done();
|
||
n2++;
|
||
}
|
||
await eda.pcb_Document.save();
|
||
return {n1, n2};
|
||
""" % (PCB, json.dumps(plan)))
|
||
print(u'\n两阶段写入:临时 %d / 目标 %d,已存盘' % (r['n1'], r['n2']))
|
||
|
||
pcb2, sch2 = collect()
|
||
left = {d: (pcb2[d], sch2[d]) for d in sorted(set(pcb2) & set(sch2)) if pcb2[d] != sch2[d]}
|
||
if left:
|
||
print(u'⛔ 回查还有 %d 个对不上:' % len(left))
|
||
for d, (a, b) in left.items():
|
||
print(u' %-6s PCB=%-10s 原理图=%s' % (d, a or '(空)', b or '(空)'))
|
||
sys.exit(1)
|
||
seen = {}
|
||
for d, u in pcb2.items():
|
||
if u:
|
||
seen.setdefault(u, []).append(d)
|
||
dup2 = {k: v for k, v in seen.items() if len(v) > 1}
|
||
if dup2:
|
||
sys.exit(u'⛔ PCB 上出现重号:%s' % dup2)
|
||
print(u' ✔ 回查:%d 个元件的通道ID 与原理图逐个一致,且无重号' % len(pcb2))
|
||
print(u'\n接着重开导入对话框核对:pcb_import.py --base --probe')
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main()
|