master
John Lauer Preserve validated CRR thermal routing recipe and narrated demonstration 92d2870 1mo ago
import json,time,math
from pathlib import Path
from crr import call,execute,P
from plan_priority import THERMAL
DOC='bq25792-thermal-live-01';T=P/'recording-02';T.mkdir(exist_ok=True);events=[];calls=[];records=[]
def native(body):
 compile(body,"native-recording-script","exec");start=time.time();r=execute(DOC,body);calls.append({'start':start,'end':time.time(),'result':r});(T/'calls.json').write_text(json.dumps(calls,indent=2));return r
def stage(name):events.append({'name':name,'time':time.time()});(T/'stages.json').write_text(json.dumps(events,indent=2))
def panel(title,detail,checks,percent):
 p={'title':'Astra: '+title,'detail':'Codex Astra / Fusion on CRR','percent':percent,'tone':'done' if percent==100 else 'working','routing':{'connection':detail,'plan':'Horizontal / vertical / 45-degree paths','checks':checks,'events':'Native copper, readback and Autodesk DRC'}}
 return "pal=app.userInterface.palettes.itemById('AdomActivityPalette')\nassert pal and pal.isVisible\npal.sendInfoToHTML('update',"+repr(json.dumps(p))+")\nadsk.doEvents()\n"
def command(cmd,title,detail,checks,percent):return native(panel(title,detail,checks,percent)+"app.executeTextCommand("+repr('Electron.run '+cmd)+")\napp.executeTextCommand('Electron.ImmediateRepaint /full')\nresult={'success':True}")
j=json.loads((P/'thermal-final/candidate-plan.json').read_text());items=[]
for r in j['routes']:
 for p in r['parts']:
  for a,b in zip(p['points'],p['points'][1:]):
   if math.dist(a,b)>1e-7:items.append({'kind':'segment','net':r['net'],'layer':p['layer'],'width':p['width'],'start':a,'end':b})
 for v in r['vias']:items.append({'kind':'via','net':r['net'],'at':v,'diameter':.45,'drill':.2,'extent':'1-16'})
