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
Add native thermal-pour expansion experiment, finite-difference comparison and recording scripts
33c4ac5
1mo ago
"""Connected copper measurement with drill subtraction and no polygon-hole shadowing."""
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();NETS={'VAC1':['Q1'],'VBUS':['Q2','Q4'],'VAC2':['Q3'],'VBAT':['Q5']}
def ring(e):
v=e.findall('vertex');assert v and not any(float(x.get('curve','0')) for x in v)
return [(float(x.get('x')),float(x.get('y'))) for x in v]
def copper(file,layer=16):
b=ET.parse(file).find('drawing/board');cu=defaultdict(list)
drill_voids=[Point(at).buffer(r,quad_segs=32) for at,r in M.holes]
for p in M.pads.values():
if layer 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'))
drill_voids.append(at.buffer(dr/2,quad_segs=32))
if v.get('extent')=='1-16':cu[n].append(at.buffer(dia/2,quad_segs=32))
for w in s.findall('wire'):
if int(w.get('layer'))!=layer:continue
assert not float(w.get('curve','0'))
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=32))
for p in s.findall('polygonpour'):
if int(p.get('layer'))!=layer:continue
shapes=p.findall('polygonfilldetails/polygonshape');assert shapes,'Refilled contours required'
for shape in shapes:
outer=shape.find('polygonoutlinesegments')
if outer is None:outer=shape.find('polygonoutlineobjects')
if outer is None:continue
polygon_holes=[ring(h) for h in shape.findall('polygonholelist/polygonholesegments')]
cu[n].append(make_valid(Polygon(ring(outer),polygon_holes)))
drill=unary_union(drill_voids)
return {n:unary_union(gs).difference(drill) for n,gs in cu.items()}
def regions(file):
cu=copper(file);out={}
for net,qs in NETS.items():
pads=[p for p in M.pads.values() if any(p['name'].startswith(q+'.P$') for q in qs)]
assert {p['net'] for p in pads}=={net}
u=cu[net];parts=list(u.geoms) if hasattr(u,'geoms') else [u]
g=unary_union([g for g in parts if any(g.intersects(p['shape']) for p in pads)])
out[net]={'geometry':g,'sources':unary_union([p['shape'] for p in pads]),'components':qs,'areaMm2':g.area,'allNetAreaMm2':u.area,'bounds':list(g.bounds)}
return out
if __name__=='__main__':
f=Path(sys.argv[1]);r=regions(f);data={n:{k:v for k,v in x.items() if k not in ['geometry','sources']} for n,x in r.items()};print(json.dumps(data,indent=2));f.with_suffix('.areas.json').write_text(json.dumps(data,indent=2))