main
John Lauer Publish independent Astra ESC source and full-pad Kelvin review lesson d057af8 22d ago
from component_search import *
from soft_search2 import search_soft
import soft_search2 as soft
import ast
from route_search import search2
import copy,sys,random
allplans=json.load(open('soft3-current.json'))
fixed=[p for p in allplans if p.get('stage')=='kelvin']
plans=[p for p in allplans if p.get('stage')!='kelvin']
by['GND_OUT']=[p for p in by['GND_OUT'] if p['name']!='U6.4']
original_load=load
def load(plans):
 original_load(plans)
 for p in fixed:
  for path in p['paths']:register2('__kelvin_'+p['net'],path,p['width'])
load(plans)
def gaps():return {p['net']:len(components(p['net']))-1 for p in plans if p['net'] not in ['GND','+3V3'] and len(components(p['net']))>1}
def pathgeo(plan,path):
 old=list(m.copper);m.copper.clear();register2(plan['net'],path,plan['width']);cs=list(m.copper);m.copper[:]=old;return cs
best=sum(gaps().values());print('INITIAL',gaps(),flush=True)
attempts=defaultdict(int)
for iteration in range(80):
 m.X0=104.0+.025*(iteration%2);m.Y0=58.0+.025*((iteration//2)%2)
 missing=gaps()
 if not missing:break
 net=min(missing,key=lambda n:attempts[n]);attempts[net]+=1;plan=next(p for p in plans if p['net']==net)
 before=copy.deepcopy(plans);old=list(m.copper)
 cs=components(net);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]))
 # Search against pads, planes, and this net, then identify exact donor paths.
 m.copper[:]=old
 candidates=[]
 for _,i,j in pairs:
  route=search_soft(net,plan['width'],cs[i],cs[j],allow_planes=(iteration%3==0))
  if route:candidates.append(route)
  if len(candidates)>=3:break
 m.copper[:]=old
 if not candidates:
  for _,i,j in pairs:
   route=search_soft(net,plan['width'],cs[i],cs[j],allow_planes=True)
   if route:candidates.append(route);break
 if not candidates:
  print(iteration,'NO CANDIDATE',net,flush=True);continue
 scored=[]
 for route in candidates:
  new=pathgeo(plan,route);donors=[]
  for q in plans:
   if q['net']==net:continue
   for ix,path in enumerate(q['paths']):
    gc=pathgeo(q,path)
    if any(set(a['zs'])&set(b['zs']) and a['g'].distance(b['g'])<.205 for a in new for b in gc):donors.append((q['net'],ix))
  scored.append((len(donors),route,donors))
 _,route,donors=min(scored,key=lambda x:x[0]);print(iteration,'RIP',net,donors,flush=True)
 plane_pads=[]
 for n,i in donors:
  if n in ['GND','+3V3']:
   q=next(p for p in plans if p['net']==n);v=q['paths'][i][0];pt=Point((v['x'],v['y']) if isinstance(v,dict) else v);plane_pads.append(min((p for p in by[n] if p['type']!='thru_hole' and p['name']!='U6.3'),key=lambda p:p['g'].distance(pt)))
 for q in plans:q['paths']=[p for i,p in enumerate(q['paths']) if (q['net'],i) not in donors]
 load(plans)
 # Recheck the route against all remaining copper before registering it.
 # Exact paths above came from the hard pad/plane mask; all conflicts removed.
 register2(net,route,plan['width']);plan['paths'].append(route)
 plane_ok=True
 for p in plane_pads:
  q=next(q for q in plans if q['net']==p['net'])
  stub=search2(masks(p['net'],.25),p,(p['x'],p['y']),[],plane=True)
  if not stub:plane_ok=False;break
  e=stub[-1];stub.append(dict(x=e[0],y=e[1],layer='In1.Cu' if p['net']=='GND' else 'In2.Cu'));register2(p['net'],stub,.25);q['paths'].append(stub)
 if not plane_ok:
  plans=before;load(plans);print('REVERT plane stub blocked',flush=True);continue
 todo=list(dict.fromkeys(n for n,i in donors if n not in ['GND','+3V3']))
 for n in todo:
  q=next(p for p in plans if p['net']==n);route_components(q)
 for c in pathgeo(plan,route):
  for z in c['zs']:
   tmp=np.zeros((H,W),dtype=np.uint16);raster(tmp,c['g'].buffer(.205),5);soft.history[z]=np.minimum(soft.history[z]+tmp,2000)
 remaining=gaps();score=sum(remaining.values());print('RESULT',score,remaining,flush=True)
 Path('soft3-current.json').write_text(json.dumps(fixed+plans,indent=2))
 if score<best:
  best=score;Path('soft3-best.json').write_text(json.dumps(fixed+plans,indent=2));print('BEST',best,flush=True)
 if score>max(best+3,5):
  plans=before;load(plans);print('REVERT regression',flush=True)