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/bridge-ground-to-existing-vias.py
"""Join isolated ground lands to existing return barrels without ripping foreign copper."""
import json,math
from shapely.geometry import Point,LineString,box
from shapely.strtree import STRtree
import verify_supplier_connectivity as g
key=next(k for k,v in g.nets.items() if v=='GND');net=next(e for e in g.j if e['type']=='source_net' and e['name']=='GND');records=[]
for attempt in range(40):
es,rs=g.groups(key);lands={}
for e,r in zip(es,rs):
if e.get('pcb_port_id'):lands.setdefault(r,[]).append(e)
if len(lands)<2:break
main=max(lands,key=lambda r:len(lands[r]));vias=[e for e,r in zip(es,rs) if r==main and e['type']=='pcb_via'];found=None
for root in sorted((r for r in lands if r!=main),key=lambda r:len(lands[r])):
for land in lands[root]:
p=g.ports[land['pcb_port_id']];a=(p['x'],p['y']);layer=land.get('layer','top')
for v in sorted(vias,key=lambda v:math.dist(a,(v['x'],v['y']))):
b=(v['x'],v['y'])
if math.dist(a,b)>5:continue
for pts in [[a,b],[a,(b[0],a[1]),b],[a,(a[0],b[1]),b]]:
if LineString(pts).length>6:continue
shape=LineString(pts).buffer(.08);bad=False
if not box(-17.15,-17.15,17.15,17.15).covers(shape):continue
for e in g.j:
if g.key(e)==key:continue
typ=e['type'];shapes=[];limit=.153
if typ in ['pcb_smtpad','pcb_plated_hole'] and layer in e.get('layers',[e.get('layer','top')]):shapes=[g.geometry(e)];limit=.253 if typ=='pcb_plated_hole' else .153
elif typ=='pcb_via':shapes=[Point(e['x'],e['y']).buffer(e['outer_diameter']/2)]
elif typ=='pcb_trace':shapes=[LineString([(aa['x'],aa['y']),(bb['x'],bb['y'])]).buffer(max(aa['width'],bb['width'])/2) for aa,bb in zip(e['route'],e['route'][1:]) if aa['route_type']==bb['route_type']=='wire' and aa['layer']==bb['layer']==layer]
elif typ=='pcb_keepout':shapes=[Point(e['center']['x'],e['center']['y']).buffer(e['radius'],quad_segs=128)]
elif typ=='pcb_hole':shapes=[Point(e['x'],e['y']).buffer(e['hole_diameter']/2)];limit=.253
if any(shape.distance(s)<limit for s in shapes):bad=True;break
if not bad:found=(p,pts,layer,v);break
if found:break
if found:break
if found:break
if not found:break
p,pts,layer,v=found;uid='reviewed_local_ground_bridge_'+p['pcb_port_id']
while any(e.get('pcb_trace_id')==uid for e in g.j):uid+='_next'
source=next(e['source_trace_id'] for e in g.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',[]));route=[dict(route_type='wire',x=x,y=y,layer=layer,width=.16) for x,y in pts];route[0]['start_pcb_port_id']=p['pcb_port_id']
g.j.append(dict(type='pcb_trace',pcb_trace_id=uid,route=route,pcb_port_ids=[p['pcb_port_id']],source_net_id=net['source_net_id'],source_trace_id=source,subcircuit_connectivity_map_key=key,subcircuit_id='subcircuit_source_group_0'));records.append({'trace':uid,'points_mm':pts,'layer':layer,'existing_return_via':v['pcb_via_id']});print(records[-1],flush=True)
g.path.write_text(json.dumps(g.j,indent=2)+'\n');(g.ROOT/'artifacts/opposite-edge/ground-existing-return-bridges.json').write_text(json.dumps(records,indent=2)+'\n');print('Ground pad groups remaining',len(lands))