main
John Lauer Publish independent Astra ESC source and full-pad Kelvin review lesson d057af8 23d ago
from geometry import *
from shapely.ops import unary_union
from collections import defaultdict
by=defaultdict(list)
for p in pads:
 if p['net']:by[p['net']].append(p)
outline=box(107,61,165,129).buffer(3).buffer(-.5)
zones=[]
# Reference planes are separate prescribed layers; these are the new outer finishing pours.
for net in ['GND','+VBAT','GND_OUT','/DRV_SHA','/DRV_SHB','/DRV_SHC']:
 if net=='GND':g=outline;priority=0;connection='thermal'
 else:
  relevant=[p for p in by[net] if p['name'].startswith(('Q','C2.','C3.','R32.','MC'))]
  if net.startswith('/DRV_SH'):relevant=[p for p in relevant if p['name'].startswith(('Q','MC'))]
  g=unary_union([p['g'] for p in relevant]).convex_hull.buffer(.75 if net.startswith('/DRV_SH') else 1.5).intersection(outline)
  priority=3 if net.startswith('/DRV_SH') else 2;connection='solid'
 zones.append({'net':net,'layers':['F.Cu','B.Cu'],'polygon':[[round(x,5),round(y,5)] for x,y in g.exterior.coords[:-1]],'clearance':.2,'minThickness':.25,'priority':priority,'connection':connection,'thermalGap':.3,'thermalBridge':.35,'name':'Astra outer '+net})
# High-current ground connections need solid local patches, not narrow thermal spokes.
for ref in ['Q2','Q4','Q6','R32','C2','C3']:
 ps=[p for p in by['GND'] if p['name'].split('.')[0]==ref]
 g=unary_union([p['g'] for p in ps]).convex_hull.buffer(.8).intersection(outline)
 zones.append({'net':'GND','layers':['F.Cu','B.Cu'],'polygon':[[round(x,5),round(y,5)] for x,y in g.exterior.coords[:-1]],'clearance':.2,'minThickness':.25,'priority':1,'connection':'solid','name':'Astra solid power return '+ref})
# Pour-only keepouts stop same-net GND copper from swallowing the sense taps.
plans=json.load(open('soft3-current.json'))
for p in plans:
 if p.get('stage')!='kelvin':continue
 path=p['paths'][0];g=LineString(path).buffer(.5,cap_style=2,join_style=2)
 shunt=next(q for q in by[p['net']] if q['name'].startswith('R32.'))
 g=g.difference(shunt['g'].buffer(-.1))
 for piece in ([g] if g.geom_type=='Polygon' else list(g.geoms)):
  zones.append({'type':'keepout','layers':['F.Cu','B.Cu'],'polygon':[[round(x,5),round(y,5)] for x,y in piece.exterior.coords[:-1]],'keepout':{'copperPour':True},'name':'Astra Kelvin isolation '+p['net']})
patches=[z for z in zones if z.get('net')=='GND' and z.get('priority')==1]
merged=unary_union([Polygon(z['polygon']) for z in patches]);zones=[z for z in zones if z not in patches]
for i,g in enumerate([merged] if merged.geom_type=='Polygon' else merged.geoms):
 assert not g.interiors
 zones.append(dict(patches[0],polygon=[[round(x,5),round(y,5)] for x,y in g.exterior.coords[:-1]],name='Astra solid return cluster '+str(i+1)))
Path('outer-pours.json').write_text(json.dumps(zones,indent=2));print('candidate zone objects',len(zones))