# -*- coding: utf-8 -*- u"""按 `pcb_place.plan()` 的新摆位,把**改摆位之前**板上抓的焊盘表刚体变换成新焊盘表(离线,不连 EDA)。 /c/Python314/python .claude/r2/synth_pads.py # 两趟收敛,写 _blade_pads.json + _blade_pad_offsets.json 为什么要有:2026-09-11 晚主板转 180°(方案 §35)要先在服务器上试布线、比完再下发; 而 `net_peers()` / 布线器吃的都是焊盘表。改摆位前的表 + 当时的摆位在 `archive_blade_prerot_0911/`(`_blade_pads.json` / `old_pos.json`)。 变换:新焊盘 = 新原点 + R(Δrot)·(旧焊盘 − 旧原点)。Δrot 只允许 0 / 180(180 的时候顺/逆时针没区别, 底层「先镜像再转」也不影响);出现 90 / 270 直接报错 —— 那种情况底层的旋转方向要实测,不许猜。 ⚠ **合成的表只用于离线试布线**。摆位真下发之后必须 `pcb_route.py --dump` 重抓,并和这份逐脚对账 (对不上 = 旋转语义推错了,不许拿合成表下发铜)。 """ import io import json import math 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)) sys.path.insert(0, HERE) ARC = os.path.join(HERE, 'archive_blade_prerot_0911') OLD_PADS = os.path.join(ARC, '_blade_pads.json') OLD_POS = os.path.join(ARC, 'old_pos.json') OUT = os.path.join(HERE, '_blade_pads.json') OFFS = os.path.join(HERE, '_blade_pad_offsets.json') def synth(new_pos): old = json.load(io.open(OLD_PADS, encoding='utf-8')) op = json.load(io.open(OLD_POS, encoding='utf-8')) out, bad = [], [] for row in old: ref = row[0] if ref not in new_pos or ref not in op: bad.append(ref) continue ox, oy, orot = op[ref] nx, ny, nrot = new_pos[ref] d = (nrot - orot) % 360 if d % 90: raise SystemExit(u'%s 旋转变了 %d°,不是 90 的倍数' % (ref, d)) dx, dy = row[2] - ox, row[3] - oy # 方案 §25.3 实测:底层先 y 镜像、再 rot=θ 按 R(+θ)(**逆时针**)转;顶层同为逆时针。 # 镜像在旋转之前 ⇒ 改 rot 只是在已有图案上再左乘 R(+Δ),两面同一个式子。 for _k in range(d // 90): dx, dy = -dy, dx r = list(row) r[2], r[3] = round(nx + dx, 3), round(ny + dy, 3) out.append(r) return out, sorted(set(bad)) def offsets(pads, pos): o = {} for r in pads: if r[0] in pos: o.setdefault(r[0], []).append([round(r[2] - pos[r[0]][0], 4), round(r[3] - pos[r[0]][1], 4)]) return o if __name__ == '__main__': import importlib import pcb_place as PL for k in range(2): importlib.reload(PL) pos, _r, un, _sz = PL.plan() pads, bad = synth(pos) io.open(OUT, 'w', encoding='utf-8').write(json.dumps(pads, ensure_ascii=False)) io.open(OFFS, 'w', encoding='utf-8').write(json.dumps(offsets(pads, pos))) # ⚠ 每一趟都写:pcb_place.flip_2pin 要知道焊盘表是按哪一版 rot 做的(只在最后写的话第二趟会翻回去) json.dump({r: list(v) for r, v in pos.items()}, io.open(os.path.join(HERE, '_blade_place_synth.json'), 'w', encoding='utf-8'), indent=0) print(u'第 %d 趟:%d 焊盘 / %d 件;缺: %s;排不下: %s' % (k + 1, len(pads), len(pos), bad or u'无', un or u'无'))