AnasSarkiz/NEMA14_RP2040

A NEMA motor assembly concept with a controller board, four proposed M3 screws and spacers, motor lead splices, and a JST 4-pin connector harness.

Version
1.13.0
License
ISC
Stars
0

scripts/repair-supplier-routing.py

"""Add selected missing connections; check the result independently afterward."""
import verify_supplier_connectivity as g
import json,math,heapq,numpy as np
from PIL import Image,ImageDraw
j=g.j;ROOT=g.ROOT;path=g.path;ports=g.ports;sp=g.sp;comp=g.comp;src=g.src
STEP=.025;LOW=-17.5;N=1401;layers=['top','inner2','bottom']
xy=lambda x,y:(round((x-LOW)/STEP),round((y-LOW)/STEP))
world=lambda p:(LOW+p[0]*STEP,LOW+p[1]*STEP)
key=g.key
traces={e['subcircuit_connectivity_map_key']:e['source_trace_id'] for e in j if e['type']=='source_trace'}
nets={e['subcircuit_connectivity_map_key']:e for e in j if e['type']=='source_net'}
def findport(c,p):return next(e for e in ports.values() if src[comp[e['pcb_component_id']]['source_component_id']]==c and sp[e['source_port_id']]['name']==p)
def obstacle(net,width,via=False):
 imgs=[Image.new('L',(N,N)) for l in layers];draws=[ImageDraw.Draw(i) for i in imgs];margin=.15+(.20 if via else width/2)+.005
 def add(l,geom):
  if l not in layers:return
  geom=geom.buffer(margin)
  if geom.geom_type=='Polygon':draws[layers.index(l)].polygon([xy(x,y) for x,y in geom.exterior.coords],fill=1)
 for e in j:
  typ=e['type']
  if typ in ['pcb_smtpad','pcb_plated_hole']:
   if key(e)==net and not via:continue
   for l in e.get('layers',[e.get('layer','top')]):add(l,g.geometry(e))
  elif typ=='pcb_via':
   if key(e)==net and not via:continue
   for l in layers:add(l,g.Point(e['x'],e['y']).buffer(e['outer_diameter']/2))
  elif typ=='pcb_trace' and key(e)!=net:
   for a,b in zip(e['route'],e['route'][1:]):
    if a['route_type']==b['route_type']=='wire' and a['layer']==b['layer']:add(a['layer'],g.LineString([(a['x'],a['y']),(b['x'],b['y'])]).buffer(max(a['width'],b['width'])/2))
  elif typ=='pcb_keepout':
   c=e['center'];shape=g.box(c['x']-e['width']/2,c['y']-e['height']/2,c['x']+e['width']/2,c['y']+e['height']/2)
   for l in layers:add(l,shape)
  elif typ=='pcb_hole':
   for l in layers:add(l,g.Point(e['x'],e['y']).buffer(e['hole_diameter']/2+.1))
 border=.3+(.20 if via else width/2)
 for d in draws:
  b=math.ceil(border/STEP);d.rectangle([0,0,N-1,b],fill=1);d.rectangle([0,N-1-b,N-1,N-1],fill=1);d.rectangle([0,0,b,N-1],fill=1);d.rectangle([N-1-b,0,N-1,N-1],fill=1)
 return [np.asarray(im,dtype=np.bool_) for im in imgs]
