AnasSarkiz/NEMA14_CH32X035G8U6

A NEMA stepper motor is shown with proposed M3 screws/spacers, four wired motor leads and splices, and a JST SHR-04V-S connector.

Version
1.12.2
License
ISC
Stars
0

scripts/reserve-review-escapes.py

"""Reserve ordinary masked escapes from isolated physical lands; qualify afterward."""
import json,sys,math
from shapely.strtree import STRtree
from shapely.ops import unary_union
import verify_supplier_connectivity as g
stack=['top','inner1','inner2','bottom'];j=g.j
rows=[]
def index():
 a=[];o=[];ls=[]
 def add(e,p,l):a.append(p);o.append(e);ls.append(l)
 for e in j:
  if e['type'] in ['pcb_smtpad','pcb_plated_hole']:add(e,g.geometry(e),e.get('layers',[e.get('layer','top')]))
  elif e['type']=='pcb_via':add(e,g.Point(e['x'],e['y']).buffer(e['outer_diameter']/2),stack)
  elif e['type']=='pcb_trace':
   for p,q in zip(e['route'],e['route'][1:]):
    if p['route_type']==q['route_type']=='wire' and p['layer']==q['layer']:add(e,g.LineString([(p['x'],p['y']),(q['x'],q['y'])]).buffer(max(p['width'],q['width'])/2),[p['layer']])
  elif e['type']=='pcb_hole':add(e,g.Point(e['x'],e['y']).buffer(e['hole_diameter']/2+.1),stack)
  elif e['type']=='pcb_keepout':c=e['center'];r=e['radius'];add(e,g.box(c['x']-r,c['y']-r,c['x']+r,c['y']+r),stack)
 return STRtree(a),a,o,ls
for spec in sys.argv[1:]:
 ref,role=spec.split(':');p=next(p for p in g.ports.values() if g.src[g.sp[p['source_port_id']]['source_component_id']]==ref and g.sp[p['source_port_id']]['name']==role);pad=next(e for e in j if e.get('pcb_port_id')==p['pcb_port_id'] and e['type']=='pcb_smtpad');net=g.key(pad);name=g.nets[net];w=.3 if name in ['A_PLUS','A_MINUS','B_PLUS','B_MINUS','SENSE1','SENSE2','V3V3','DATA_VBUS','LOGIC_IN','BUCK_SW'] else .55 if name=='PD_VBUS' else .16
 tree,shapes,owners,layers=index();best=None
 # Inward QFN escapes preserve the dense outer pin row and USB fanout.
 forced={}
 candidates=[forced[spec]] if spec in forced else [(round(p['x']+ix*.1,4),round(p['y']+iy*.1,4)) for ix in range(-40,41) for iy in range(-40,41) if .45<=math.hypot(ix*.1,iy*.1)<=4]
 for x,y in candidates:
  path_candidates=[[(p['x'],p['y']),(x,y)],[(p['x'],p['y']),(p['x'],y),(x,y)],[(p['x'],p['y']),(x,p['y']),(x,y)]]
  for points in path_candidates:
   wire=g.LineString(points).buffer(w/2);via=g.Point(x,y).buffer(.225);hits=set();bad=False
   policy=json.loads((g.ROOT/'artifacts/engineering-fixes/usb-routing-plan.json').read_text())
   if any(via.intersects(g.LineString(c['centerline']).buffer(.95)) for c in policy['cores']):continue
   if not g.box(-17.15,-17.15,17.15,17.15).covers(via):continue
   for geom,l in [(wire,pad['layer']),(via,None)]:
    for i in tree.query(geom.buffer(.153)):
     e=owners[i];typ=e['type'];same=g.key(e)==net
     if l is not None and l not in layers[i]:continue
     if same and l is not None:continue
     if same and typ=='pcb_trace':continue
     margin=.003 if same and typ in ['pcb_smtpad','pcb_plated_hole'] else .103 if same and typ=='pcb_via' else .153
     if geom.distance(shapes[i])>=margin:continue
     protected=typ=='pcb_trace' and (e['pcb_trace_id'].startswith(('usb_','review_escape_','oscillator_')) or g.nets.get(g.key(e)) in ['USB_DP','USB_DM','MCU_DP','MCU_DM','XIN','XOUT','XTAL_OUT'])
     if typ!='pcb_trace' or protected:bad=True;break
     hits.add(e['pcb_trace_id'])
    if bad:break
   if bad or len(hits)>5:continue
   cost=len(hits)*10+g.LineString(points).length+.05*(len(points)-2)
   if best is None or cost<best[0]:best=(cost,x,y,hits,points)
 if best is None:print('NO ESCAPE',spec,flush=True);continue
 _,x,y,hits,points=best;j[:]=[e for e in j if e.get('pcb_trace_id') not in hits]
 source=next(e['source_trace_id'] for e in j if e['type']=='source_trace' and e.get('subcircuit_connectivity_map_key')==net and p['source_port_id'] in e.get('connected_source_port_ids',[]));n=next(e for e in j if e['type']=='source_net' and e['subcircuit_connectivity_map_key']==net);base={'source_net_id':n['source_net_id'],'source_trace_id':source,'subcircuit_connectivity_map_key':net,'subcircuit_id':'subcircuit_source_group_0'};uid='review_escape_'+ref+'_'+role
 j.append({'type':'pcb_trace','pcb_trace_id':uid,'route':[{**{'route_type':'wire','x':a,'y':b,'width':w,'layer':pad['layer']},**({'start_pcb_port_id':p['pcb_port_id']} if i==0 else {})} for i,(a,b) in enumerate(points)],'pcb_port_ids':[p['pcb_port_id']],**base})
 j.append({'type':'pcb_via','pcb_via_id':uid+'_via','x':x,'y':y,'outer_diameter':.45,'hole_diameter':.3,'layers':stack,'from_layer':'top','to_layer':'bottom','tented_on_top':True,'tented_on_bottom':True,**base});row={'net':name,'port':spec,'via_mm':[x,y],'removed_foreign_trace_ids':sorted(hits)};rows.append(row);print(row,flush=True)
g.path.write_text(json.dumps(j,indent=2)+'\n');(g.ROOT/'artifacts/engineering-fixes/isolated-pad-escapes.json').write_text(json.dumps(rows,indent=2)+'\n')