# -*- coding: utf-8 -*- u"""下板 PCB1 板框 + 定位孔(2026-09-06 两板对齐版)。 /c/Python314/python .claude/pcb1_outline.py --push 板框 220 × 135、左下角 (−30,−5)(下板坐标原点不动,= 载板坐标 − (30,5)); 定位孔取 `pcb1_place.MH`(= 载板 `pcb2_place.MH` 减 (30,5),四角 + 两侧中点)。 原来 `pcb_place.py --outline` 干这件事,但那个单板时代的脚本在 import 时就要找 J2 之类早就删掉的位号, 跑不起来了;这里把它的 `outline_poly` / `push_outline` 抠出来独立跑(同样的多段线 + MULTI 层自由焊盘写法, 注意事项见 pcb_place.push_outline 的 docstring)。 """ import json import os import sys HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, HERE) from eda import run # noqa: E402 from pcb1_place import MH, PCB, MIL, MH_HOLE, MH_PAD # noqa: E402 # ⚠️ 不能 `from pcb_place import ...`:那个单板时代的脚本在 import 时就跑 put() 表,找不到 J2 直接 assert。 # 板框常量在这里独立维护(和 pcb_place.py 里的 OX/OY/OW/OH 同值)。 OX, OY, OW, OH = -30.0, -5.0, 220.0, 135.0 CORNER_R = 5.0 def outline_poly(): u"""板框多边形 -> TPCB_PolygonSourceArray(mil),逆时针、四角 R5、末段圆弧回到起点(抄自 pcb_place.outline_poly)。""" R = CORNER_R X0, Y0, X1, Y1 = OX, OY, OX + OW, OY + OH pts = [] L = lambda x, y: pts.append(('L', x, y)) A = lambda x, y: pts.append(('ARC', 90.0, x, y)) start = (X0 + R, Y0) L(X1 - R, Y0); A(X1, Y0 + R) L(X1, Y1 - R); A(X1 - R, Y1) L(X0 + R, Y1); A(X0, Y1 - R) L(X0, Y0 + R); A(X0 + R, Y0) src, mode = [start[0] * MIL, start[1] * MIL], None for q in pts: if q[0] == 'L': if mode != 'L': src.append('L'); mode = 'L' src += [q[1] * MIL, q[2] * MIL] else: src.append('ARC'); mode = 'ARC' src += [q[1], q[2] * MIL, q[3] * MIL] return [round(v, 4) if isinstance(v, float) else v for v in src] OPEN = ("await eda.dmt_EditorControl.openDocument('%s');" "await new Promise(r=>setTimeout(r,1500));" % PCB) def call(body, timeout=240): r = run(OPEN + body, timeout=timeout) if not r.get('success'): sys.exit(u'下发失败: ' + json.dumps(r, ensure_ascii=False)[:500]) return r['result'] def main(): if '--push' not in sys.argv: print(u'板框 %.0f×%.0f 左下角 (%.0f,%.0f);定位孔 %s(加 --push 下发)' % (OW, OH, OX, OY, MH)) return n = call(""" let del = 0; for (const l of await eda.pcb_PrimitiveLine.getAll()) if (l.getState_Layer() === 11) { await eda.pcb_PrimitiveLine.delete(l.getState_PrimitiveId()); del++; } for (const p of await eda.pcb_PrimitivePolyline.getAll()) if (p.getState_Layer() === 11) { await eda.pcb_PrimitivePolyline.delete(p.getState_PrimitiveId()); del++; } const poly = await eda.pcb_MathPolygon.createPolygon(%s); const r = await eda.pcb_PrimitivePolyline.create('', 11, poly, 5); const left = (await eda.pcb_PrimitivePolyline.getAll()).filter(p => p.layer === 11).length; return {del, made: r ? r.primitiveId : null, left}; """ % json.dumps(outline_poly())) if n['left'] != 1: sys.exit(u'!! layer 11 上有 %d 条多段线,应为 1' % n['left']) print(u'板框: %.0f × %.0f,左下角 (%.0f,%.0f),R%.0f;删旧 %d' % (OW, OH, OX, OY, CORNER_R, n['del'])) k = call(""" const MH = %s; let del = 0; for (const p of await eda.pcb_PrimitivePad.getAll()) { const num = p.getState_PadNumber(); let parent = null; try { parent = p.getState_ParentPrimitiveId ? p.getState_ParentPrimitiveId() : null; } catch (e) {} if (typeof num === 'string' && num.indexOf('MH') === 0 && !parent) { await eda.pcb_PrimitivePad.delete(p.getState_PrimitiveId()); del++; } } let k = 0; for (let i = 0; i < MH.length; i++) { await eda.pcb_PrimitivePad.create(12, 'MH' + (i + 1), MH[i][0], MH[i][1], 0, ['ELLIPSE', %.2f, %.2f], 'GND', ['ROUND', %.4f], 0, 0, 0, true); k++; } await eda.pcb_Document.save(); return {del, k}; """ % (json.dumps([[round(x * MIL, 2), round(y * MIL, 2)] for x, y in MH]), MH_PAD * MIL, MH_PAD * MIL, MH_HOLE * MIL)) print(u'定位孔: 删旧 %d / 新建 %d(Φ%.1f / 焊盘 Φ%.1f / 接 GND)' % (k['del'], k['k'], MH_HOLE, MH_PAD)) if __name__ == '__main__': main()