(T/'items.json').write_text(json.dumps(items,indent=2))
s=native("result=handle(app,{'expectDocument':target.name},'routing_state')");assert s['segmentCount']==s['viaCount']==s['pourCount']==0
native("pal=app.userInterface.palettes.itemById('AdomActivityPalette')\npal.height=600\nresult={'visible':pal.isVisible}")
w=call('desktop_list_windows',{})['windows'];main=next(x for x in w if x.get('image')=='Fusion360.exe' and x.get('owner')==0);pal=next(x for x in w if x.get('image')=='Fusion360.exe' and x.get('title','').upper()=='ADOM')
call('desktop_bring_to_front',{'hwnd':main['hwnd'],'reason':'Record the user requested Fusion routing and thermal design demonstration on CRR'})
try:
 for name,hwnd in [('board',main['hwnd']),('panel',pal['hwnd'])]:
  start=time.time();r=call('desktop_record_window_start',{'hwnd':hwnd,'fps':30,'audio':False,'maxDurationMs':600000,'reason':'Record real 45-degree routing and connected thermal copper on CRR'});assert r.get('success'),r;records.append((name,r));(T/(name+'-start.json')).write_text(json.dumps({'calledAt':start,**r},indent=2))
 stage('unrouted');time.sleep(3)
 for start in range(0,len(items),70):
  end=min(start+70,len(items));stage('routes-'+str(start))
  body="import time,math\nitems="+repr(items)+"\nroot,b,m=snapshot(app,{'expectDocument':target.name})\no=objects(b)\nassert copper_matches(items[:"+str(start)+"],o[2]+o[3]),'Prefix mismatch'\n"
  body+="pal=app.userInterface.palettes.itemById('AdomActivityPalette')\nassert pal and pal.isVisible\n"
  body+="for index in range("+str(start)+","+str(end)+"):\n i=items[index]\n if index%8==0:\n  payload={'title':'Astra: '+i['net'],'detail':'CRR / explicit thermal-aware routing','percent':round(index*80/len(items)),'tone':'done','routing':{'connection':i['net']+' / primitive '+str(index+1)+' of '+str(len(items)),'plan':'Horizontal, vertical and 45 degrees; thermal bottom regions reserved first','checks':'Readback verified through primitive '+str("+str(start)+"),'events':'LINE +0 / VIA / native repaint'}}\n  pal.sendInfoToHTML('update',json.dumps(payload))\n if i['kind']=='segment':\n  cmd=\"LAYER %d; LINE '%s' %.6fmm (%.6fmm %.6fmm) +0 (%.6fmm %.6fmm);\"%(i['layer'],i['net'],i['width'],*i['start'],*i['end'])\n else:\n  cmd=\"CHANGE DRILL %.6fmm; VIA '%s' %.6fmm ROUND 1-16 (%.6fmm %.6fmm);\"%(i['drill'],i['net'],i['diameter'],*i['at'])\n app.executeTextCommand('Electron.run '+cmd)\n app.executeTextCommand('Electron.ImmediateRepaint /full')\n adsk.doEvents()\n time.sleep(.045)\n"
  body+="app.executeTextCommand('Electron.run RATSNEST;')\nroot,b,m=snapshot(app,{'expectDocument':target.name})\no=objects(b)\nresult={'completed':"+str(end)+",'verified':copper_matches(items[:"+str(end)+"],o[2]+o[3])}\nassert result['verified']"
  r=native(body);assert r['verified'];print('Verified',end,'of',len(items),flush=True)
 stage('routed');command('DISPLAY NONE; DISPLAY 1 17 18 20 21; WINDOW (-1mm -1mm) (52mm 28mm);','Clean top-layer routing','576 planned segments / 89 vias','Angles constrained; final DRC follows pours',80);time.sleep(4)
 for name,net,bounds in THERMAL:
  if name=='IC1':continue
  x,y,z,w=bounds;cmd=f"DISPLAY NONE; DISPLAY 16 17 18 20; 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 ({(x+z)/2}mm {y}mm); CHANGE THERMALS OFF ({(x+z)/2}mm {y}mm); CHANGE ORPHANS ON ({(x+z)/2}mm {y}mm); CHANGE RANK 1 ({(x+z)/2}mm {y}mm); RATSNEST;"
  stage('thermal-'+name);command(cmd,'Heat-spreading copper',name+' / '+net+' / bottom layer','Correct pad net / existing thermal holes retained',86);time.sleep(3)
 for l in [1,2,15,16]:
  cmd=f"DISPLAY NONE; DISPLAY {l} 17 18 20; LAYER {l}; POLYGON 'PGND' 0.1mm (0mm 0mm) +0 (50.4mm 0mm) +0 (50.4mm 26.4mm) +0 (0mm 26.4mm) +0 (0mm 0mm); CHANGE ISOLATE 0.1mm (25mm 0mm); CHANGE THERMALS OFF (25mm 0mm); CHANGE ORPHANS ON (25mm 0mm); CHANGE RANK 6 (25mm 0mm); RATSNEST;"
  stage('ground-'+str(l));command(cmd,'Retain remaining copper','Ground fill / layer '+str(l),'Priority thermal regions stay on their correct nets',92);time.sleep(3)
 stage('thermal-closeup');command('DISPLAY NONE; DISPLAY 16 17 18 20; WINDOW (7mm 8mm) (16mm 26mm);','Thermal paths, not floating islands','Q1, Q2 and Q4 / connected bottom copper','35 MOSFET footprint thermal holes preserved',95);time.sleep(5)
 stage('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.json').write_text(json.dumps(r,indent=2));assert r['drc']['airwireCount']==0 and r['drc']['errorCount']==1 and r['drc']['warningCount']==0,r['drc'];assert r['drc']['errors'][0]['signature']=='5,1,f10227fd41e0ab5d'
 r=native("root,b,m=snapshot(app,{'expectDocument':target.name})\np=Path('C:/Users/john/AppData/Local/Temp/adom-thermal/bq25792-astra-thermal-routed.brd')\nET.ElementTree(root).write(p,encoding='utf-8',xml_declaration=True)\napp.executeTextCommand('Document.CopyToDesktop C:/Users/john/AppData/Local/Temp/adom-thermal/bq25792-astra-thermal-routed.fbrd')\nresult={'path':p.as_posix()}")
 call('pull_file',{'filePaths':[r['path'],'C:/Users/john/AppData/Local/Temp/adom-thermal/bq25792-astra-thermal-routed.fbrd'],'saveTo':str(T)})
 stage('final');command('DISPLAY NONE; DISPLAY 16 17 18 20; WINDOW (-1mm -1mm) (52mm 28mm);','Native checks complete','0 airwires / 0 warnings / 0 new errors','1 inherited clearance error; load and thermal qualification remain',100);time.sleep(6)
finally:
 paths=[]
 for name,r in records:
  stop=call('desktop_record_window_stop',{'recordingId':r['recordingId']});(T/(name+'-stop.json')).write_text(json.dumps(stop,indent=2));assert stop.get('success'),stop;paths.append(stop['path'])
 if paths:print(call('pull_file',{'filePaths':paths,'saveTo':str(T)}),flush=True)
print('Recorded and natively validated on CRR',flush=True)