skill
EDA Skillpack
Public Made by Adomby adom
All skills for all EDA capabilities across the Adom ecosystem.
main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
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)
from shapely.ops import nearest_points
for ca in source:
for cb in target:
if ca['g'].distance(cb['g'])>2:continue
pa,pb=nearest_points(ca['g'],cb['g'])
if pa.distance(pb)<.00001:continue
for z in set(ca['zs'])&set(cb['zs']):
line=LineString([(pa.x,pa.y),(pb.x,pb.y)])
pts=[line.interpolate(t,normalized=True) for t in np.linspace(0,1,max(3,int(line.length/.01)+1))]
if all(not a[z,ij(pt.x,pt.y)[1],ij(pt.x,pt.y)[0]] for pt in pts):
first={'x':pa.x,'y':pa.y,'layer':'B.Cu'} if z else [pa.x,pa.y]
return [first,[pb.x,pb.y]]
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:
before=len(m.copper);register2(net,route,width)
if len(components(net))>=len(cs):
del m.copper[before:];continue
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))