master
John Lauer Add native thermal-pour expansion experiment, finite-difference comparison and recording scripts 33c4ac5 1mo ago
import json,numpy as np
from pathlib import Path
import matplotlib;matplotlib.use('Agg')
import matplotlib.pyplot as plt
P=Path(__file__).resolve().parent;E=P/'edit';d=json.loads((P/'thermal-results.json').read_text());ns=['VAC1','VBUS','VAC2','VBAT'];colors=['#edb659','#50c9d6','#9ba5ff','#e48fa9'];bg='#101925'
plt.rcParams.update({'figure.facecolor':bg,'axes.facecolor':bg,'text.color':'#eef4f9','axes.labelcolor':'#d2dfeb','xtick.color':'#d2dfeb','ytick.color':'#d2dfeb','axes.edgecolor':'#465365','font.size':19,'savefig.facecolor':bg})
def fig():return plt.subplots(figsize=(14.2,8.6),dpi=100)
def done(f,n):f.tight_layout(pad=2);f.savefig(E/(n+'.png'));plt.close(f)
f,a=fig();x=np.arange(4);a.bar(x-.18,[d['boards']['before'][n]['areaMm2'] for n in ns],.36,label='Before',color='#536a83');bars=a.bar(x+.18,[d['boards']['expanded'][n]['areaMm2'] for n in ns],.36,label='Expanded',color=colors)
a.bar_label(bars,fmt='%.1f',padding=8);a.set_xticks(x,ns);a.set_ylim(0,285);a.set_ylabel('Connected bottom copper (mm²)');a.set_title('126.7 → 765.7 mm² on the four MOSFET nets',pad=28);a.legend();a.text(.02,.93,'Q2 + Q4 share VBUS • drill voids subtracted',transform=a.transAxes,color='#b6c7d8');done(f,'area')
f,a=fig()
for n in ns:
 z=np.load(P/f'expanded-{n}-temperature.npz');v=z['temperatureRiseC'];x0,y0,x1,y1=z['extent'];c=a.imshow(25+v,origin='lower',extent=(x0,x1,y0,y1),vmin=25,vmax=65,cmap='inferno',interpolation='nearest')
for n,x,y in [('VAC1',15,23),('VBUS',17,13),('VAC2',15,4),('VBAT',45.5,18)]:a.text(x,y,n,ha='center',color='white',fontsize=22,bbox={'facecolor':'#172333','alpha':.8,'pad':4})
a.set_xlim(0,50.4);a.set_ylim(0,26.4);a.set_xlabel('Board x (mm)');a.set_ylabel('Board y (mm)');a.set_title('Modeled expanded bottom copper: source held at 65°C',pad=25);f.colorbar(c,ax=a,label='Copper temperature (°C)',shrink=.7);done(f,'model')
f,a=fig();ks=['masked','halfBrightApprox','bright','oxidized'];labels=['Solder mask','Half bright*','Bright exposed','Oxidized exposed'];ys=[1000*sum(d['boards']['expanded'][n][k]['bottomHeatW'] for n in ns) for k in ks];bars=a.bar(labels,ys,color=['#50c9d6','#8298b0','#edb659','#a2b582']);a.bar_label(bars,fmt='%.0f mW',padding=10);a.set_ylim(0,355);a.set_ylabel('Modeled bottom heat flow (mW)');a.set_title('Keep mask for the assumed air-cooled case',pad=24);a.text(.02,.93,'Same geometry • 65°C source / 25°C surroundings • h = 5 W/m²K',transform=a.transAxes,fontsize=16);a.text(.02,.02,'*Area-weighted finish approximation; not a manufactured mask window',transform=a.transAxes,fontsize=15);done(f,'mask')
f,a=fig();ks=['h3','masked','h10'];x=np.arange(3)
for state,col,offset,label in [('before','#536a83',-.18,'Before'),('expanded','#50c9d6',.18,'Expanded')]:
 bars=a.bar(x+offset,[1000*sum(d['boards'][state][n][k]['bottomHeatW'] for n in ns) for k in ks],.36,color=col,label=label);a.bar_label(bars,fmt='%.0f',padding=6)
a.set_xticks(x,['h = 3','h = 5','h = 10']);a.set_ylabel('MOSFET regions: bottom-only heat flow (mW)');a.set_xlabel('Assumed convection coefficient (W/m²K)');a.set_ylim(0,480);a.set_title('Sensitivity, not a board power rating',pad=25);a.legend();a.text(.02,.94,'Package, vias, FR4, other layers and enclosure are not modeled',transform=a.transAxes,fontsize=16);done(f,'limits')
print('Charts written')