文件

174 行
7.7 KiB
Python

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# -*- 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()