main
John Lauer Corrected Astra ESC routing: native zero-open result, full accounting and voiceover handoff a80479d 23d ago
from planner import *
import planner as m
# Component-aware search can start and end on copper on either layer.
def components(net):
 ps=by[net];nodes=[dict(g=p['g'],zs=[0,1] if p['type']=='thru_hole' else [0],pad=p) for p in ps]+[dict(c) for c in m.copper if c['net']==net]
 par=list(range(len(nodes)))
 def find(i):
  while par[i]!=i:par[i]=par[par[i]];i=par[i]
  return i
 tree=shapely.STRtree([c['g'] for c in nodes])
 for i,c in enumerate(nodes):
  for j in tree.query(c['g'],predicate='dwithin',distance=.004999):
   if j<i and set(c['zs'])&set(nodes[j]['zs']):par[find(i)]=find(int(j))
 out=defaultdict(list)
 for i,c in enumerate(nodes):out[find(i)].append(c)
 return [v for v in out.values() if any('pad' in c for c in v)]
def register2(net,points,width):
 z=1 if isinstance(points[0],dict) and points[0].get('layer')=='B.Cu' else 0
 for p,q in zip(points,points[1:]):
  zz=q.get('layer','B.Cu' if z else 'F.Cu') if isinstance(q,dict) else ('B.Cu' if z else 'F.Cu')
  pos=lambda v:(v['x'],v['y']) if isinstance(v,dict) else v
  a,b=pos(p),pos(q)
  if a!=b:m.copper.append(dict(net=net,g=LineString([a,b]).buffer(width/2),zs=[z]))
  if isinstance(q,dict) and zz!=('B.Cu' if z else 'F.Cu'):
   m.copper.append(dict(net=net,g=Point(*b).buffer(.4),zs=[0,1]));z=1 if zz=='B.Cu' else 0

def search_components(net,width,source,target):
 a=masks(net,width);start=np.zeros((2,H,W),dtype=np.uint8);goal=np.zeros_like(start)
 for collection,arr in [(source,start),(target,goal)]:
  for c in collection:
   for z in c['zs']:raster(arr[z],c['g'].buffer(width/2-.005))
  arr[a[:2]>0]=0
 ids=np.flatnonzero(start)
 if not len(ids) or not np.any(goal):return None
 g=unary_union([c['g'] for c in target]);t=g.centroid;tx,ty=ij(t.x,t.y)
 a.tofile('mask.bin');goal.tofile('goal.bin')
 r=subprocess.run(['./search2'],input=f'{W} {H} 0 {tx} {ty} {len(ids)} '+' '.join(map(str,ids)),capture_output=True,text=True)
 raw=json.loads(r.stdout)
 if not raw:return None
 out=[];curz=0
 for i,j,z in compress(raw):
  x,y=xy(i,j)
  if z!=curz or (not out and z):out.append(dict(x=x,y=y,layer='B.Cu' if z else 'F.Cu'))
  elif not out or [x,y]!=out[-1]:out.append([x,y])
  curz=z
 return out

def route_components(plan):
 net=plan['net'];width=plan['width'];cs=components(net)
 while len(cs)>1:
  pairs=sorted((unary_union([c['g'] for c in x]).distance(unary_union([c['g'] for c in y])),i,j) for i,x in enumerate(cs) for j,y in enumerate(cs[:i]))
  found=False
  for _,i,j in pairs:
   route=search_components(net,width,cs[i],cs[j])
   if route:
    register2(net,route,width);plan['paths'].append(route);found=True;break
  if not found:break
  cs=components(net)
 return len(cs)-1

def load(plans):
 m.copper.clear()
 for p in plans:
  for path in p['paths']:register2(p['net'],path,p['width'])
if __name__=='__main__':
 import sys
 plans=json.load(open(sys.argv[1]));load(plans)
 for plan in plans:
  if plan['net'] in ['GND','+3V3']:continue
  gaps=route_components(plan);print(plan['net'],gaps,flush=True)
  Path('component-repaired.json').write_text(json.dumps(plans,indent=2))