174 行
7.7 KiB
Python
174 行
7.7 KiB
Python
# -*- 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()
|