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/construct-reviewed-escapes.py

"""Construct exact local escapes after independent pad/via/trace collision screening."""
import json,sys,math
from shapely.geometry import Point,LineString,box
import verify_supplier_connectivity as g
j=g.j;stack=['top','inner1','inner2','bottom'];nets={e['name']:e for e in j if e['type']=='source_net'};rows=[]
def add(ref,role,points,site,replace_near=None,width=.16,reuse_existing=False,diameter=.45):
 assert .45<=diameter<=.6 and .16<=width<=.55
 uid='reviewed_local_'+ref+'_'+role
 j[:]=[e for e in j if e.get(e['type']+'_id') not in [uid,uid+'_via']]
 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);key=g.sp[p['source_port_id']]['subcircuit_connectivity_map_key'];name=g.nets[key];layer=p.get('layers',['top'])[0];wire=LineString([(p['x'],p['y'])]+points+[site]).buffer(width/2);via=Point(*site).buffer(diameter/2);hole=Point(*site).buffer(.15);ripped=set();bad=[]
 for e in j:
  typ=e['type'];same=g.key(e)==key
  if typ in ['pcb_smtpad','pcb_plated_hole']:
   if same:
    if typ=='pcb_smtpad' and hole.intersects(g.geometry(e)):bad.append(('hole-in-pad',e['pcb_smtpad_id']))
    continue
   if (layer in e.get('layers',[e.get('layer','top')]) and wire.distance(g.geometry(e))<.153) or via.distance(g.geometry(e))<.153:bad.append((typ,e[typ+'_id']))
  elif typ=='pcb_via' and not same:
   s=Point(e['x'],e['y']).buffer(e['outer_diameter']/2)
   if via.distance(s)<.153 or wire.distance(s)<.153:bad.append(('via',e['pcb_via_id']))
  elif typ=='pcb_trace' and not same:
   for a,b in zip(e['route'],e['route'][1:]):
    if a['route_type']==b['route_type']=='wire' and a['layer']==b['layer']:
     s=LineString([(a['x'],a['y']),(b['x'],b['y'])]).buffer(max(a['width'],b['width'])/2)
     if via.distance(s)<.153 or a['layer']==layer and wire.distance(s)<.153:ripped.add(e['pcb_trace_id']);break
 if bad:raise RuntimeError('Illegal reviewed escape '+ref+':'+role+' '+str(bad))
 fixed_nets={'USB_DP','USB_DM','MCU_DP','MCU_DM','XIN','XOUT','XTAL_OUT','QSPI_SCLK','QSPI_SD0','QSPI_SD1','QSPI_SD2','QSPI_SD3','QSPI_SS','RUN','SWCLK','SWDIO'}
 if '--protect-power' in sys.argv:fixed_nets|={'PD_VBUS','DATA_VBUS','PD_VDD','LOGIC_IN','V3V3','V1V1','VREF','VCP','VREG','BUCK_SW','BUCK_BST','CP1','CP2','A_PLUS','A_MINUS','B_PLUS','B_MINUS','SENSE1','SENSE2'}
 if '--protect-signals' in sys.argv:fixed_nets|=set(g.nets.values())
 if '--allow-vref-rip' in sys.argv:fixed_nets.discard('VREF')
 protected=[t for t in j if t.get('pcb_trace_id') in ripped and (t['pcb_trace_id'].startswith(('usb_','oscillator_','reviewed_local_','reviewed_phase_inner1_')) or g.nets.get(g.key(t)) in fixed_nets)]
 assert not protected, ('Reviewed escape conflicts with fixed critical copper',ref,role, [t['pcb_trace_id'] for t in protected])
 j[:]=[e for e in j if e.get('pcb_trace_id') not in ripped]
 n=nets[name];source=next(e['source_trace_id'] for e in j if e['type']=='source_trace' and e.get('subcircuit_connectivity_map_key')==key and p['source_port_id'] in e.get('connected_source_port_ids',[]));attrs=dict(source_trace_id=source,source_net_id=n['source_net_id'],subcircuit_connectivity_map_key=key,subcircuit_id='subcircuit_source_group_0');uid='reviewed_local_'+ref+'_'+role
 if reuse_existing:
  v=next((v for v in j if v['type']=='pcb_via' and g.key(v)==key and math.dist((v['x'],v['y']),site)<.001),None)
  assert v is not None, 'Reuse requires an actual same-net plated barrel at the endpoint'
 elif replace_near:
  v=next((v for v in j if v['type']=='pcb_via' and g.key(v)==key and math.dist((v['x'],v['y']),replace_near)<.12),None)
 else:v=None
 if v:v['x'],v['y']=site;v['outer_diameter'],v['hole_diameter']=diameter,.3
 else:j.append(dict(type='pcb_via',pcb_via_id=uid+'_via',x=site[0],y=site[1],outer_diameter=diameter,hole_diameter=.3,layers=stack,from_layer='top',to_layer='bottom',tented_on_top=True,tented_on_bottom=True,**attrs))
 pts=[(p['x'],p['y'])]+points+[site];route=[dict(route_type='wire',x=x,y=y,layer=layer,width=width) for x,y in pts];route[0]['start_pcb_port_id']=p['pcb_port_id'];j.append(dict(type='pcb_trace',pcb_trace_id=uid,route=route,pcb_port_ids=[p['pcb_port_id']],**attrs));rows.append(dict(terminal=ref+':'+role,via_mm=site,removed_foreign_trace_records=sorted(ripped)))
# Caller supplies JSON so every proposed point is explicit and reviewable.
rows_to_build=json.load(open(sys.argv[1]))
if '--replace-group' in sys.argv:
 ids={identifier for row in rows_to_build for identifier in ['reviewed_local_'+row['ref']+'_'+row['role'],'reviewed_local_'+row['ref']+'_'+row['role']+'_via']}
 j[:]=[e for e in j if e.get(e['type']+'_id') not in ids]
for row in rows_to_build:add(**row)
g.path.write_text(json.dumps(j,indent=2)+'\n');(g.ROOT/'artifacts/upright-revision/reviewed-local-escapes.json').write_text(json.dumps(rows,indent=2)+'\n');print(rows)