R2:底板 / 刀片布局布线脚本、快照、截图与规划文档更新(含外壳、打印件、布线归档)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
@@ -0,0 +1,183 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
u"""下板 PCB1:把元件的**通道ID(uniqueId)**与原理图重新对齐。
|
||||
|
||||
/c/Python314/python .claude/pcb1_uid_sync.py # 只对账,不动板
|
||||
/c/Python314/python .claude/pcb1_uid_sync.py --push # 写回 + 存盘 + 回查
|
||||
|
||||
## 修的是什么
|
||||
|
||||
「设计 → 从原理图导入变更」是**按 uniqueId 配对**原理图元件与 PCB 元件的,
|
||||
**不是按位号**。2026-09-08 实测下板上这个 ID 与原理图**整体错了一格**:
|
||||
|
||||
位号 PCB 原理图
|
||||
J26 gge418 gge419
|
||||
J27 gge419 gge420 ← 板上 J27 拿着原理图 J26 的 ID
|
||||
Q9 gge423 gge424
|
||||
R753 gge413 gge414
|
||||
|
||||
于是 EasyEDA 把**原理图的 J26 配到了板上的 J27** 头上,进而提出一整套
|
||||
「改位号 / 改封装 / 改网络」的重映射。**那份清单看着像在修错,其实会把一块
|
||||
本来正确的板改坏** —— 同期按四项(位号 / 封装 / 器件值 / 焊盘网络)逐个对账,
|
||||
风扇块 23 件全部一致。
|
||||
|
||||
错位的来源是 skill §9o:某一页被整页重画过,元件 uuid 全新,
|
||||
EasyEDA 重编 uniqueId 时和 PCB 侧对不上了。
|
||||
|
||||
## 判据(不要拿导入对话框当判据)
|
||||
|
||||
对话框是**结果**不是**原因**:它按错位的配对去算差异,算出来的东西自然也是错位的。
|
||||
真正的判据是这里做的事 —— **按位号把两边的 uniqueId 摆在一起比**。
|
||||
|
||||
## 为什么要两阶段写
|
||||
|
||||
这是一个**排列**(J27 的 ID 要给 J26、J26 的给别人),直接按目标值挨个写会在
|
||||
中途出现**两个元件同 ID**。所以先把所有要改的写成一批临时 ID(`zzsync<n>`),
|
||||
再写成目标值。
|
||||
|
||||
## ⚠️ 写之前先存盘快照
|
||||
|
||||
`_pcb1_snapshot.py` 存一份。这个脚本改的是元件身份,出错了看不出来 ——
|
||||
DRC / 几何检查 / 连通性一条都不会报,只有下次开导入对话框才发现。
|
||||
"""
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, HERE)
|
||||
from eda import run
|
||||
|
||||
PCB = '7e9415a16a909c51'
|
||||
CACHE = os.path.join(HERE, 'eda-parts.json')
|
||||
|
||||
|
||||
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 pages():
|
||||
c = json.load(io.open(CACHE, encoding='utf-8'))
|
||||
return c['pages'] # Board1 的页表就在顶层(`--board Board2` 才走 schematic_boards)
|
||||
|
||||
|
||||
JS_PCB = 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; // 离板器件不上 PCB
|
||||
o[c.getState_Designator()] = c.getState_UniqueId() || '';
|
||||
}
|
||||
return {page: cur.name, uids: o};
|
||||
"""
|
||||
|
||||
|
||||
def collect():
|
||||
pcb = call(JS_PCB)
|
||||
sch = {}
|
||||
for name, uuid in sorted(pages().items()):
|
||||
r = call(JS_SCH % uuid)
|
||||
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), 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%s' % (d, a or '(空)', b or '(空)',
|
||||
u' ← 新建件,本来就没有' if not a else ''))
|
||||
if not diff:
|
||||
print(u'\n✅ 全部一致,导入对话框应当只报真实差异。')
|
||||
return
|
||||
# 目标值有没有撞车(同一个 uniqueId 被两个位号要)
|
||||
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}
|
||||
if dup:
|
||||
sys.exit(u'⛔ 目标 uniqueId 撞车,不能自动修:%s' % dup)
|
||||
|
||||
if not push:
|
||||
print(u'\n加 --push 写回(两阶段:先临时 ID 再目标 ID,避免中途重号)')
|
||||
return
|
||||
|
||||
plan = [[d, b] for d, (_a, b) in sorted(diff.items())]
|
||||
r = call(u"""
|
||||
await eda.dmt_EditorControl.openDocument('%s');
|
||||
await new Promise(r => setTimeout(r, 2000));
|
||||
const PLAN = %s;
|
||||
const all = await eda.pcb_PrimitiveComponent.getAll();
|
||||
const by = {};
|
||||
for (const c of all) by[c.getState_Designator()] = c;
|
||||
// 第一阶段:全部写成临时 ID,解开排列里的环
|
||||
let n1 = 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++;
|
||||
}
|
||||
// 第二阶段:写成目标 ID
|
||||
let n2 = 0;
|
||||
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']))
|
||||
|
||||
# §9ad:save() 的返回值不算数,重新读一遍
|
||||
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接着开一次「从原理图导入变更」核对:p1_import_rows2.py')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
在新工单中引用
屏蔽一个用户