master
John Lauer Publish thermal bottleneck planner, accepted candidate, analysis and native recording scripts 0049da5 29d ago
import sys,json,xml.etree.ElementTree as ET,math
from pathlib import Path
from shapely.geometry import LineString,Point
P=Path(__file__).resolve().parent;sys.path.insert(0,str(P.parent));from geometry import Geometry,LAYERS
sys.path.insert(0,str(P.parent/'thermal'));from plan_priority import detour,simple
m=Geometry();t=ET.parse(P/'candidate-01/filled.brd');b=t.find('drawing/board')
for s in b.findall('signals/signal'):
 n=s.get('name')
 for w in s.findall('wire'):
  a=[float(w.get('x1')),float(w.get('y1'))];z=[float(w.get('x2')),float(w.get('y2'))]
  m.copper.append((n,int(w.get('layer')),LineString([a,z]).buffer(float(w.get('width'))/2)))
 for v in s.findall('via'):
  a=[float(v.get('x')),float(v.get('y'))];dia=float(v.get('diameter'))
  for l in LAYERS:m.copper.append((n,l,Point(a).buffer(dia/2)))
  m.holes.append((a,float(v.get('drill'))/2))
net='SDA';s=next(s for s in b.findall('signals/signal') if s.get('name')==net)
line=LineString([(25.2,1.2),(22.2,4.2),(22.2,12.2),(19.2,15.2)])
starts=[];ends=[]
for distance in [.9,1.2,1.6,2.,2.5,3.,4.,5.,6.]:
 for dest,at in [(starts,line.interpolate(distance)),(ends,line.interpolate(line.length-distance))]:
  p=(at.x,at.y)
  if m.via_clear(p,net):dest.append(p)
print('via sites',starts,ends,flush=True)
chosen=None
for a in starts[:3]:
 for z in ends[:3]:
  for l in [15,2,1]:
   pts=detour(m,a,z,net,l,.15,limit=45000,step=.1)
   print(a,z,l,pts,flush=True)
   if pts:chosen=(a,z,l,pts);break
  if chosen:break
 if chosen:break
assert chosen,'No validated candidate path'
a,z,l,pts=chosen
# preserve original bottom path before/after the hop, including existing vertices.
coords=list(line.coords)
da=line.project(Point(a));dz=line.project(Point(z))
pre=[coords[0]]+[p for p in coords[1:-1] if line.project(Point(p))<da]+[a]
post=[z]+[p for p in coords[1:-1] if line.project(Point(p))>dz]+[coords[-1]]
removed=[dict(w.attrib) for w in s.findall('wire') if w.get('layer')=='16']
for w in list(s.findall('wire')):
 if w.get('layer')=='16':s.remove(w)
for layer,points in [(16,pre),(l,pts),(16,post)]:
 for x,y in zip(points,points[1:]):ET.SubElement(s,'wire',{'x1':str(x[0]),'y1':str(x[1]),'x2':str(y[0]),'y2':str(y[1]),'width':'.15','layer':str(layer)})
for p in [a,z]:ET.SubElement(s,'via',{'x':str(p[0]),'y':str(p[1]),'extent':'1-16','drill':'.2','diameter':'.45'})
for p in b.findall('signals/signal/polygonpour'):
 for f in p.findall('polygonfilldetails'):p.remove(f)
out=P/'candidate-02';out.mkdir(exist_ok=False);t.write(out/'bq25792-thermal-reroute.brd',encoding='utf-8',xml_declaration=True)
r=json.loads((P/'candidate-01/plan.json').read_text());r.append({'net':'SDA','layer':l,'points':pts,'width':.15,'removed':removed,'newVias':[a,z],'bottomParts':[pre,post],'lengthMm':sum(LineString(p).length for p in [pre,pts,post])});(out/'plan.json').write_text(json.dumps(r,indent=2))