master
John Lauer Publish thermal bottleneck planner, accepted candidate, analysis and native recording scripts 0049da5 28d ago
"""Record native trace relocation, refill and thermal net inspection on a task copy."""
import sys,json,time,shutil
from pathlib import Path
P=Path(__file__).resolve().parent;sys.path.insert(0,str(P.parent/'thermal'));from transport import call,execute,HOST_OUTPUT
D='bq25792-thermal-reroute-live';T=P/'recording';events=[];rec=None
routes=json.loads((P/'candidate-01/plan.json').read_text())
def native(body):return execute(D,body)
def command(cmd):return native("app.executeTextCommand("+repr('Electron.run '+cmd)+")\napp.executeTextCommand('Electron.ImmediateRepaint /full')\nadsk.doEvents()\nresult={'ok':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':'Thermal bottleneck rerouting / CRR','plan':'Restore broad bottom heat paths; preserve electrical connectivity','checks':'Candidate checked; live replay verification follows','events':'RIPUP / LINE / 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)}")
# Restore the precisely inspected one-segment probe before beginning the take.
s=native("result=handle(app,{'expectDocument':target.name},'routing_state')")
if s['segmentCount']==575:
 command("LAYER 16; LINE 'N$1' .15mm (15.2mm 11.322875mm) +0 (15.2mm 25.2mm); RATSNEST;")
s=native("result=handle(app,{'expectDocument':target.name},'routing_state')");assert (s['segmentCount'],s['viaCount'],s['pourCount'],s['airwireCount'])==(576,89,12,0)
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 Astra thermal bottleneck routing improvement on CRR'})
try:
 rec=call('desktop_record_window_start',{'hwnd':main['hwnd'],'fps':30,'audio':False,'maxDurationMs':480000,'reason':'Record native trace relocation and heat-spreading copper refill'});assert rec.get('success'),rec;(T/'start.json').write_text(json.dumps(rec,indent=2))
 banner('Find the heat-flow obstruction','A large pour is not useful if its connection is narrow')
 command('DISPLAY NONE; DISPLAY 16 17 18 20; WINDOW (-1mm -1mm) (52mm 28mm);');mark('before');time.sleep(4)
 command('WINDOW (7mm 9mm) (22mm 27mm); SHOW VAC1 VBUS');mark('neck');time.sleep(6)
 for net in ['N$1','VUSB','PGND']:
  selected=[r for r in routes if r['net']==net]
  banner('Relocate '+net,'Reuse existing plated endpoints; no additional via holes needed')
  command('DISPLAY NONE; DISPLAY 16 17 18 20; WINDOW (-1mm -1mm) (52mm 28mm); SHOW '+net);mark('before-'+net);time.sleep(3)
  command('DISPLAY NONE; DISPLAY 16 17 18 20; LAYER 16; SET CONFIRM YES;')
  for r in selected:
   for w in r['removed']:
    x=(float(w['x1'])+float(w['x2']))/2;y=(float(w['y1'])+float(w['y2']))/2
    command(f'RIPUP ({x:.8f}mm {y:.8f}mm);')
  mark('removed-'+net)
  command('DISPLAY NONE; DISPLAY '+str(selected[0]['layer'])+' 17 18 20;')
  for r in selected:
   for a,z in zip(r['points'],r['points'][1:]):
    command("LAYER %d; LINE '%s' %.6fmm (%.6fmm %.6fmm) +0 (%.6fmm %.6fmm);"%(r['layer'],net,r['width'],*a,*z));time.sleep(.12)
  command('RATSNEST; SHOW '+net);mark('routed-'+net);time.sleep(5)
  command('DISPLAY NONE; DISPLAY 16 17 18 20; SHOW '+('VAC1 VBUS' if net in ['N$1','VUSB'] else 'VAC2'));mark('thermal-'+net);time.sleep(5)
 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/'native-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=HOST_OUTPUT+'/live/bq25792-thermal-bottlenecks'
 r=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={'boardBytes':Path("+repr(host+'.brd')+").stat().st_size,'nativeBytes':Path("+repr(host+'.fbrd')+").stat().st_size}")
 assert r['boardBytes']>100 and r['nativeBytes']>100
 pull=call('pull_file',{'filePaths':[host+'.brd',host+'.fbrd'],'saveTo':str(T)});(T/'exports.json').write_text(json.dumps(pull,indent=2))
 for ext in ['brd','fbrd']:
  f=T/('bq25792-thermal-bottlenecks.'+ext)
  if not f.exists():shutil.copy2(Path('/mnt/c')/str(f).lstrip('/'),f)
 for net in ['VAC1','VBUS','VAC2']:
  banner('Verified '+net,'Compare the same source temperature and ambient assumptions')
  command('DISPLAY NONE; DISPLAY 16 17 18 20; SHOW '+net);mark('final-'+net);time.sleep(5)
 banner('Checkpoint complete','Zero airwires; zero new DRC errors; scratch document will close')
 command('SHOW VAC1 VBUS VAC2 VBAT');mark('final');time.sleep(6)
finally:
 if rec and rec.get('recordingId'):
  stop=call('desktop_record_window_stop',{'recordingId':rec['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)
# Only close after durable verified replay exports exist.
if (T/'bq25792-thermal-bottlenecks.fbrd').is_file():
 r=call('fusion_close_document',{'name':D,'save':False});assert r.get('success'),r
 (T/'cleanup.json').write_text(json.dumps(call('fusion_get_app_state',{}),indent=2))