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
"""BQ-specific estimate of bottom copper physically connected to footprint thermal pads."""
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()
def ring(e):
vs=e.findall('vertex');assert vs and not any(v.get('curve') for v in vs)
return [(float(v.get('x')),float(v.get('y'))) for v in vs]
def measure(file):
b=ET.parse(file).find('drawing/board');cu=defaultdict(list)
holes=[Point(at).buffer(r,quad_segs=24) for at,r in m.holes]
for p in m.pads.values():
if 16 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'))
holes.append(at.buffer(dr/2,quad_segs=24))
if v.get('extent')=='1-16':cu[n].append(at.buffer(dia/2,quad_segs=24))
for w in s.findall('wire'):
if w.get('layer')!='16':continue
if float(w.get('curve','0')):raise ValueError('Arc needs tessellation')
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=24))
for p in s.findall('polygonpour'):
if p.get('layer')!='16':continue
shapes=p.findall('polygonfilldetails/polygonshape');assert shapes,'Native fill contours missing: refill and export before measuring'
for shape in shapes:
outer=shape.find('polygonoutlinesegments')
if outer is None:outer=shape.find('polygonoutlineobjects')
if outer is None:continue
holes=[ring(h) for h in shape.findall('polygonholelist/polygonholesegments')]
cu[n].append(make_valid(Polygon(ring(outer),holes)))
drill=unary_union(holes)
result=[]
for q in ['Q1','Q2','Q3','Q4','Q5']:
pads=[p for p in m.pads.values() if p['name'].startswith(q+'.P$')];net=pads[0]['net'];u=unary_union(cu[net]).difference(drill);parts=list(u.geoms) if hasattr(u,'geoms') else [u];connected=[g for g in parts if any(g.intersects(p['shape']) for p in pads)];area=unary_union(connected).area
result.append({'component':q,'thermalNet':net,'footprintThermalHoles':len(pads),'bottomConnectedCopperEstimateMm2':area,'connectedRegions':len(connected)})
return result
if __name__=='__main__':
files=[P.parent/'ablation/take-02/bq25792-ablation-routed.brd',P/'recording-02/bq25792-astra-thermal-routed.brd'];r={str(f):measure(f) for f in files};(P/'thermal-area-comparison.json').write_text(json.dumps(r,indent=2));print(json.dumps(r,indent=2))