← Commit history
demo/routing/bq25792/thermal-expansion/README.mdadded+72
@@ -0,0 +1,72 @@+# Expanded thermal copper in Fusion — video 11++Astra expands the connected bottom heatsinks from video 10 while preserving every routed segment, via, component and layer. It reallocates available copper among VAC1, VBUS, VAC2, VBAT and PGND, then records native polygon creation and a highlighted tour of each net in Fusion on CRR. This is a constrained thermal-area experiment, not a proven globally optimal layout or a new autorouting run.++## Result on the actual saved replay++| Thermal net | Before (mm²) | Expanded (mm²) | Before model (mW) | Expanded model (mW) |+| --- | ---: | ---: | ---: | ---: |+| VAC1 | 28.1 | 172.8 | 12.1 | 54.0 |+| VBUS | 44.6 | 205.6 | 19.3 | 78.0 |+| VAC2 | 29.5 | 151.8 | 12.7 | 57.2 |+| VBAT | 24.4 | 235.4 | 10.4 | 94.2 |++Q1 uses VAC1; Q2/Q4 share VBUS; Q3 uses VAC2; Q5 uses VBAT. The four regions total **126.7 → 765.7 mm²**, about **6.0×** the connected bottom area. Each FET retains its seven footprint thermal holes. Q2/Q4 share one region and are counted once. Thermal-pad net assignments are taken from the board, not inferred from the word “heatsink.”++The left notch reserves ground beneath U2, and the central ground corridor serves IC1. The bottom PGND area connected to their five added thermal vias falls from **928.0 to 275.2 mm²**. This is a real allocation tradeoff: the model's corresponding bottom ground heat path falls from **231 to 80 mW** at the same imposed source temperature. Both internal ground pours remain unchanged. A load-weighted whole-board analysis could choose a different allocation; without actual component losses, the selected boundaries are an engineering candidate, not a universal best design.++Total bottom copper coverage is approximately **85.4% → 83.8%** because the larger net partitions need isolation boundaries. Top and internal coverage stay unchanged. This is six times the useful MOSFET-connected area, not six times more metal, board cooling, or allowable current. SW1/SW2 geometry remains unchanged; enlarging high-dv/dt switching-node copper indiscriminately is inappropriate.++**Native validation:** 576 segments, 89 vias, 12 pours; zero off-angle segments; zero airwires; zero warnings; zero new DRC errors. One inherited top-layer copper clearance error remains, signature `5,1,f10227fd41e0ab5d`. The inherited diagnostic same-net rule profile matches video 10; no additional rules were disabled. A first candidate with touching same-rank polygon boundaries produced two new clearance errors and was rejected. The accepted boundaries have separation, including 0.30 mm between the horizontal pour outlines. Native fill uses the existing clearance rules and 0.20 mm board-edge clearance.++The replay adds four larger polygons to the original eight; overlaps on the same net union to the same filled geometry as the eight-polygon analytical candidate. Exported connected regions agree within 0.0001 mm², and the route/via comparison is unchanged at six-decimal coordinate precision.++## How the thermal estimate works++The table is an illustrative **bottom-surface heat-path comparison**, not measured heat and not a junction-temperature prediction. No actual operating current, ambient/enclosure or airflow was supplied for this run. The model uses:++- 35 µm copper, from the board's `mtCopper` setting; assumed conductivity 390 W/(m·K).+- Bottom copper around the thermal holes held at 65°C; ambient air and radiative surroundings at 25°C. This is a boundary condition, not a measured device temperature.+- Convection coefficient 5 W/(m²·K), with sensitivity at 3 and 10.+- Provisional mask thickness 25 µm and conductivity 0.2 W/(m·K), to be replaced by the selected mask data.+- Emissivity 0.8 for mask, 0.05 for bright copper, 0.8 for oxidized copper, using [Analog Devices' SOAtherm reference values](https://www.analog.com/en/resources/technical-articles/ltspice-soatherm-support-for-pcb-and-heat-sink-thermal-models.html). Real plating, oxidation, roughness and view factors change these values.++A 0.10 mm finite-difference grid follows the actual connected fill contours, pads and tracks. Adjacent cells conduct through the 35 µm copper sheet with per-square conductance `k × thickness`. Surface loss is calculated with convection and Stefan–Boltzmann radiation:++```+Qconv = h A (Ts - Ta)+Qrad  = epsilon sigma A (Ts^4 - Ta^4)       [temperatures in kelvin]+hr    = epsilon sigma (Ts + Ta)(Ts^2 + Ta^2)+U     = 1 / (1/(h + hr) + mask_thickness / mask_conductivity)+```++The solver iterates radiation with local copper temperature and integrates loss across the sheet. The thin mask film uses an approximate series resistance; ignoring that small correction changes little under these air-cooling assumptions. The 0.15 mm comparison grid changes expanded masked results by at most about 3.2%. Energy balance is checked, along with an analytical isothermal-sheet limit and an equal-area narrow-neck case. This is numerical checking, not physical validation. Fine copper connections, round pads and drill shapes are rasterized/approximated.++At the stated conditions, the MOSFET regions' bottom path grows from **0.0546 to 0.2834 W**, an estimated **0.229 W** difference. Varying convection from 3 to 10 gives **0.236–0.397 W** for the expanded regions. These watts must not be added directly to an existing board power rating: package and via resistance, FR4 coupling, other copper layers, enclosure, neighboring component heat and actual simultaneous loads are omitted. The source-temperature constraint also assumes the thermal holes can deliver the needed heat; their resistance has not been declared zero in the real board.++## Why the selected version keeps solder mask++For the same expanded geometry and nominal boundary conditions:++| Finish model | MOSFET-region bottom heat flow |+| --- | ---: |+| Masked, epsilon 0.8 | 0.283 W |+| Half bright exposed, area-weighted approximation | 0.218 W |+| Fully bright exposed, epsilon 0.05 | 0.150 W |+| Oxidized exposed, epsilon 0.8 | 0.284 W |++The half-exposed case is an area-weighted emissivity illustration, not a shaped manufacturing mask opening or an actual ablation of the mask. Mask removal adds almost no surface area and the assumed thin coating contributes little resistance to still-air convection. Bright copper's lower radiation outweighs that small benefit. **Keep the existing mask for this assumed air-cooled configuration.** No large solder-mask openings were added. Oxidized copper's modeled similarity is not a reason to rely on uncontrolled oxidation. Real board pads already have manufacturing openings; the finish sweep idealizes the spreading region as a uniform surface.++An electrically appropriate heatsink or chassis interface is a different case: compare contact area, interface resistance, electrical isolation and corrosion/assembly requirements explicitly. Do not expose a VAC/VBUS/VBAT region for contact without designing that interface. The [CSD17581Q3A datasheet](https://www.ti.com/lit/gpn/CSD17581Q3A) also ties its thermal resistance examples to specified copper area and thickness; its test-board theta-JA is not this board's thermal resistance.++## Reproduce and inspect++Install `adom/codex`, read `codex-adom-electrical-routing` and `codex-adom-routing-video`, and select your own live desktop. Preserve the previous board and pinned bridge. Source scripts and checks are on the `adom/fusion-bridge` branch `feature/per-net-trace-routing`, under `demo/routing/bq25792/thermal-expansion/`. Configure the adjacent `thermal/crr.example.py` transport for your discovered target; do not reuse this session's machine names, IDs or paths blindly.++Start with the video-10 saved board. Inventory component thermal nets and ground requirements; establish the load contract; propose allocation boundaries; native-refill each candidate; reject new DRC errors; measure actual connected copper; compare finish and heat-flow assumptions. For the exact historical replay, use the published board and `expansion-03/bounds.json`. For a fresh Astra design, recalculate boundaries using the actual component losses and fabrication contract.++The recorded operations use `fusion_open_board`, `fusion_run_modeling_script`, `Electron.run POLYGON`, `CHANGE ISOLATE`, `CHANGE THERMALS`, `CHANGE ORPHANS`, `CHANGE RANK`, `RATSNEST`, `DISPLAY`, `WINDOW`, and `SHOW <net>`. Snapshot/readback and `routing_drc` use the bridge's routing helper inside `fusion_run_modeling_script`; they are not invented top-level verbs. `Document.CopyToDesktop` saves the native FBRD. Adom Bridge's `desktop_record_window_start/stop` records the actual Fusion window. A terminal `SHOW` without a trailing semicolon keeps the net highlighted for the camera; verify its pixels after any snapshot or export, which can clear selection. [Autodesk documents SHOW highlighting](https://help.autodesk.com/cloudhelp/ENU/Fusion-ECAD/files/ECD-CLI-S.htm).++The video uses 3× action, removed waits, explicit inspection holds, postproduction numerical charts, and Adom TTS narration. Raw footage and edit timing remain in the workspace. Shared skills teach the general method; this worked example's figures are not acceptance targets for other boards.++[Native FBRD](https://wiki.adom.inc/api/pages/adom/codex/files/boards/fusion/bq25792-expanded-thermal.fbrd) · [Exported BRD](https://wiki.adom.inc/api/pages/adom/codex/files/boards/fusion/bq25792-expanded-thermal.brd) · [Numerical results](https://wiki.adom.inc/api/pages/adom/codex/files/docs/fusion-thermal-expansion/thermal-results.json) · [Native audit](https://wiki.adom.inc/api/pages/adom/codex/files/docs/fusion-thermal-expansion/audit.json)
demo/routing/bq25792/thermal-expansion/analyze.pyadded+49
@@ -0,0 +1,49 @@+"""Connected copper measurement with drill subtraction and no polygon-hole shadowing."""+import sys,json,xml.etree.ElementTree as ET+from pathlib import Path+from collections import defaultdict+from shapely.geometry import Polygon,Point,LineString+from shapely.ops import unary_union+from shapely import make_valid+P=Path(__file__).resolve().parent;sys.path.insert(0,str(P.parent));from geometry import Geometry+M=Geometry();NETS={'VAC1':['Q1'],'VBUS':['Q2','Q4'],'VAC2':['Q3'],'VBAT':['Q5']}+def ring(e):+ v=e.findall('vertex');assert v and not any(float(x.get('curve','0')) for x in v)+ return [(float(x.get('x')),float(x.get('y'))) for x in v]+def copper(file,layer=16):+ b=ET.parse(file).find('drawing/board');cu=defaultdict(list)+ drill_voids=[Point(at).buffer(r,quad_segs=32) for at,r in M.holes]+ for p in M.pads.values():+  if layer in p['layers']:cu[p['net']].append(p['shape'])+ for s in b.findall('signals/signal'):+  n=s.get('name')+  for v in s.findall('via'):+   at=Point(float(v.get('x')),float(v.get('y')));dr=float(v.get('drill'));dia=float(v.get('diameter'))+   drill_voids.append(at.buffer(dr/2,quad_segs=32))+   if v.get('extent')=='1-16':cu[n].append(at.buffer(dia/2,quad_segs=32))+  for w in s.findall('wire'):+   if int(w.get('layer'))!=layer:continue+   assert not float(w.get('curve','0'))+   cu[n].append(LineString([(float(w.get('x1')),float(w.get('y1'))),(float(w.get('x2')),float(w.get('y2')))]).buffer(float(w.get('width'))/2,quad_segs=32))+  for p in s.findall('polygonpour'):+   if int(p.get('layer'))!=layer:continue+   shapes=p.findall('polygonfilldetails/polygonshape');assert shapes,'Refilled contours required'+   for shape in shapes:+    outer=shape.find('polygonoutlinesegments')+    if outer is None:outer=shape.find('polygonoutlineobjects')+    if outer is None:continue+    polygon_holes=[ring(h) for h in shape.findall('polygonholelist/polygonholesegments')]+    cu[n].append(make_valid(Polygon(ring(outer),polygon_holes)))+ drill=unary_union(drill_voids)+ return {n:unary_union(gs).difference(drill) for n,gs in cu.items()}+def regions(file):+ cu=copper(file);out={}+ for net,qs in NETS.items():+  pads=[p for p in M.pads.values() if any(p['name'].startswith(q+'.P$') for q in qs)]+  assert {p['net'] for p in pads}=={net}+  u=cu[net];parts=list(u.geoms) if hasattr(u,'geoms') else [u]+  g=unary_union([g for g in parts if any(g.intersects(p['shape']) for p in pads)])+  out[net]={'geometry':g,'sources':unary_union([p['shape'] for p in pads]),'components':qs,'areaMm2':g.area,'allNetAreaMm2':u.area,'bounds':list(g.bounds)}+ return out+if __name__=='__main__':+ f=Path(sys.argv[1]);r=regions(f);data={n:{k:v for k,v in x.items() if k not in ['geometry','sources']} for n,x in r.items()};print(json.dumps(data,indent=2));f.with_suffix('.areas.json').write_text(json.dumps(data,indent=2))
demo/routing/bq25792/thermal-expansion/audit.jsonadded+49
@@ -0,0 +1,49 @@+{+  "segments": 576,+  "vias": 89,+  "pours": 12,+  "originalRoutesUnchanged": true,+  "matchesAnalyzedCandidate": true,+  "offAngleCount": 0,+  "drc": {+    "airwireCount": 0,+    "checked": true,+    "clean": false,+    "errorCount": 1,+    "errors": [+      {+        "at": [+          23.214006,+          1.914+        ],+        "code": 5,+        "description": "Copper Clearance",+        "layer": 1,+        "signature": "5,1,f10227fd41e0ab5d",+        "state": 0,+        "type": 2+      }+    ],+    "source": "Autodesk DRC + UL_BOARD.errors",+    "typeConstants": {+      "consistency": 3,+      "error": 2,+      "warning": 1+    },+    "warningCount": 0+  },+  "estimatedCoveragePercentByLayer": {+    "before": {+      "1": 76.93535483056763,+      "2": 84.61761068064153,+      "15": 83.29171033470635,+      "16": 85.38687735266052+    },+    "after": {+      "1": 76.93535483056763,+      "2": 84.61761068064153,+      "15": 83.29171033470635,+      "16": 83.8230499802942+    }+  }+}\ No newline at end of file
demo/routing/bq25792/thermal-expansion/audit.pyadded+34
@@ -0,0 +1,34 @@+import json,math,xml.etree.ElementTree as ET+from collections import Counter+from shapely.geometry import box+from shapely.ops import unary_union+from analyze import P,regions,copper+base=P.parent/'thermal/recording-02/bq25792-astra-thermal-routed.brd';final=P/'recording/bq25792-expanded-thermal.brd';candidate=P/'expansion-03/expansion-03-filled.brd'+def primitives(f,kind):+ b=ET.parse(f).find('drawing/board');out=[]+ attrs=['x1','y1','x2','y2','width','layer','curve'] if kind=='wire' else ['x','y','diameter','drill','extent']+ for s in b.findall('signals/signal'):+  for e in s.findall(kind):+   values=[]+   for k in attrs:+    v=e.get(k,'0')+    try:v=round(float(v),6)+    except ValueError:pass+    values.append(v)+   out.append((s.get('name'),*values))+ return Counter(out)+for kind in ['wire','via']:assert primitives(base,kind)==primitives(final,kind),kind+for n,r in regions(final).items():assert r['geometry'].symmetric_difference(regions(candidate)[n]['geometry']).area<1e-4,n+b=ET.parse(final).find('drawing/board');off=[]+for s in b.findall('signals/signal'):+ for w in s.findall('wire'):+  a=math.degrees(math.atan2(float(w.get('y2'))-float(w.get('y1')),float(w.get('x2'))-float(w.get('x1'))))%180+  if abs(a-round(a/45)*45)>.02:off.append(w.attrib)+assert not off+coverage={}+for tag,f in [('before',base),('after',final)]:+ coverage[tag]={str(l):unary_union(list(copper(f,l).values())).intersection(box(0,0,50.4,26.4)).area/(50.4*26.4)*100 for l in [1,2,15,16]}+drc=json.loads((P/'recording/final-drc.json').read_text())['drc']+assert drc['airwireCount']==drc['warningCount']==0 and [e['signature'] for e in drc['errors']]==['5,1,f10227fd41e0ab5d']+r={'segments':sum(primitives(final,'wire').values()),'vias':sum(primitives(final,'via').values()),'pours':len(b.findall('signals/signal/polygonpour')),'originalRoutesUnchanged':True,'matchesAnalyzedCandidate':True,'offAngleCount':0,'drc':drc,'estimatedCoveragePercentByLayer':coverage}+(P/'audit.json').write_text(json.dumps(r,indent=2));print(json.dumps(r,indent=2))
demo/routing/bq25792/thermal-expansion/candidate.pyadded+37
@@ -0,0 +1,37 @@+"""Native-refilled expansion candidates; original video-10 board remains untouched."""+import sys,json,xml.etree.ElementTree as ET+from pathlib import Path+P=Path(__file__).resolve().parent+sys.path.insert(0,str(P.parent/'thermal'))+from crr import call,execute+BOUNDS={'VAC1':(0,18.7,23,26.4),'VBUS':(0,7.7,23,18.7),'VAC2':(0,0,23,7.7),'VBAT':(40.8,0,50.4,26.4)}+def build(name,bounds):+ out=P/name;out.mkdir(exist_ok=False)+ t=ET.parse(P.parent/'thermal/recording-02/bq25792-astra-thermal-routed.brd');b=t.find('drawing/board')+ for s in b.findall('signals/signal'):+  for p in s.findall('polygonpour'):+   for e in p.findall('polygonfilldetails'):p.remove(e)+   if s.get('name') not in bounds or p.get('layer')!='16':continue+   x,y,z,w=bounds[s.get('name')];o=p.find('polygonoutlineobjects')+   for v in list(o):o.remove(v)+   for xx,yy in [(x,y),(z,y),(z,w),(x,w)]:ET.SubElement(o,'vertex',{'x':str(xx),'y':str(yy)})+   p.set('orphans','no')+ f=out/(name+'.brd');t.write(f,encoding='utf-8',xml_declaration=True)+ (out/'bounds.json').write_text(json.dumps(bounds,indent=2))+ r=call('send_files',{'filePaths':[str(f)],'dest':'C:/Users/john/AppData/Local/Temp/adom-thermal-expansion','reason':'Validate expanded heatsinking on a separate copy of the Fusion demo board'});assert r.get('success'),r+ r=call('fusion_open_board',{'filePath':r['destinationPaths'][0]});assert r.get('success'),r+ r=execute(name,"app.executeTextCommand('Electron.run RATSNEST;')\ns=handle(app,{'expectDocument':target.name},'routing_state')\nresult=handle(app,{'expectDocument':target.name,'boardId':s['boardId'],'expectedRevision':s['revision']},'routing_drc')")+ (out/'native-drc.json').write_text(json.dumps(r,indent=2));print(name,r['drc'],flush=True)+ host='C:/Users/john/AppData/Local/Temp/adom-thermal-expansion/'+name+'-filled.brd'+ r=execute(name,"root,b,m=snapshot(app,{'expectDocument':target.name})\np=Path("+repr(host)+")\nET.ElementTree(root).write(p,encoding='utf-8',xml_declaration=True)\nresult={'path':p.as_posix()}")+ r=call('pull_file',{'filePaths':[host],'saveTo':str(out)});assert r.get('success'),r+ + # Current AB mesh pull lands on the local Windows host. Normalize into this container.+ import shutil+ filled=out/(name+'-filled.brd')+ if not filled.exists():+  src=Path('/mnt/c')/str(out).lstrip('/')/filled.name+  assert src.exists(),r+  shutil.copy2(src,filled)+ return out+if __name__=='__main__':build('expansion-01',BOUNDS)
demo/routing/bq25792/thermal-expansion/charts.pyadded+21
@@ -0,0 +1,21 @@+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')
demo/routing/bq25792/thermal-expansion/expansion-03/bounds.jsonadded+26
@@ -0,0 +1,26 @@+{+  "VAC1": [+    0,+    18.85,+    29,+    26.4+  ],+  "VBUS": [+    5,+    7.85,+    29,+    18.55+  ],+  "VAC2": [+    0,+    0,+    29,+    7.55+  ],+  "VBAT": [+    40.8,+    0,+    50.4,+    26.4+  ]+}\ No newline at end of file
demo/routing/bq25792/thermal-expansion/narration.pyadded+21
@@ -0,0 +1,21 @@+import json,subprocess+from pathlib import Path+P=Path(__file__).resolve().parent;E=P/'edit';E.mkdir(exist_ok=True)+chapters=[+('intro','Astra expands the heatsinks',"This continues our latest Fusion routing demonstration. The traces already work geometrically. Now Astra looks for copper that can do more useful thermal work. I preserve that board, then expand the heatsinking on a separate copy through Adom Bridge. These are native Fusion edits, with precomputed boundaries.",['Video 10 preserved','576 traces / 89 vias retained','Native Fusion on CRR']),+('VAC1','Q1 / VAC1',"The highlighted region is VAC one, connected to Q one’s drain thermal pad through its seven footprint holes. I expand it toward the board edges and the reserved ground corridor. Connected bottom copper grows from about twenty eight to one hundred seventy three square millimeters.",['VAC1 / Q1 drain','28.1 → 172.8 mm²','Connected bottom copper']),+('VBUS','Q2 + Q4 / VBUS',"Q two and Q four share V bus. This is one thermal region, so I count its area once. It expands from forty five to two hundred six square millimeters. The notch on the left preserves ground copper for the linear regulator instead of giving every available square millimeter to the MOSFETs.",['VBUS / Q2 + Q4 drains','44.6 → 205.6 mm²','Shared region counted once']),+('VAC2','Q3 / VAC2',"This lower left heatsink belongs to VAC two and Q three. Astra expands the connected copper to roughly one hundred fifty two square millimeters. The native refill respects other nets and the board edge. A trial with touching polygon boundaries failed clearance checks; the separated boundaries pass.",['VAC2 / Q3 drain','29.5 → 151.8 mm²','Separated polygon boundaries']),+('VBAT','Q5 / VBAT',"On the right, Q five’s thermal pad is on V bat, not ground. Its region grows from twenty four to two hundred thirty five square millimeters. Existing routes and thermal holes stay in place. Expanding quiet supply copper is different from enlarging the charger’s high speed switching nodes, which I leave unchanged.",['VBAT / Q5 drain','24.4 → 235.4 mm²','SW1 / SW2 unchanged']),+('ground','PGND remains a thermal path',"Ground still matters. The highlighted P ground copper serves the charger and linear regulator, with their existing added thermal vias. Both internal ground pours remain. This is a redistribution of existing copper, not six times more metal on the board. The bottom ground area connected to those thermal vias decreases from nine hundred twenty eight to two hundred seventy five square millimeters.",['PGND / IC1 + U2','Dedicated bottom areas retained','Internal pours unchanged']),+('area','Six times the connected MOSFET area',"Together, the four MOSFET regions grow from one hundred twenty seven to seven hundred sixty six square millimeters. Floating islands do not count as heatsinking. These measurements subtract drill holes; I also corrected a hole-subtraction bug in the earlier helper and recalculated both boards consistently.",['126.7 → 765.7 mm²','6.0× connected area','Drills subtracted / no islands']),+('model','Estimate heat flow, not a power rating',"Here is a first order calculation using the actual copper shapes. The board specifies thirty five micron copper. I hold the bottom source copper at sixty five degrees Celsius, with twenty five degree ambient air, and solve lateral heat conduction plus surface convection and radiation. The four regions shed about fifty five milliwatts before expansion and two hundred eighty three afterward. That is not a junction temperature prediction.",['65°C source / 25°C ambient','35 µm copper / h = 5 W/m²K','Bottom-only estimate']),+('mask','Keep solder mask for this air-cooled option',"Removing solder mask does not automatically improve cooling. Bright copper radiates poorly. In this model, fully masked expanded regions shed two hundred eighty three milliwatts, compared with roughly one hundred fifty for bright exposed copper. A half-exposed approximation also loses ground. Oxidized copper performs similarly to mask, but its surface condition varies. I choose to retain solder mask. A designed thermal interface to a heatsink would require a separate comparison.",['Masked: 0.283 W','Bright exposed: 0.150 W','Chosen: retain solder mask']),+('limits','What the calculation leaves out',"Airflow sensitivity gives about two hundred thirty six to three hundred ninety seven milliwatts for the expanded MOSFET regions. Ground loses some dedicated bottom cooling area. This model omits package resistance, via resistance, other layers, the enclosure, and simultaneous real loads. Use it to compare copper layouts, then measure the assembled board under its actual operating conditions.",['h = 3–10 W/m²K sensitivity','Package / via / enclosure omitted','Real loads still required']),+('final','Native copper, checked and saved',"The actual Fusion replay finishes with zero airwires, zero warnings, and no new D R C errors. The inherited clearance error remains disclosed. Astra calculated the expansion and thermal comparison; Adom Bridge created and inspected the native copper. The saved board, equations, assumptions, and scripts make this experiment reproducible.",['576 segments / 89 vias','12 native pours','0 new errors / 1 inherited'])]+voices=[]+for key,title,speech,lines in chapters:+ f=E/(key+'.txt');f.write_text(speech);audio=E/(key+'.mp3')+ if not audio.exists():subprocess.run(['adom-tts','say','@'+str(f),'--out',str(audio)],check=True,stdout=subprocess.DEVNULL)+ dur=float(subprocess.check_output(['ffprobe','-v','error','-show_entries','format=duration','-of','default=nw=1:nk=1',str(audio)]))+ voices.append({'key':key,'title':title,'speech':speech,'lines':lines,'audio':str(audio),'duration':dur});(E/'voices.json').write_text(json.dumps(voices,indent=2));print(key,round(dur,1),flush=True)
demo/routing/bq25792/thermal-expansion/prepare_live.pyadded+11
@@ -0,0 +1,11 @@+import sys,json,shutil+from pathlib import Path+P=Path(__file__).resolve().parent;sys.path.insert(0,str(P.parent/'thermal'));from crr import call,execute+D='bq25792-thermal-expansion-live';T=P/'recording';T.mkdir(exist_ok=True)+f=T/(D+'.brd');shutil.copy2(P.parent/'thermal/recording-02/bq25792-astra-thermal-routed.brd',f)+r=call('send_files',{'filePaths':[str(f)],'dest':'C:/Users/john/AppData/Local/Temp/adom-thermal-expansion','reason':'Prepare a separate live copper-expansion demonstration preserving video ten'});assert r.get('success'),r+r=call('fusion_open_board',{'filePath':r['destinationPaths'][0]});assert r.get('success'),r+r=execute(D,"app.executeTextCommand('Electron.run RATSNEST; DISPLAY NONE; DISPLAY 16 17 18 20; WINDOW (-1mm -1mm) (52mm 28mm); SHOW VAC1;')\nresult=handle(app,{'expectDocument':target.name},'routing_state')")+(T/'initial-state.json').write_text(json.dumps(r,indent=2));assert (r['segmentCount'],r['viaCount'],r['pourCount'],r['airwireCount'])==(576,89,8,0)+w=call('desktop_list_windows',{})['windows'];T.joinpath('windows.json').write_text(json.dumps(w,indent=2));main=next(x for x in w if x.get('image')=='Fusion360.exe' and x.get('owner')==0)+r=call('desktop_screenshot_window',{'hwnd':main['hwnd'],'resizeMax':1600});T.joinpath('screen.json').write_text(json.dumps(r));print({k:v for k,v in r.items() if k not in ['image','data','output']})
demo/routing/bq25792/thermal-expansion/record_expansion.pyadded+44
@@ -0,0 +1,44 @@+"""Record additive native pour expansion and net highlighting; preserve prior board."""+import sys,json,time,shutil,xml.etree.ElementTree as ET+from pathlib import Path+P=Path(__file__).resolve().parent;sys.path.insert(0,str(P.parent/'thermal'));from crr import call,execute+D='bq25792-thermal-expansion-live';T=P/'recording';events=[];records=[]+bounds=json.loads((P/'expansion-03/bounds.json').read_text())+def native(body):+ compile(body,'native-expansion','exec');return execute(D,body)+def command(cmd):return native("app.executeTextCommand("+repr('Electron.run '+cmd)+")\napp.executeTextCommand('Electron.ImmediateRepaint /full')\nadsk.doEvents()\nresult={'success':True}")+def mark(n):events.append({'name':n,'time':time.time()});(T/'events.json').write_text(json.dumps(events,indent=2));print(n,flush=True)+def banner(title,detail):+ payload={'title':'Astra: '+title,'detail':detail,'percent':50,'tone':'working','routing':{'connection':'Native Fusion / four net-specific thermal regions','plan':'Expand connected copper; preserve ground and compact switch nodes','checks':'No new DRC errors in preflight; final live DRC follows','events':'POLYGON / CHANGE / RATSNEST / SHOW'}}+ native("p=app.userInterface.palettes.itemById('AdomActivityPalette')\nif p:\n p.isVisible=True\n p.sendInfoToHTML('update',"+repr(json.dumps(payload))+")\nresult={'palette':bool(p)}")+s=native("result=handle(app,{'expectDocument':target.name},'routing_state')");assert (s['segmentCount'],s['viaCount'],s['pourCount'])==(576,89,8)+w=call('desktop_list_windows',{})['windows'];main=next(x for x in w if x.get('image')=='Fusion360.exe' and x.get('owner')==0)+call('desktop_bring_to_front',{'hwnd':main['hwnd'],'reason':'Record the requested Fusion heatsink expansion and net-by-net video on CRR'})+try:+ r=call('desktop_record_window_start',{'hwnd':main['hwnd'],'fps':30,'audio':False,'maxDurationMs':480000,'reason':'Show native copper pours expanding and highlight each actual thermal net'});assert r.get('success'),r;records.append(r);(T/'start.json').write_text(json.dumps({'calledAt':time.time(),**r},indent=2))+ command('DISPLAY NONE; DISPLAY 16 17 18 20; WINDOW (-1mm -1mm) (52mm 28mm);');mark('before');banner('Video ten baseline','Existing 576 segments, 89 vias and eight pours');time.sleep(4)+ for i,(net,b) in enumerate(bounds.items()):+  command('SHOW '+net);mark('before-'+net);time.sleep(3)+  x,y,z,w=b;cx=z if net!='VBAT' else x;cy=(y+w)/2+  cmd=f"LAYER 16; POLYGON '{net}' 0.1mm ({x}mm {y}mm) +0 ({z}mm {y}mm) +0 ({z}mm {w}mm) +0 ({x}mm {w}mm) +0 ({x}mm {y}mm); CHANGE ISOLATE 0.1mm ({cx}mm {cy}mm); CHANGE THERMALS OFF ({cx}mm {cy}mm); CHANGE ORPHANS OFF ({cx}mm {cy}mm); CHANGE RANK 1 ({cx}mm {cy}mm); RATSNEST;"+  mark('expand-'+net);banner('Expanding '+net,'Precomputed boundaries; native fill determines connected copper');command(cmd)+  s=native("result=handle(app,{'expectDocument':target.name},'routing_state')");assert (s['segmentCount'],s['viaCount'],s['pourCount'],s['airwireCount'])==(576,89,9+i,0)+  (T/('state-'+net+'.json')).write_text(json.dumps(s,indent=2));command('SHOW '+net);mark('after-'+net);time.sleep(6)+ command('SHOW PGND');mark('ground');banner('Preserve ground cooling','Ground regions remain for IC1 and U2; inner planes retained');time.sleep(7)+ command('DISPLAY NONE; DISPLAY 1 17 18 20 21; WINDOW (-1mm -1mm) (52mm 28mm);');mark('top');time.sleep(4)+ command('DISPLAY NONE; DISPLAY 16 17 18 20; WINDOW (-1mm -1mm) (52mm 28mm);');mark('validation')+ r=native("s=handle(app,{'expectDocument':target.name},'routing_state')\nresult=handle(app,{'expectDocument':target.name,'boardId':s['boardId'],'expectedRevision':s['revision']},'routing_drc')");(T/'final-drc.json').write_text(json.dumps(r,indent=2));assert r['drc']['airwireCount']==0 and r['drc']['warningCount']==0 and [e['signature'] for e in r['drc']['errors']]==['5,1,f10227fd41e0ab5d'],r['drc']+ host='C:/Users/john/AppData/Local/Temp/adom-thermal-expansion/bq25792-expanded-thermal'+ native("root,b,m=snapshot(app,{'expectDocument':target.name})\nET.ElementTree(root).write("+repr(host+'.brd')+",encoding='utf-8',xml_declaration=True)\napp.executeTextCommand("+repr('Document.CopyToDesktop '+host+'.fbrd')+")\nresult={'saved':True}")+ r=call('pull_file',{'filePaths':[host+'.brd',host+'.fbrd'],'saveTo':str(T)});(T/'exports.json').write_text(json.dumps(r,indent=2))+ for ext in ['brd','fbrd']:+  f=T/('bq25792-expanded-thermal.'+ext)+  if not f.exists():shutil.copy2(Path('/mnt/c')/str(f).lstrip('/'),f)+ command('SHOW VAC1 VBUS VAC2 VBAT');mark('final');banner('Expanded and checked','0 airwires / 0 warnings / 0 new errors; one inherited error');time.sleep(7)+finally:+ for r in records:+  stop=call('desktop_record_window_stop',{'recordingId':r['recordingId']});(T/'stop.json').write_text(json.dumps(stop,indent=2));assert stop.get('success'),stop+  pull=call('pull_file',{'filePaths':[stop['path']],'saveTo':str(T)});(T/'pull.json').write_text(json.dumps(pull,indent=2))+  f=T/Path(stop['path']).name+  if not f.exists():shutil.copy2(Path('/mnt/c')/str(f).lstrip('/'),f)+print('Recorded, exported, native DRC verified',flush=True)
demo/routing/bq25792/thermal-expansion/render.pyadded+38
@@ -0,0 +1,38 @@+"""Native action at 3x, explicitly labeled inspection holds and postproduction analysis."""+import json,subprocess,textwrap+from pathlib import Path+from PIL import Image,ImageDraw,ImageFont+P=Path(__file__).resolve().parent;E=P/'edit';V=next((P/'recording').glob('*.mp4'));voices=json.loads((E/'voices.json').read_text())+font='/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf'+cuts={'intro':(3,6),'VAC1':(7,20),'VBUS':(21,33),'VAC2':(34,45),'VBAT':(48,61),'ground':(2,6),'final':(78,85)}+clips=[];manifest=[];pos=0+for i,v in enumerate(voices):+ key=v['key'];dur=v['duration']+1.2;out=E/f'clip-{i:02}.mp4';chart=key not in cuts+ im=Image.new('RGBA',(1920,1080),'#101925');im.paste((0,0,0,0),(10,125,1450,990));d=ImageDraw.Draw(im)+ d.text((30,24),'CODEX ASTRA / FUSION',font=ImageFont.truetype(font,23),fill='#50c9d6')+ d.text((30,60),v['title'],font=ImageFont.truetype(font,34),fill='white')+ y=220+ for line in v['lines']:+  for s in textwrap.wrap(line,27):d.text((1480,y),s,font=ImageFont.truetype(font,23),fill='#d7e7f0');y+=37+  y+=25+ if key in ['VAC1','VBUS','VAC2','VBAT']:+  d.text((1480,650),'Native bridge calls',font=ImageFont.truetype(font,24),fill='#50c9d6')+  for z,s in enumerate(['fusion_run_modeling_script','Electron.run POLYGON','CHANGE / RATSNEST','SHOW '+key]):d.text((1480,700+z*38),s,font=ImageFont.truetype(font,19),fill='#d7e7f0')+ if key in ['model','mask','limits']:d.text((1480,680),'ESTIMATE',font=ImageFont.truetype(font,28),fill='#edb659');d.text((1480,725),'Not measured temperature',font=ImageFont.truetype(font,21),fill='#d7e7f0')+ footer='POSTPRODUCTION ANALYSIS / stated assumptions' if chart else 'NATIVE FUSION / 3x action + inspection hold / waits removed'+ d.text((30,1025),footer,font=ImageFont.truetype(font,22),fill='#aebfcf');im.save(E/(key+'-overlay.png'))+ if chart:+  ins=['-loop','1','-i',str(E/(key+'.png'))]+  flt='[0:v]scale=1420:860,pad=1920:1080:20:125:color=0x101925[v]'+ else:+  a,b=cuts[key];ins=['-ss',str(a),'-t',str(b-a),'-i',str(P/'recording/ground-highlight.mp4' if key=='ground' else V)]+  flt=f'[0:v]setpts=(PTS-STARTPTS)/3,crop=3020:1570:20:370,scale=1420:-2,pad=1420:860:0:60:color=0x101925,pad=1920:1080:20:125:color=0x101925,tpad=stop_mode=clone:stop_duration={dur}[v]'+ flt+=';[v][1:v]overlay=0:0,fps=30,format=yuv420p[out];[2:a]adelay=400|400,apad[a]'+ if key=='ground' or not out.exists():subprocess.run(['ffmpeg','-v','error',*ins,'-i',str(E/(key+'-overlay.png')),'-i',v['audio'],'-filter_complex',flt,'-map','[out]','-map','[a]','-t',str(dur),'-c:v','libx264','-preset','fast','-crf','19','-threads','4','-c:a','aac','-b:a','192k','-ar','48000','-y',str(out)],check=True)+ clips.append(out);manifest.append({**v,'outputStart':pos,'outputDuration':dur,'sourceInterval':cuts.get(key),'sourceFile':str(P/'recording/ground-highlight.mp4' if key=='ground' else V) if not chart else str(E/(key+'.png')),'speed':3 if not chart else None,'hold':not chart});pos+=dur;print(key,round(pos,1),flush=True)+# Last frame held for five seconds; silent audio remains compatible for concatenation.+last=E/'hold.mp4';subprocess.run(['ffmpeg','-v','error','-sseof','-0.1','-i',str(clips[-1]),'-f','lavfi','-i','anullsrc=r=48000:cl=mono','-vf','tpad=stop_mode=clone:stop_duration=5','-t','5','-map','0:v','-map','1:a','-c:v','libx264','-preset','fast','-crf','19','-threads','4','-c:a','aac','-y',str(last)],check=True);clips.append(last)+(E/'concat.txt').write_text(''.join("file '"+str(x)+"'\n" for x in clips))+subprocess.run(['ffmpeg','-v','error','-f','concat','-safe','0','-i',str(E/'concat.txt'),'-c','copy','-y',str(E/'joined.mp4')],check=True)+# Loudness normalization is done separately, with measured two-pass values.+(E/'edit-manifest.json').write_text(json.dumps({'chapters':manifest,'duration':pos+5,'source':str(V),'cutMethod':'Actual encoded frames inspected every six seconds; initial black frames and validation waits removed. Native action accelerated 3x followed by explicit inspection holds. PGND highlighting uses a supplemental native-window take. Analysis charts are postproduction.'},indent=2))
demo/routing/bq25792/thermal-expansion/test_model.pyadded+13
@@ -0,0 +1,13 @@+from shapely.geometry import box+from thermal_model import mesh,solve,SIGMA+# Entire sheet held at the source temperature must match the direct surface equation.+g=box(0,0,10,10);q,_=solve(mesh(g,g,.1),5,.8);Ta=298.15;Ts=338.15+hr=.8*SIGMA*(Ts+Ta)*(Ts*Ts+Ta*Ta)+expected=(1/(1/(5+hr)+25e-6/.2))*100e-6*40+assert abs(q['bottomHeatW']-expected)<1e-10+# Equal-area copper behind a narrow neck must not be credited as an isothermal plate.+narrow=box(0,0,20,10).difference(box(4,0,6,4.9).union(box(4,5.1,6,10)))+wide=box(0,0,narrow.area/10,10);src=box(0,0,1,10)+a=solve(mesh(narrow,src,.1),5,.8)[0];b=solve(mesh(wide,src,.1),5,.8)[0]+assert a['bottomHeatW']<b['bottomHeatW']*.8,(a,b)+print('Passed: surface analytical limit, narrow-neck penalty, and source/surface energy balance')
demo/routing/bq25792/thermal-expansion/thermal-results.jsonadded+611
@@ -0,0 +1,611 @@+{+  "assumptions": {+    "sourceCopperC": 65,+    "ambientC": 25,+    "copperUm": 35,+    "copperConductivityWmK": 390,+    "maskUm": 25,+    "maskConductivityWmK": 0.2,+    "maskEmissivity": 0.8,+    "brightCopperEmissivity": 0.05,+    "oxidizedCopperEmissivity": 0.8,+    "nominalConvectionWm2K": 5,+    "scope": "Bottom-only copper sheet; no FR4/top/internal conduction, package/via resistance, enclosure, neighboring component heat or actual load power. Existing ground copper is reassigned: do not add these W values to whole-board capability."+  },+  "boards": {+    "before": {+      "VAC1": {+        "areaMm2": 28.147563799109513,+        "masked": {+          "bottomHeatW": 0.012147866860353736,+          "conductanceWPerK": 0.00030369667150884343,+          "isothermalUpperW": 0.012210268234873272,+          "sheetEfficiency": 0.9948894345874144,+          "energyResidualW": 4.167673151034279e-14,+          "meshAreaMm2": 28.130000000000006+        },+        "bright": {+          "bottomHeatW": 0.006024289766621891,+          "conductanceWPerK": 0.0001506072441655473,+          "isothermalUpperW": 0.006038553338454444,+          "sheetEfficiency": 0.9976379157336047,+          "energyResidualW": 6.514060124640508e-14,+          "meshAreaMm2": 28.130000000000006+        },+        "oxidized": {+          "bottomHeatW": 0.012164277583443125,+          "conductanceWPerK": 0.0003041069395860781,+          "isothermalUpperW": 0.012226853415271082,+          "sheetEfficiency": 0.9948820984678035,+          "energyResidualW": 6.481447323292144e-14,+          "meshAreaMm2": 28.130000000000006+        },+        "h3": {+          "bottomHeatW": 0.009922727778153916,+          "conductanceWPerK": 0.0002480681944538479,+          "isothermalUpperW": 0.009965408774717726,+          "sheetEfficiency": 0.9957170852166053,+          "energyResidualW": 5.2268953054657175e-14,+          "meshAreaMm2": 28.130000000000006+        },+        "h10": {+          "bottomHeatW": 0.017690261886338272,+          "conductanceWPerK": 0.0004422565471584568,+          "isothermalUpperW": 0.017817515975157242,+          "sheetEfficiency": 0.9928579220023488,+          "energyResidualW": 3.6349395715618016e-14,+          "meshAreaMm2": 28.130000000000006+        },+        "halfBrightApprox": {+          "bottomHeatW": 0.009098426392540295,+          "conductanceWPerK": 0.00022746065981350737,+          "isothermalUpperW": 0.009132703376862766,+          "sheetEfficiency": 0.9962467866404914,+          "energyResidualW": 5.196190699940928e-14,+          "meshAreaMm2": 28.130000000000006+        },+        "coarseGrid": {+          "bottomHeatW": 0.012411802946259098,+          "conductanceWPerK": 0.00031029507365647744,+          "isothermalUpperW": 0.012481559299458973,+          "sheetEfficiency": 0.9944112469022282,+          "energyResidualW": 1.8384599398402202e-14,+          "meshAreaMm2": 28.755+        }+      },+      "VBUS": {+        "areaMm2": 44.60395378984274,+        "masked": {+          "bottomHeatW": 0.01929535557447545,+          "conductanceWPerK": 0.00048238388936188624,+          "isothermalUpperW": 0.019311583141468604,+          "sheetEfficiency": 0.9991596977381773,+          "energyResidualW": 5.690239945899123e-14,+          "meshAreaMm2": 44.49000000000001+        },+        "bright": {+          "bottomHeatW": 0.009546847542709357,+          "conductanceWPerK": 0.00023867118856773394,+          "isothermalUpperW": 0.009550488376389556,+          "sheetEfficiency": 0.9996187803663319,+          "energyResidualW": 6.176482936215422e-14,+          "meshAreaMm2": 44.49000000000001+        },+        "oxidized": {+          "bottomHeatW": 0.019321540268173076,+          "conductanceWPerK": 0.0004830385067043269,+          "isothermalUpperW": 0.019337814022232867,+          "sheetEfficiency": 0.9991584491379905,+          "energyResidualW": 6.262004803581078e-14,+          "meshAreaMm2": 44.49000000000001+        },+        "h3": {+          "bottomHeatW": 0.015750100952421214,+          "conductanceWPerK": 0.00039375252381053036,+          "isothermalUpperW": 0.015761145978926114,+          "sheetEfficiency": 0.9992992244015969,+          "energyResidualW": 5.478256737134757e-14,+          "meshAreaMm2": 44.49000000000001+        },+        "h10": {+          "bottomHeatW": 0.02814643190327043,+          "conductanceWPerK": 0.0007036607975817608,+          "isothermalUpperW": 0.02817992483948616,+          "sheetEfficiency": 0.9988114611232461,+          "energyResidualW": 6.053491041768666e-14,+          "meshAreaMm2": 44.49000000000001+        },+        "halfBrightApprox": {+          "bottomHeatW": 0.014435318791653946,+          "conductanceWPerK": 0.00036088296979134866,+          "isothermalUpperW": 0.014444151199311212,+          "sheetEfficiency": 0.999388513209576,+          "energyResidualW": 3.5273867160512395e-14,+          "meshAreaMm2": 44.49000000000001+        },+        "coarseGrid": {+          "bottomHeatW": 0.019193922712511278,+          "conductanceWPerK": 0.0004798480678127819,+          "isothermalUpperW": 0.01921066286544272,+          "sheetEfficiency": 0.9991286009728714,+          "energyResidualW": 4.224745553393916e-14,+          "meshAreaMm2": 44.2575+        }+      },+      "VAC2": {+        "areaMm2": 29.51575785643567,+        "masked": {+          "bottomHeatW": 0.012691805580901026,+          "conductanceWPerK": 0.00031729513952252563,+          "isothermalUpperW": 0.012761531678111419,+          "sheetEfficiency": 0.9945362281762786,+          "energyResidualW": 3.620194422015999e-14,+          "meshAreaMm2": 29.400000000000006+        },+        "bright": {+          "bottomHeatW": 0.006295229685194157,+          "conductanceWPerK": 0.00015738074212985392,+          "isothermalUpperW": 0.0063111791024017296,+          "sheetEfficiency": 0.9974728308373465,+          "energyResidualW": 6.561851756403669e-14,+          "meshAreaMm2": 29.400000000000006+        },+        "oxidized": {+          "bottomHeatW": 0.012708944739729375,+          "conductanceWPerK": 0.0003177236184932344,+          "isothermalUpperW": 0.012778865638427651,+          "sheetEfficiency": 0.994528395502648,+          "energyResidualW": 5.5887586225544794e-14,+          "meshAreaMm2": 29.400000000000006+        },+        "h3": {+          "bottomHeatW": 0.010367622441813017,+          "conductanceWPerK": 0.0002591905610453254,+          "isothermalUpperW": 0.010415322359640992,+          "sheetEfficiency": 0.9954202168515868,+          "energyResidualW": 5.954091386595195e-14,+          "meshAreaMm2": 29.400000000000006+        },+        "h10": {+          "bottomHeatW": 0.01847980610074386,+          "conductanceWPerK": 0.0004619951525185965,+          "isothermalUpperW": 0.01862193280019989,+          "sheetEfficiency": 0.9923677793824653,+          "energyResidualW": 5.4328069820641645e-14,+          "meshAreaMm2": 29.400000000000006+        },+        "halfBrightApprox": {+          "bottomHeatW": 0.009506708391132034,+          "conductanceWPerK": 0.00023766770977830086,+          "isothermalUpperW": 0.009545022370414692,+          "sheetEfficiency": 0.9959859728143315,+          "energyResidualW": 5.366367072934253e-14,+          "meshAreaMm2": 29.400000000000006+        },+        "coarseGrid": {+          "bottomHeatW": 0.01286302156237503,+          "conductanceWPerK": 0.00032157553905937575,+          "isothermalUpperW": 0.012950350259063066,+          "sheetEfficiency": 0.9932566536857241,+          "energyResidualW": 2.4636542805822614e-14,+          "meshAreaMm2": 29.834999999999997+        }+      },+      "VBAT": {+        "areaMm2": 24.426218041916556,+        "masked": {+          "bottomHeatW": 0.010420174127830726,+          "conductanceWPerK": 0.00026050435319576815,+          "isothermalUpperW": 0.010621587760659402,+          "sheetEfficiency": 0.981037332895306,+          "energyResidualW": 6.901076932130934e-14,+          "meshAreaMm2": 24.470000000000006+        },+        "bright": {+          "bottomHeatW": 0.0052053457289262355,+          "conductanceWPerK": 0.00013013364322315589,+          "isothermalUpperW": 0.005252875939992188,+          "sheetEfficiency": 0.9909515831691195,+          "energyResidualW": 5.4599554044632015e-14,+          "meshAreaMm2": 24.470000000000006+        },+        "oxidized": {+          "bottomHeatW": 0.010434055211581469,+          "conductanceWPerK": 0.0002608513802895367,+          "isothermalUpperW": 0.010636015039874987,+          "sheetEfficiency": 0.9810117015126097,+          "energyResidualW": 6.084022174945858e-14,+          "meshAreaMm2": 24.470000000000006+        },+        "h3": {+          "bottomHeatW": 0.00852990160660338,+          "conductanceWPerK": 0.0002132475401650845,+          "isothermalUpperW": 0.008668807419742009,+          "sheetEfficiency": 0.9839763641741204,+          "energyResidualW": 5.3469381700033125e-14,+          "meshAreaMm2": 24.470000000000006+        },+        "h10": {+          "bottomHeatW": 0.015096624370403749,+          "conductanceWPerK": 0.00037741560926009374,+          "isothermalUpperW": 0.015499275361254807,+          "sheetEfficiency": 0.9740213021921265,+          "energyResidualW": 6.51770304394006e-14,+          "meshAreaMm2": 24.470000000000006+        },+        "halfBrightApprox": {+          "bottomHeatW": 0.00783208253608439,+          "conductanceWPerK": 0.00019580206340210973,+          "isothermalUpperW": 0.007944445489933588,+          "sheetEfficiency": 0.9858564132648938,+          "energyResidualW": 7.237786758818032e-14,+          "meshAreaMm2": 24.470000000000006+        },+        "coarseGrid": {+          "bottomHeatW": 0.010804337569563007,+          "conductanceWPerK": 0.00027010843923907515,+          "isothermalUpperW": 0.011016587550696183,+          "sheetEfficiency": 0.9807336001137882,+          "energyResidualW": 4.275919895935232e-14,+          "meshAreaMm2": 25.380000000000003+        }+      },+      "PGND": {+        "areaMm2": 927.9700353304204,+        "masked": {+          "bottomHeatW": 0.23138621972254808,+          "conductanceWPerK": 0.005784655493063702,+          "isothermalUpperW": 0.40251346736154764,+          "sheetEfficiency": 0.5748533614024676,+          "energyResidualW": 4.804767694821521e-13,+          "meshAreaMm2": 927.3100000000002+        },+        "bright": {+          "bottomHeatW": 0.14448152647865836,+          "conductanceWPerK": 0.003612038161966459,+          "isothermalUpperW": 0.19906188753224996,+          "sheetEfficiency": 0.7258120992912365,+          "energyResidualW": 5.760392163267625e-13,+          "meshAreaMm2": 927.3100000000002+        },+        "oxidized": {+          "bottomHeatW": 0.23158108648644804,+          "conductanceWPerK": 0.005789527162161201,+          "isothermalUpperW": 0.4030602005159982,+          "sheetEfficiency": 0.5745570666366404,+          "energyResidualW": 4.5152770411505116e-13,+          "meshAreaMm2": 927.3100000000002+        },+        "h3": {+          "bottomHeatW": 0.20170189816304457,+          "conductanceWPerK": 0.005042547454076114,+          "isothermalUpperW": 0.3285113121536969,+          "sheetEfficiency": 0.6139876792695546,+          "energyResidualW": 4.474198789239381e-13,+          "meshAreaMm2": 927.3100000000002+        },+        "h10": {+          "bottomHeatW": 0.2936781984454644,+          "conductanceWPerK": 0.00734195496113661,+          "isothermalUpperW": 0.5873572960868491,+          "sheetEfficiency": 0.49999923454094614,+          "energyResidualW": 3.898548150971237e-13,+          "meshAreaMm2": 927.3100000000002+        },+        "halfBrightApprox": {+          "bottomHeatW": 0.19208392175039432,+          "conductanceWPerK": 0.004802098043759858,+          "isothermalUpperW": 0.3010610440241241,+          "sheetEfficiency": 0.6380231702611202,+          "energyResidualW": 4.415634524690404e-13,+          "meshAreaMm2": 927.3100000000002+        },+        "coarseGrid": {+          "bottomHeatW": 0.2309257806009128,+          "conductanceWPerK": 0.0057731445150228195,+          "isothermalUpperW": 0.4016268881624815,+          "sheetEfficiency": 0.5749758977976839,+          "energyResidualW": 2.7480795417034187e-13,+          "meshAreaMm2": 925.2674999999999+        }+      }+    },+    "expanded": {+      "VAC1": {+        "areaMm2": 172.77533644726253,+        "masked": {+          "bottomHeatW": 0.05398289124364424,+          "conductanceWPerK": 0.001349572281091106,+          "isothermalUpperW": 0.0741861693573477,+          "sheetEfficiency": 0.72766786196513,+          "energyResidualW": 8.253814298697648e-14,+          "meshAreaMm2": 170.91000000000003+        },+        "bright": {+          "bottomHeatW": 0.030020645601695053,+          "conductanceWPerK": 0.0007505161400423764,+          "isothermalUpperW": 0.03668855851671699,+          "sheetEfficiency": 0.8182563397255378,+          "energyResidualW": 1.3414269695033454e-13,+          "meshAreaMm2": 170.91000000000003+        },+        "oxidized": {+          "bottomHeatW": 0.05404329553012112,+          "conductanceWPerK": 0.001351082388253028,+          "isothermalUpperW": 0.07428693626747176,+          "sheetEfficiency": 0.7274939342704487,+          "energyResidualW": 1.086700174290911e-13,+          "meshAreaMm2": 170.91000000000003+        },+        "h3": {+          "bottomHeatW": 0.04546730893922982,+          "conductanceWPerK": 0.0011366827234807453,+          "isothermalUpperW": 0.06054703212538238,+          "sheetEfficiency": 0.7509419924179755,+          "energyResidualW": 1.0395156957443419e-13,+          "meshAreaMm2": 170.91000000000003+        },+        "h10": {+          "bottomHeatW": 0.07402072722860556,+          "conductanceWPerK": 0.001850518180715139,+          "isothermalUpperW": 0.10825423588034569,+          "sheetEfficiency": 0.683767490728227,+          "energyResidualW": 9.948986079422184e-14,+          "meshAreaMm2": 170.91000000000003+        },+        "halfBrightApprox": {+          "bottomHeatW": 0.04245072455995337,+          "conductanceWPerK": 0.0010612681139988342,+          "isothermalUpperW": 0.05548774739209439,+          "sheetEfficiency": 0.7650468176331399,+          "energyResidualW": 1.3423290257108533e-13,+          "meshAreaMm2": 170.91000000000003+        },+        "coarseGrid": {+          "bottomHeatW": 0.055724519629652816,+          "conductanceWPerK": 0.0013931129907413203,+          "isothermalUpperW": 0.07481122397015315,+          "sheetEfficiency": 0.7448684391513871,+          "energyResidualW": 6.356026815979021e-14,+          "meshAreaMm2": 172.35+        }+      },+      "VBUS": {+        "areaMm2": 205.64224689672196,+        "masked": {+          "bottomHeatW": 0.07802043524400466,+          "conductanceWPerK": 0.0019505108811001166,+          "isothermalUpperW": 0.08899215049817696,+          "sheetEfficiency": 0.8767114268758225,+          "energyResidualW": 1.0787204463014177e-13,+          "meshAreaMm2": 205.02000000000004+        },+        "bright": {+          "bottomHeatW": 0.041091096765739424,+          "conductanceWPerK": 0.0010272774191434856,+          "isothermalUpperW": 0.04401081427123818,+          "sheetEfficiency": 0.9336590891614828,+          "energyResidualW": 1.2510131819354342e-13,+          "meshAreaMm2": 205.02000000000004+        },+        "oxidized": {+          "bottomHeatW": 0.0781147030552476,+          "conductanceWPerK": 0.0019528675763811902,+          "isothermalUpperW": 0.0891130283398108,+          "sheetEfficiency": 0.8765800524405504,+          "energyResidualW": 1.4260814751310136e-13,+          "meshAreaMm2": 205.02000000000004+        },+        "h3": {+          "bottomHeatW": 0.06483561274107105,+          "conductanceWPerK": 0.0016208903185267764,+          "isothermalUpperW": 0.07263093163855769,+          "sheetEfficiency": 0.8926721890849556,+          "energyResidualW": 1.7034984534092246e-13,+          "meshAreaMm2": 205.02000000000004+        },+        "h10": {+          "bottomHeatW": 0.10934746698116998,+          "conductanceWPerK": 0.0027336866745292497,+          "isothermalUpperW": 0.12985947832302658,+          "sheetEfficiency": 0.8420445576499792,+          "energyResidualW": 1.5978884881917566e-13,+          "meshAreaMm2": 205.02000000000004+        },+        "halfBrightApprox": {+          "bottomHeatW": 0.06009173846099287,+          "conductanceWPerK": 0.0015022934615248216,+          "isothermalUpperW": 0.06656192130552449,+          "sheetEfficiency": 0.9027945300011252,+          "energyResidualW": 1.3546802568598082e-13,+          "meshAreaMm2": 205.02000000000004+        },+        "coarseGrid": {+          "bottomHeatW": 0.0776782473843497,+          "conductanceWPerK": 0.0019419561846087425,+          "isothermalUpperW": 0.0887577550183749,+          "sheetEfficiency": 0.8751713849484871,+          "energyResidualW": 5.5594417958104714e-14,+          "meshAreaMm2": 204.48+        }+      },+      "VAC2": {+        "areaMm2": 151.82188420732476,+        "masked": {+          "bottomHeatW": 0.05718577413523235,+          "conductanceWPerK": 0.0014296443533808087,+          "isothermalUpperW": 0.06608216267604361,+          "sheetEfficiency": 0.865373828873848,+          "energyResidualW": 1.3354595207459852e-13,+          "meshAreaMm2": 152.24+        },+        "bright": {+          "bottomHeatW": 0.030059664078886855,+          "conductanceWPerK": 0.0007514916019721714,+          "isothermalUpperW": 0.03268074512073603,+          "sheetEfficiency": 0.919797390415493,+          "energyResidualW": 1.2856035680464117e-13,+          "meshAreaMm2": 152.24+        },+        "oxidized": {+          "bottomHeatW": 0.05725559949148262,+          "conductanceWPerK": 0.0014313899872870656,+          "isothermalUpperW": 0.06617192193177639,+          "sheetEfficiency": 0.8652551991842319,+          "energyResidualW": 1.4180323582024812e-13,+          "meshAreaMm2": 152.24+        },+        "h3": {+          "bottomHeatW": 0.04747052675198095,+          "conductanceWPerK": 0.0011867631687995238,+          "isothermalUpperW": 0.05393294816434506,+          "sheetEfficiency": 0.8801767447855483,+          "energyResidualW": 1.281613704051665e-13,+          "meshAreaMm2": 152.24+        },+        "h10": {+          "bottomHeatW": 0.08046478380242991,+          "conductanceWPerK": 0.0020116195950607477,+          "isothermalUpperW": 0.09642867515314393,+          "sheetEfficiency": 0.8344487122180114,+          "energyResidualW": 1.0566547636869927e-13,+          "meshAreaMm2": 152.24+        },+        "halfBrightApprox": {+          "bottomHeatW": 0.0439619844876048,+          "conductanceWPerK": 0.00109904961219012,+          "isothermalUpperW": 0.049426333526256215,+          "sheetEfficiency": 0.8894445804735112,+          "energyResidualW": 1.1277090372630028e-13,+          "meshAreaMm2": 152.24+        },+        "coarseGrid": {+          "bottomHeatW": 0.05698041645633754,+          "conductanceWPerK": 0.0014245104114084385,+          "isothermalUpperW": 0.06588466278102521,+          "sheetEfficiency": 0.8648509994764352,+          "energyResidualW": 5.525441215681326e-14,+          "meshAreaMm2": 151.785+        }+      },+      "VBAT": {+        "areaMm2": 235.44643982775554,+        "masked": {+          "bottomHeatW": 0.09416876376484842,+          "conductanceWPerK": 0.0023542190941212107,+          "isothermalUpperW": 0.10224417642105935,+          "sheetEfficiency": 0.9210183607626221,+          "energyResidualW": 1.9508006321444782e-13,+          "meshAreaMm2": 235.55000000000004+        },+        "bright": {+          "bottomHeatW": 0.04863927009032111,+          "conductanceWPerK": 0.0012159817522580277,+          "isothermalUpperW": 0.05056456590376624,+          "sheetEfficiency": 0.961924011824618,+          "energyResidualW": 1.4633433353949954e-13,+          "meshAreaMm2": 235.55000000000004+        },+        "oxidized": {+          "bottomHeatW": 0.09428595507081479,+          "conductanceWPerK": 0.0023571488767703697,+          "isothermalUpperW": 0.10238305446025964,+          "sheetEfficiency": 0.9209136762706395,+          "energyResidualW": 1.7714996136675154e-13,+          "meshAreaMm2": 235.55000000000004+        },+        "h3": {+          "bottomHeatW": 0.07786268450825894,+          "conductanceWPerK": 0.0019465671127064735,+          "isothermalUpperW": 0.08344657080998082,+          "sheetEfficiency": 0.933084292769356,+          "energyResidualW": 1.166428065246805e-13,+          "meshAreaMm2": 235.55000000000004+        },+        "h10": {+          "bottomHeatW": 0.1331532390586727,+          "conductanceWPerK": 0.0033288309764668176,+          "isothermalUpperW": 0.149197152077792,+          "sheetEfficiency": 0.892465018295028,+          "energyResidualW": 1.4405143744511406e-13,+          "meshAreaMm2": 235.55000000000004+        },+        "halfBrightApprox": {+          "bottomHeatW": 0.07194657064633803,+          "conductanceWPerK": 0.0017986642661584507,+          "isothermalUpperW": 0.07647381018201295,+          "sheetEfficiency": 0.9408001311180941,+          "energyResidualW": 1.526279103103434e-13,+          "meshAreaMm2": 235.55000000000004+        },+        "coarseGrid": {+          "bottomHeatW": 0.09455356814815623,+          "conductanceWPerK": 0.0023638392037039057,+          "isothermalUpperW": 0.10285078324147295,+          "sheetEfficiency": 0.919327643097899,+          "energyResidualW": 8.126832540256146e-14,+          "meshAreaMm2": 236.9475+        }+      },+      "PGND": {+        "areaMm2": 275.1768118585527,+        "masked": {+          "bottomHeatW": 0.07980671017583532,+          "conductanceWPerK": 0.001995167754395883,+          "isothermalUpperW": 0.1192248267356076,+          "sheetEfficiency": 0.6693799635608975,+          "energyResidualW": 1.6465995233971853e-13,+          "meshAreaMm2": 274.67+        },+        "bright": {+          "bottomHeatW": 0.04675764753565073,+          "conductanceWPerK": 0.0011689411883912682,+          "isothermalUpperW": 0.05896229809716609,+          "sheetEfficiency": 0.7930092456470594,+          "energyResidualW": 3.1616376183762895e-13,+          "meshAreaMm2": 274.67+        },+        "oxidized": {+          "bottomHeatW": 0.07988543215506116,+          "conductanceWPerK": 0.0019971358038765293,+          "isothermalUpperW": 0.11938676955465727,+          "sheetEfficiency": 0.669131365670199,+          "energyResidualW": 2.220307271372235e-13,+          "meshAreaMm2": 274.67+        },+        "h3": {+          "bottomHeatW": 0.06828888054438331,+          "conductanceWPerK": 0.0017072220136095829,+          "isothermalUpperW": 0.09730532627627861,+          "sheetEfficiency": 0.7018000263468721,+          "energyResidualW": 1.5315526624704034e-13,+          "meshAreaMm2": 274.67+        },+        "h10": {+          "bottomHeatW": 0.1054663457932144,+          "conductanceWPerK": 0.00263665864483036,+          "isothermalUpperW": 0.17397572388540491,+          "sheetEfficiency": 0.606213001663861,+          "energyResidualW": 2.139677324208833e-13,+          "meshAreaMm2": 274.67+        },+        "halfBrightApprox": {+          "bottomHeatW": 0.06437039365647462,+          "conductanceWPerK": 0.0016092598414118656,+          "isothermalUpperW": 0.08917453382591167,+          "sheetEfficiency": 0.7218472684380925,+          "energyResidualW": 2.196437476342794e-13,+          "meshAreaMm2": 274.67+        },+        "coarseGrid": {+          "bottomHeatW": 0.07872776363375607,+          "conductanceWPerK": 0.0019681940908439018,+          "isothermalUpperW": 0.11920963443599078,+          "sheetEfficiency": 0.6604144371907178,+          "energyResidualW": 1.1293743717999405e-13,+          "meshAreaMm2": 274.635+        }+      }+    }+  }+}\ No newline at end of file
demo/routing/bq25792/thermal-expansion/thermal_model.pyadded+68
@@ -0,0 +1,68 @@+"""First-order 2D copper-sheet conduction plus bottom-surface convection/radiation.+Not a package, whole-board, enclosure, or junction-temperature simulation.+Source copper held at 65 C, ambient/radiative surroundings at 25 C.+"""+import json,sys+import numpy as np+from scipy.sparse import coo_matrix,diags+from scipy.sparse.linalg import spsolve+from scipy.ndimage import label+from shapely import contains_xy+from shapely.geometry import Point+from shapely.ops import unary_union+from analyze import P,regions,copper+SIGMA=5.670374419e-8+BASE=P.parent/'thermal/recording-02/bq25792-astra-thermal-routed.brd'+FINAL=P/'expansion-03/expansion-03-filled.brd'+def ground(file):+ g=copper(file)['PGND'];src=unary_union([Point(x,y).buffer(.225) for x,y in [(2.05,10.3),(2.8,11.05),(35.2,12.3),(35.2,12.9),(35.2,14)]])+ parts=list(g.geoms) if hasattr(g,'geoms') else [g];g=unary_union([p for p in parts if p.intersects(src)])+ return {'geometry':g,'sources':src,'areaMm2':g.area,'components':['IC1','U2']}+def mesh(g,src,step):+ x0,y0,x1,y1=g.bounds+ xx,yy=np.meshgrid(np.arange(x0+step/2,x1,step),np.arange(y0+step/2,y1,step))+ present=contains_xy(g,xx,yy);fixed=contains_xy(src,xx,yy)&present+ labs,n=label(present);keep=np.unique(labs[fixed]);present &= np.isin(labs,keep[keep>0]);fixed &= present+ ids=np.full(present.shape,-1,dtype=int);ids[present]=np.arange(present.sum());N=int(present.sum());bc=fixed[present]+ edges=[]+ for ax in [0,1]:+  a=ids[:-1,:] if ax==0 else ids[:,:-1];b=ids[1:,:] if ax==0 else ids[:,1:];ok=(a>=0)&(b>=0);edges.extend(zip(a[ok],b[ok]))+ a,b=np.array(edges).T+ # k=390 W/m/K, native stackup copper=35 um. Per square sheet conductance k*t.+ conductance=390*35e-6+ rows=np.concatenate([a,b,a,b]);cols=np.concatenate([a,b,b,a]);vals=np.concatenate([np.ones(len(a)),np.ones(len(a)),-np.ones(len(a)),-np.ones(len(a))])*conductance+ L=coo_matrix((vals,(rows,cols)),shape=(N,N)).tocsr();free=~bc+ rhs=-L[free][:,bc].sum(axis=1).A.ravel()*40+ return L,free,rhs,N,step**2*1e-6,ids,present,bc++def solve(m,h,eps,mask=True):+ L,free,rhs,N,A,ids,present,bc=m;T=np.full(N,40.);Ta=298.15+ # Film resistance only for masked areas: provisional 25um / 0.2W/mK.+ film=25e-6/.2 if mask else 0.+ for _ in range(12):+  Ts=Ta+T;hr=eps*SIGMA*(Ts+Ta)*(Ts*Ts+Ta*Ta)+  coeff=1/(1/(h+hr)+film);H=coeff*A+  nxt=spsolve(L[free][:,free]+diags(H[free]),rhs)+  delta=np.max(abs(nxt-T[free]),initial=0.);T[free]=nxt+  if delta<1e-5:break+ Q=float(np.sum(H*T));iso=(1/(1/(h+eps*SIGMA*((Ta+40)+Ta)*((Ta+40)**2+Ta**2))+film))*A*N*40+ # source energy balance, including surface heat lost at fixed-temperature source cells.+ inputQ=float((L@T)[bc].sum()+(H*T)[bc].sum())+ assert abs(inputQ-Q)<1e-6,(inputQ,Q)+ heat=np.full(ids.shape,np.nan);heat[present]=T+ return {'bottomHeatW':Q,'conductanceWPerK':Q/40,'isothermalUpperW':iso,'sheetEfficiency':Q/iso,'energyResidualW':inputQ-Q,'meshAreaMm2':N*A*1e6},heat++def main():+ result={'assumptions':{'sourceCopperC':65,'ambientC':25,'copperUm':35,'copperConductivityWmK':390,'maskUm':25,'maskConductivityWmK':.2,'maskEmissivity':.8,'brightCopperEmissivity':.05,'oxidizedCopperEmissivity':.8,'nominalConvectionWm2K':5,'scope':'Bottom-only copper sheet; no FR4/top/internal conduction, package/via resistance, enclosure, neighboring component heat or actual load power. Existing ground copper is reassigned: do not add these W values to whole-board capability.'},'boards':{}}+ for name,file in [('before',BASE),('expanded',FINAL)]:+  rs=regions(file);rs['PGND']=ground(file);result['boards'][name]={}+  for n,r in rs.items():+   m=mesh(r['geometry'],r['sources'],.1);q,heat=solve(m,5,.8);entry={'areaMm2':r['areaMm2'],'masked':q,'bright':solve(m,5,.05,False)[0],'oxidized':solve(m,5,.8,False)[0],'h3':solve(m,3,.8)[0],'h10':solve(m,10,.8)[0]}+   # Half-area exposed finish approximation via area-weighted emissivity, explicitly not a shaped mask window.+   entry['halfBrightApprox']=solve(m,5,(.8+.05)/2,False)[0]+   entry['coarseGrid']=solve(mesh(r['geometry'],r['sources'],.15),5,.8)[0]+   result['boards'][name][n]=entry+   np.savez_compressed(P/f'{name}-{n}-temperature.npz',temperatureRiseC=heat,extent=r['geometry'].bounds)+   print(name,n,round(r['areaMm2'],2),round(q['bottomHeatW'],4),'W',flush=True)+ (P/'thermal-results.json').write_text(json.dumps(result,indent=2))+if __name__=='__main__':main()