R2:底板 / 刀片布局布线脚本、快照、截图与规划文档更新(含外壳、打印件、布线归档)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
@@ -0,0 +1,173 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
u"""刀片板 **1:1 打印稿**(A4 竖版 PDF + 600 dpi PNG),拿去和小米8 主板比孔位 / 侧键 / 排线槽。
|
||||
|
||||
/c/Python314/python .claude/r2/print_blade.py # 出 .claude/r2/print/blade_1to1.pdf / .png
|
||||
|
||||
- 数据全用**板上读回**:线 / 孔 / 元件 = 最终整板快照 `_blade_board_rot180_final_0911.json`,
|
||||
焊盘 = `_blade_pads.json`(下发后 --dump 的),板框 / 孔 / 槽 = `pcb_blade`(和板上折线同源)。
|
||||
- 上半张 **A 面**(手机那一面,正看):板框 + 缺口、七个孔、两条槽、手机投影、K1~K4、金手指 B 排、A 面走线(淡)。
|
||||
- 下半张 **B 面**(**从背面看、左右镜像**,和翻过来的实物一致):元件本体 + 位号、焊盘、B 面走线(淡)。
|
||||
- 两根标尺(横 80 mm / 竖 50 mm):打出来量,不是 80.0 / 50.0 就是打印缩放了。
|
||||
⚠ 打印一律「实际大小 / 100% / 不缩放」。
|
||||
"""
|
||||
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)
|
||||
import matplotlib # noqa: E402
|
||||
matplotlib.use('Agg')
|
||||
import matplotlib.pyplot as plt # noqa: E402
|
||||
from matplotlib.patches import Rectangle, Circle, Polygon # noqa: E402
|
||||
import pcb_blade as B # noqa: E402
|
||||
import pcb_place as PL # noqa: E402
|
||||
|
||||
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei', 'DejaVu Sans']
|
||||
plt.rcParams['axes.unicode_minus'] = False
|
||||
MIL = 1 / 0.0254
|
||||
OUT = os.path.join(HERE, 'print')
|
||||
SNAP = os.path.join(HERE, '_blade_board_rot180_final_0911.json')
|
||||
A4 = (210.0, 297.0) # mm
|
||||
MM = 1 / 25.4 # mm → inch
|
||||
|
||||
snap = json.load(io.open(SNAP, encoding='utf-8'))
|
||||
pads = json.load(io.open(os.path.join(HERE, '_blade_pads.json'), encoding='utf-8'))
|
||||
comps = {c[0]: (c[1] / MIL, c[2] / MIL, c[3], c[4]) for c in snap['comps']}
|
||||
sz = PL.sizes()
|
||||
|
||||
|
||||
def board_axes(fig, x0, y0, mirror=False):
|
||||
u"""在页面 (x0, y0)(mm,左下角)放一块 1:1 的板子坐标系:1 个数据单位 = 1 mm 纸。"""
|
||||
pad = 1.0
|
||||
w, h = B.BW + 2 * pad, B.BH + 2 * pad
|
||||
ax = fig.add_axes([x0 / A4[0], y0 / A4[1], w / A4[0], h / A4[1]])
|
||||
if mirror:
|
||||
ax.set_xlim(B.BW + pad, -pad) # 从背面看:左右镜像
|
||||
else:
|
||||
ax.set_xlim(-pad, B.BW + pad)
|
||||
ax.set_ylim(-pad, B.BH + pad)
|
||||
ax.set_aspect('equal')
|
||||
ax.axis('off')
|
||||
return ax
|
||||
|
||||
|
||||
def outline(ax):
|
||||
pts = B.outline_polygon()
|
||||
ax.add_patch(Polygon(pts, closed=True, fill=False, ec='k', lw=0.6))
|
||||
for (a, b, c, d) in B.mill_cuts():
|
||||
ax.add_patch(Rectangle((a, b), c - a, d - b, fc='0.75', ec='k', lw=0.4, hatch='///'))
|
||||
for hn, (hx, hy) in B.phone_holes().items():
|
||||
ax.add_patch(Circle((hx, hy), B.HOLE_D / 2, fc='k'))
|
||||
ax.add_patch(Circle((hx, hy), 1.6, fill=False, ec='k', lw=0.3, ls=':')) # M1.6 螺钉头 Ø3.2
|
||||
ax.text(hx + 2.0, hy + 1.2, hn, fontsize=5)
|
||||
# 十字线,方便对孔
|
||||
ax.plot([hx - 1.5, hx + 1.5], [hy, hy], 'w', lw=0.25)
|
||||
ax.plot([hx, hx], [hy - 1.5, hy + 1.5], 'w', lw=0.25)
|
||||
|
||||
|
||||
def tracks(ax, layer, col):
|
||||
for l in snap['lines']:
|
||||
if l[0] == layer:
|
||||
ax.plot([l[1] / MIL, l[3] / MIL], [l[2] / MIL, l[4] / MIL], color=col,
|
||||
lw=max(l[5] / MIL * 2.83, 0.2), alpha=0.35, solid_capstyle='round')
|
||||
for v in snap['vias']:
|
||||
ax.add_patch(Circle((v[0] / MIL, v[1] / MIL), (v[3] or 0.6 * MIL) / MIL / 2, fc='0.55', ec='none'))
|
||||
|
||||
|
||||
def pads_on(ax, layer):
|
||||
for p in pads:
|
||||
if p[6] != layer:
|
||||
continue
|
||||
ref, pin, x, y, w, h = p[:6]
|
||||
col = '#b8860b' if ref == 'J1' else ('#808080' if p[7] == 'GND' else '#2e7d32')
|
||||
ax.add_patch(Rectangle((x - w / 2, y - h / 2), w, h, fc=col, ec='none', alpha=0.85))
|
||||
|
||||
|
||||
def bodies(ax, side):
|
||||
for ref, (x, y, rot, lay) in comps.items():
|
||||
if ref == 'J1' or ref not in sz:
|
||||
continue
|
||||
if (lay == 2) != (side == 'B'):
|
||||
continue
|
||||
w, h = PL.wh(ref, int(rot) % 360, sz)
|
||||
ax.add_patch(Rectangle((x - w / 2, y - h / 2), w, h, fill=False, ec='#1a3fa0', lw=0.35))
|
||||
ax.text(x, y, ref, fontsize=3.2 if w * h < 6 else 4.5, ha='center', va='center', color='#1a3fa0')
|
||||
|
||||
|
||||
def ruler(fig, x0, y0, length, vertical=False):
|
||||
ax = fig.add_axes([0, 0, 1, 1], facecolor='none')
|
||||
ax.set_xlim(0, A4[0])
|
||||
ax.set_ylim(0, A4[1])
|
||||
ax.axis('off')
|
||||
if vertical:
|
||||
ax.plot([x0, x0], [y0, y0 + length], 'k', lw=0.6)
|
||||
for k in range(0, int(length) + 1):
|
||||
t = 2.0 if k % 10 == 0 else (1.2 if k % 5 == 0 else 0.6)
|
||||
ax.plot([x0, x0 + t], [y0 + k, y0 + k], 'k', lw=0.25)
|
||||
ax.text(x0 + 3, y0 + length / 2, u'%d mm' % length, fontsize=6, rotation=90, va='center')
|
||||
else:
|
||||
ax.plot([x0, x0 + length], [y0, y0], 'k', lw=0.6)
|
||||
for k in range(0, int(length) + 1):
|
||||
t = 2.0 if k % 10 == 0 else (1.2 if k % 5 == 0 else 0.6)
|
||||
ax.plot([x0 + k, x0 + k], [y0, y0 + t], 'k', lw=0.25)
|
||||
ax.text(x0 + length / 2, y0 - 3.5, u'%d mm(打出来量这根,应为 %.1f)' % (length, length),
|
||||
fontsize=6, ha='center')
|
||||
return ax
|
||||
|
||||
|
||||
def main():
|
||||
os.makedirs(OUT, exist_ok=True)
|
||||
fig = plt.figure(figsize=(A4[0] * MM, A4[1] * MM))
|
||||
# 整页坐标轴放文字
|
||||
txt = fig.add_axes([0, 0, 1, 1], facecolor='none')
|
||||
txt.set_xlim(0, A4[0])
|
||||
txt.set_ylim(0, A4[1])
|
||||
txt.axis('off')
|
||||
left = (A4[0] - B.BW) / 2.0
|
||||
ya, yb = 196.0, 88.0 # 两块板的下沿(mm)
|
||||
txt.text(A4[0] / 2, 287, u'PhoneRail 刀片 90 × 68 · 1:1 打印稿(2026-09-11,主板转 180° 版)',
|
||||
fontsize=9, ha='center', weight='bold')
|
||||
txt.text(A4[0] / 2, 281, u'打印选「实际大小 / 100% / 不缩放」。先量下方标尺:横 80.0 mm、竖 50.0 mm,不对就是缩放了。',
|
||||
fontsize=6.5, ha='center')
|
||||
txt.text(left, ya + B.BH + 4, u'A 面(手机那一面,正看):板框 / 缺口 / 七个定位孔 Ø1.8(虚线圈 = 螺钉头 Ø3.2)/ 两条排线槽 / 手机投影(虚线)/ K1~K4',
|
||||
fontsize=5.5)
|
||||
txt.text(left, yb + B.BH + 4, u'B 面(器件面,从背面看、左右镜像 —— 和把板子翻过来看到的一致)',
|
||||
fontsize=5.5)
|
||||
|
||||
# ── A 面 ──
|
||||
ax = board_axes(fig, left - 1.0, ya - 1.0)
|
||||
outline(ax)
|
||||
px0, py0 = B.PHONE_ORG
|
||||
ax.add_patch(Rectangle((px0, py0), B.PHONE_WH[0], B.PHONE_WH[1], fill=False, ec='#c62828', lw=0.5, ls='--'))
|
||||
ax.text(px0 + 1, py0 + B.PHONE_WH[1] - 3, u'小米8 主板投影(接口边朝上)', fontsize=5, color='#c62828')
|
||||
tracks(ax, 1, '#d32f2f')
|
||||
pads_on(ax, 1)
|
||||
for ref in ('K1', 'K2', 'K3', 'K4'):
|
||||
x, y = comps[ref][:2]
|
||||
ax.text(x + 1.3, y, ref, fontsize=4, color='#c62828', va='center')
|
||||
ax.text(45, -0.2, 'J1', fontsize=5, ha='center', va='top')
|
||||
|
||||
# ── B 面(镜像)──
|
||||
bx = board_axes(fig, left - 1.0, yb - 1.0, mirror=True)
|
||||
outline(bx)
|
||||
tracks(bx, 2, '#1565c0')
|
||||
pads_on(bx, 2)
|
||||
bodies(bx, 'B')
|
||||
|
||||
ruler(fig, left, 40.0, 80)
|
||||
ruler(fig, left - 8.0, 20.0, 50, vertical=True)
|
||||
txt.text(left + 20, 20, u'文件:.claude/r2/print/blade_1to1.pdf(print_blade.py 生成,数据 = 板上读回)', fontsize=5)
|
||||
pdf = os.path.join(OUT, 'blade_1to1.pdf')
|
||||
png = os.path.join(OUT, 'blade_1to1.png')
|
||||
fig.savefig(pdf) # ⚠ 不许 bbox_inches='tight':那会裁页面、改比例
|
||||
fig.savefig(png, dpi=600)
|
||||
print(u'已出 %s / %s(A4 %.0f×%.0f mm,板子 1:1)' % (pdf, png, A4[0], A4[1]))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
在新工单中引用
屏蔽一个用户