app
Fusion - the Fusion 360 Bridge
Public Made by Adomby adom
Drive Autodesk Fusion 360 from the cloud via Adom Bridge: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling.
master
John Lauer
Publish thermal bottleneck planner, accepted candidate, analysis and native recording scripts
0049da5
28d ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657
"""Evaluate layer relocations against fixed pads/tracks; native refill/DRC is authoritative."""
import sys,copy,json,xml.etree.ElementTree as ET
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
BASE=P.parent/'thermal-expansion/recording/bq25792-expanded-thermal.brd'
t=ET.parse(BASE);b=t.find('drawing/board');m=Geometry()
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))
for n in ['N$1','SDA','VUSB','PGND']:
s=next(s for s in b.findall('signals/signal') if s.get('name')==n)
print(n,'pads',[c.attrib for c in s.findall('contactref')])
for w in s.findall('wire'):
if w.get('layer')!='16':continue
a=[float(w.get('x1')),float(w.get('y1'))];z=[float(w.get('x2')),float(w.get('y2'))];width=float(w.get('width'))
print(a,z,'clear layers',[l for l in [1,2,15] if m.clear([a,z],n,l,width)])
if __name__=='__main__':
sys.path.insert(0,str(P.parent/'thermal'));from plan_priority import detour
tasks=[('N$1',(14.88575625,11.00863125),(15.2,25.2),.15),('SDA',(25.2,1.2),(19.2,15.2),.15),('VUSB',(19.2,25.2),(17.2,21.2),.15)]
routes=[]
for net,a,z,width in tasks:
options=[]
for layer in [15,2,1]:
pts=detour(m,a,z,net,layer,width,limit=18000,step=.2)
print('planned',net,layer,pts,flush=True)
if pts:
options.append((LineString(pts).length,layer,pts));break
if not options:
print('RETAINED: no acceptable single-layer detour',net,flush=True);continue
# Prefer inner 15 when available: keep thermal exterior clear and use layer2 as reference.
options.sort(key=lambda q:(q[1]!=15,q[1]==1,q[0]));length,layer,pts=options[0]
s=next(s for s in b.findall('signals/signal') if s.get('name')==net)
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 a,z in zip(pts,pts[1:]):ET.SubElement(s,'wire',{'x1':str(a[0]),'y1':str(a[1]),'x2':str(z[0]),'y2':str(z[1]),'width':str(width),'layer':str(layer)})
routes.append({'net':net,'layer':layer,'points':pts,'width':width,'removed':removed,'newVias':[],'lengthMm':length})
m.add_route(net,[(layer,pts,width)],[])
# Selected connector ground stubs: relocate only left/bottom sections to inner2, same width.
s=next(s for s in b.findall('signals/signal') if s.get('name')=='PGND')
for w in list(s.findall('wire')):
if w.get('layer')!='16':continue
a=[float(w.get('x1')),float(w.get('y1'))];z=[float(w.get('x2')),float(w.get('y2'))];width=float(w.get('width'))
if max(a[0],z[0])>18 or max(a[1],z[1])>6:continue
assert m.clear([a,z],'PGND',2,width)
removed=dict(w.attrib);w.set('layer','2');routes.append({'net':'PGND','layer':2,'points':[a,z],'width':width,'removed':[removed],'newVias':[],'lengthMm':LineString([a,z]).length})
for p in b.findall('signals/signal/polygonpour'):
for fill in p.findall('polygonfilldetails'):p.remove(fill)
(P/'candidate-01').mkdir(exist_ok=True);t.write(P/'candidate-01/bq25792-thermal-reroute.brd',encoding='utf-8',xml_declaration=True)
(P/'candidate-01/plan.json').write_text(json.dumps(routes,indent=2))