文件
zhou yang和Claude Opus 5 efcba30a0c R2 底板改 4 层(D15):规则 / 几何 / 背面摆位 / 4 层布线与铺铜,已下发
- 规则:铜层 2→4,差分 0.28/0.18,间距 0.15,过孔 0.3/0.2(rules_base)
- 几何:98×98 板框 + 4×M3,槽阵居中,L1 12V 大铜 + 指头(pcb_base)
- 摆位:五插槽留 L1,其余 133 件翻到 L4;背面镜像规则实测;Hub 放走廊 2、
  东移 2.5 mm;扇出圈 / 小件间距 1.5(去耦例外)/ 9 A 过孔阵禁区(place_base)
- 叠层按用户决定改为 L1 槽面 / L2 整层 +3V3 / L3 整层 GND / L4 器件面
- 布线:D± 七对按构造布通(USB3 掉头、USB1/2 北去掉头、USB4/5 东绕、
  J23 桥),+3V3 / GND / +12V 平面网每脚一孔,0.125 网格 + PathFinder
  开关(route_base、remote_route.ps1);剩 37 个网待手工补
- 铺铜:4 层重写,入口链自南向北切段 + 断面校核(pour_base);
  已下发布线 / 缝合孔 / 铜面轮廓,填铜未成功(待查)
- 工具:plot_base / plot_routes / shot_base,board_check 通孔两层都算

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_0168Bu6SEx3cXXCJLkdnE6zB
2026-09-10 21:02:10 +08:00

