# -*- coding: utf-8 -*- u"""离线画底板布线缓存(L4 / L1 并排,从上往下看),失败的脚标红叉。 /c/Python314/python .claude/r2/plot_routes.py /c/Python314/python .claude/r2/plot_routes.py --zoom 30 25 65 50 # 只看一块(mm) 只读 `_base_routes.json` + `_base_pads.json`,不碰 EasyEDA、不跑 A*。 """ import io import json import os import re 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 POWER = ('VIN_RAW', 'VIN_FUSED', 'VIN_SW', '+5V', '+3V3', '+3V3_HUB', 'FAN_12V_SW') def color(net): if net == 'GND': return '#2e9e44' if net == '+12V': return '#d62728' if net.endswith('_DP') or net.endswith('_DM') or net in ('UP_DP', 'UP_DM'): return '#8e44ad' if net in POWER: return '#ff8c00' return '#1f5fbf' def main(): out = sys.argv[1] zoom = None if '--zoom' in sys.argv: k = sys.argv.index('--zoom') zoom = [float(v) for v in sys.argv[k + 1:k + 5]] c = json.load(io.open(os.path.join(HERE, '_base_routes.json'), encoding='utf-8')) pads = json.load(io.open(os.path.join(HERE, '_base_pads.json'), encoding='utf-8')) pads = [p for p in pads if p[4] >= 0.05 and p[5] >= 0.05] fail = set() for m in c['logs']: for ref, pin in re.findall(r'([A-Z]+[0-9]+[a-z]*)\.([A-Za-z0-9#]+)', m): fail.add(('J1' if ref == 'J1dc' else ref, pin)) fig, axes = plt.subplots(1, 2, figsize=(19, 9.8), dpi=150) for ax, lay, title in ((axes[0], 2, u'L4 器件面'), (axes[1], 1, u'L1 插槽面')): ax.add_patch(Polygon(B.outline_pts(), closed=True, fill=False, lw=1.0, ec='black')) if lay == 1: for r in B.l1_12v_rects(): ax.add_patch(Rectangle((r[0], r[1]), r[2] - r[0], r[3] - r[1], fc='#f7c6c6', ec='none', alpha=0.6)) for p in pads: if p[6] not in (lay, 12): continue ax.add_patch(Rectangle((p[2] - p[4] / 2, p[3] - p[5] / 2), p[4], p[5], fc='#bbbbbb' if p[6] == lay else '#dddddd', ec='none')) for s in c['segs']: if s[0] != lay: continue lw = max(0.3, s[5] * (3.0 if zoom else 1.2)) ax.plot([s[1], s[3]], [s[2], s[4]], color=color(s[6]), lw=lw, solid_capstyle='round') for v in c['vias']: ax.add_patch(Circle((v[0], v[1]), 0.3, fc=color(v[2]), ec='black', lw=0.2)) for p in pads: if (p[0], str(p[1])) in fail and p[6] in (lay, 12): ax.plot(p[2], p[3], 'x', color='red', ms=6 if zoom else 3, mew=1.2) for _n, (hx, hy) in B.MOUNT_HOLES.items(): ax.add_patch(Circle((hx, hy), B.HOLE_D / 2, fc='white', ec='black')) if zoom: ax.set_xlim(zoom[0], zoom[2]) ax.set_ylim(zoom[1], zoom[3]) # 放大时标出焊盘的「位号.脚」,失败的那几只一眼能认 for p in pads: if p[6] in (lay, 12) and zoom[0] <= p[2] <= zoom[2] and zoom[1] <= p[3] <= zoom[3]: if p[0].startswith('J3') and p[7] in ('GND', ''): continue ax.text(p[2], p[3], '%s.%s' % (p[0], p[1]), fontsize=3.2, ha='center', va='center', color='red' if (p[0], str(p[1])) in fail else '#333333') else: ax.set_xlim(-1, B.BW + 1) ax.set_ylim(-1, B.BH + 1) ax.set_aspect('equal') ax.set_title(title, fontsize=10) ax.tick_params(labelsize=6) ax.grid(True, lw=0.2, alpha=0.4) fig.suptitle(u'底板布线缓存:%d 段 / %d 孔 / %d 条未完成 紫 = D± 红 = +12V 绿 = GND 橙 = 电源 蓝 = 信号 红叉 = 失败的脚' % (len(c['segs']), len(c['vias']), len(c['logs'])), fontsize=10) fig.tight_layout() fig.savefig(out) print('->', out) if __name__ == '__main__': main()