repairs=[]
for cname,pname in [('U_MCU','QSPI_SD0')]:
 p=findport(cname,pname);net=sp[p['source_port_id']]['subcircuit_connectivity_map_key'];width=.16
 elems,roots=g.groups(net);startroot=next(r for e,r in zip(elems,roots) if e.get('pcb_port_id')==p['pcb_port_id']);goals={}
 for e,r in zip(elems,roots):
  if r==startroot:continue
  if e['type'] in ['pcb_smtpad','pcb_plated_hole']:
   pp=ports[e['pcb_port_id']]
   for l in e.get('layers',[e.get('layer','top')]):
    if l in layers:goals[(*xy(pp['x'],pp['y']),layers.index(l))]=(pp['x'],pp['y'])
  elif e['type']=='pcb_trace':
   for a in e['route']:
    if a['route_type']=='wire' and a['layer'] in layers:goals[(*xy(a['x'],a['y']),layers.index(a['layer']))]=(a['x'],a['y'])
 assert goals,'No disconnected target island'
 blocked=obstacle(net,width);vb=obstacle(net,width,True);start=(*xy(p['x'],p['y']),0)
 pts=np.asarray(list(goals));lo=pts[:,:2].min(axis=0);hi=pts[:,:2].max(axis=0)
 def heuristic(s):return max(lo[0]-s[0],0,s[0]-hi[0])+max(lo[1]-s[1],0,s[1]-hi[1])
 heap=[(heuristic(start),0,start)];best={start:0};prev={};end=None;expanded=0
 while heap:
  _,cost,s=heapq.heappop(heap)
  if cost!=best.get(s):continue
  if s in goals:end=s;break
  expanded+=1
  if expanded>1200000:break
  x,y,l=s;options=[(x+dx,y+dy,l,1.41421356 if dx and dy else 1) for dx,dy in [(1,0),(-1,0),(0,1),(0,-1),(1,1),(1,-1),(-1,1),(-1,-1)]]
  if all(not v[y,x] for v in vb):options += [(x,y,nl,45) for nl in range(3) if nl!=l]
  for nx,ny,nl,dc in options:
   if not(0<=nx<N and 0<=ny<N) or blocked[nl][ny,nx]:continue
   if nl==l and nx!=x and ny!=y and (blocked[l][y,nx] or blocked[l][ny,x]):continue
   t=(nx,ny,nl);nc=cost+dc
   if nc+1e-8<best.get(t,float('inf')):best[t]=nc;prev[t]=s;heapq.heappush(heap,(nc+heuristic(t),nc,t))
 assert end is not None,f'No compliant path after {expanded} nodes'
 nodes=[end]
 while nodes[-1]!=start:nodes.append(prev[nodes[-1]])
 nodes.reverse();slim=[nodes[0]]
 for i in range(1,len(nodes)-1):
  a,b,c=nodes[i-1:i+2]
  if a[2]!=b[2] or b[2]!=c[2] or (b[0]-a[0],b[1]-a[1])!=(c[0]-b[0],c[1]-b[1]):slim.append(b)
 slim.append(nodes[-1]);route=[];vias=[]
 for i,s in enumerate(slim):
  x,y=world(s);layer=layers[s[2]]
  if i==0:x,y=p['x'],p['y']
  if i==len(slim)-1:x,y=goals[end]
  if route and route[-1]['layer']!=layer:
   old=route[-1]['layer'];route.append({'route_type':'via','x':x,'y':y,'from_layer':old,'to_layer':layer,'via_diameter':.4,'via_hole_diameter':.2});vias.append((x,y))
  route.append({'route_type':'wire','x':x,'y':y,'width':width,'layer':layer})
 route[0]['start_pcb_port_id']=p['pcb_port_id'];tid='supplier_repair_'+cname+'_'+pname
 j.append({'type':'pcb_trace','pcb_trace_id':tid,'route':route,'source_net_id':nets[net]['source_net_id'],'source_trace_id':traces[net],'subcircuit_connectivity_map_key':net,'subcircuit_id':'subcircuit_source_group_0','pcb_port_ids':[p['pcb_port_id']]})
 for i,(x,y) in enumerate(vias):j.append({'type':'pcb_via','pcb_via_id':tid+'_via'+str(i),'pcb_trace_id':tid,'source_trace_id':traces[net],'source_net_id':nets[net]['source_net_id'],'subcircuit_connectivity_map_key':net,'subcircuit_id':'subcircuit_source_group_0','x':x,'y':y,'hole_diameter':.2,'outer_diameter':.4,'layers':['top','inner1','inner2','bottom'],'tented_on_top':True,'tented_on_bottom':True})
 repairs.append({'connection':cname+'.'+pname,'net':nets[net]['name'],'route':route,'expandedNodes':expanded});print(cname,pname,len(route),'points',len(vias),'vias',expanded,'search nodes',flush=True)
path.write_text(json.dumps(j,indent=2)+'\n');(ROOT/'artifacts/supplier-selected-repairs.json').write_text(json.dumps(repairs,indent=2)+'\n')