110 行
5.3 KiB
Python

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# -*- coding: utf-8 -*-
u"""离线画底板摆位(背面 L4,从上往下看):两种 Hub 走廊并排,给用户对比。
/c/Python314/python .claude/r2/plot_base.py <out.png> # 定稿(走廊 2)vs 草图原样(走廊 3)
/c/Python314/python .claude/r2/plot_base.py <out.png> --one # 只画定稿(走廊 2)
不碰 EasyEDA:直接调 `place_base.plan()`。背面件是**镜像后的真实本体框**(`body_rect` 已含翻面)。
"""
import importlib
import os
import sys
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, Polygon, Circle # noqa: E402
import pcb_base as B # noqa: E402
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
ZCOL = {'A1': '#f4b6a0', 'A2': '#fcdcc8', 'A3': '#c9c3e0', 'A5': '#a8d1e6',
'A6': '#cfe3ef', 'A4H': '#8fc3dd', 'A4M': '#bdb3db'}
def plan_for(hub3):
argv = sys.argv[:]
sys.argv = [a for a in argv if a != '--hub3'] + (['--hub3'] if hub3 else [])
import place_base as P
P = importlib.reload(P)
sys.argv = argv
pos, unplaced, sz, _aim = P.plan()
bad = P.selfcheck(pos, unplaced, sz)
return P, pos, sz, bad
def draw(ax, P, pos, sz, bad):
nat = P.native()
pg = P.page_of()
ax.add_patch(Polygon(B.outline_pts(), closed=True, fill=False, lw=1.2, ec='black'))
for i in range(1, B.SLOT_N + 1):
b = B.slot_body(i)
ax.add_patch(Rectangle((b[0], b[1]), b[2] - b[0], b[3] - b[1], fill=True, fc='#eeeeee',
ec='#888888', ls='--', lw=0.6, hatch='////', alpha=0.6))
ref = B.USB_SLOT_REF[B.SLOT_N + 1 - i]
ax.text(b[2] + 0.4, (b[1] + b[3]) / 2, u'%s\n(L1)' % ref, fontsize=5, color='#666666', va='center')
# D± 脚(南排 x 47.5/48.5)
for u in (-1.5, -0.5):
ax.add_patch(Rectangle((B.pin_x(u) - 0.35, B.slot_y(i) - B.PAD_ROW_DY - 1.0), 0.7, 2.0,
fc='#7b3fa0', ec='none'))
for name, r in P.LANES.items():
col = '#b58ad6' if name.startswith('DP') else ('#7cc47c' if name.startswith('I2C') else '#f0a040')
ax.add_patch(Rectangle((r[0], r[1]), r[2] - r[0], r[3] - r[1], fc=col, ec='none', alpha=0.35))
for k, z in P.ZONES.items():
ax.add_patch(Rectangle((z[0], z[1]), z[2] - z[0], z[3] - z[1], fill=False,
ec='#555555', ls=':', lw=0.6))
ax.text(z[0] + 0.3, z[3] - 0.3, k, fontsize=6, va='top', color='#333333', weight='bold')
n = B.notch_rect()
ax.add_patch(Rectangle((n[0], n[1]), n[2] - n[0], n[3] - n[1], fc='white', ec='black', hatch='xx'))
a = P.antenna_keepout(sz, pos)
ax.add_patch(Rectangle((a[0], a[1]), a[2] - a[0], a[3] - a[1], fill=False, ec='red', ls='--', lw=0.6))
for _n, (hx, hy) in B.MOUNT_HOLES.items():
ax.add_patch(Circle((hx, hy), B.HOLE_D / 2, fc='white', ec='black'))
for ref, (x, y, rot) in pos.items():
if P.layer_of(ref) != B.BOTTOM:
continue
r = P.body_rect(ref, x, y, rot, sz)
z = P.zone_of_ref(ref, pg) if ref in pg else None
ax.add_patch(Rectangle((r[0], r[1]), r[2] - r[0], r[3] - r[1], fc=ZCOL.get(z, '#dddddd'),
ec='#333333', lw=0.4))
big = (r[2] - r[0]) * (r[3] - r[1]) > 9
ax.text((r[0] + r[2]) / 2, (r[1] + r[3]) / 2, ref, fontsize=5.5 if big else 3.2,
ha='center', va='center', weight='bold' if big else 'normal')
# U32 下行口(USB1..5 的 DP 脚)
ux, uy, ur = pos['U32']
for pn, _nn, px, py in P.pads_at(nat, 'U32', ux, uy, ur):
if pn in ('2', '4', '7', '9', '12'):
ax.plot(px, py, 'o', ms=2.2, color='#7b3fa0')
ax.text(px, py + 0.5, 'USB%d' % {'2': 1, '4': 2, '7': 3, '9': 4, '12': 5}[pn],
fontsize=3.5, color='#7b3fa0', ha='center', rotation=90, va='bottom')
L = P.dp_lengths(pos, nat)
txt = u' '.join(u'USB%d %.0f' % (i, v) for i, _r, v in L)
ax.set_title(u'Hub 在走廊 %d(y %.1f~%.1f) D± 最远 ≈ %.0f mm\n%s 自检 %s'
% (P.HUB_K, P._CH[1], P._CH[3], max(v for _i, _r, v in L), txt,
u'全过' if not bad else u'%d 条不过' % len(bad)), fontsize=8)
ax.set_xlim(-1, B.BW + 1)
ax.set_ylim(-1, B.BH + 1)
ax.set_aspect('equal')
ax.grid(True, lw=0.2, alpha=0.4)
ax.tick_params(labelsize=6)
if __name__ == '__main__':
out = sys.argv[1]
variants = [False] if '--one' in sys.argv else [False, True]
fig, axes = plt.subplots(1, len(variants), figsize=(8.5 * len(variants), 9.2), dpi=170)
if len(variants) == 1:
axes = [axes]
for ax, v in zip(axes, variants):
P, pos, sz, bad = plan_for(v)
draw(ax, P, pos, sz, bad)
fig.suptitle(u'底板背面 L4 摆位(从上往下看,背面件已镜像) 紫 = D± 车道 / 绿 = I²C 车道 / 橙 = 上行·USB6 上 L1 过孔带'
u' / 斜线 = 槽体过孔带 / 红虚线 = 天线净空', fontsize=9)
fig.tight_layout()
fig.savefig(out)
print('->', out)