imrishabh18/rp2040-motor-controller
A library of PCB component definitions covering MOSFETs, motor drivers, USB-PD control, sensors, current monitors, transistors, diodes, resistors, speakers, connectors, and mirrored bottom-side logos, with pin mappings, footprints, and 3D models.
- Version
- 1.0.55
- License
- unset
- Stars
- 5
scripts/find-encoder-cap-slots.py
import json,math
from pathlib import Path
from shapely.geometry import Point,LineString,Polygon,box
from shapely.affinity import rotate,translate
from shapely.strtree import STRtree
from copper_geometry import trace_segments
j=json.load(open('dist/baseline/circuit.json'));sc={e['source_component_id']:e['name'] for e in j if e['type']=='source_component'};pc={e['pcb_component_id']:e for e in j if e['type']=='pcb_component'}
parent={}
def root(x):
parent.setdefault(x,x)
if parent[x]!=x:parent[x]=root(parent[x])
return parent[x]
for e in j:
ids=e.get('connected_source_port_ids',[])+e.get('connected_source_net_ids',[]) if e['type']=='source_trace' else e.get('source_port_ids',[]) if e['type']=='source_component_internal_connection' else []
for v in ids[1:]:parent[root(v)]=root(ids[0])
sp={e['source_port_id']:e for e in j if e['type']=='source_port'};pp={e['pcb_port_id']:e for e in j if e['type']=='pcb_port'};st={e['source_trace_id']:e for e in j if e['type']=='source_trace'};pt={e['pcb_trace_id']:e for e in j if e['type']=='pcb_trace'}
def tnet(e):
if e.get('source_net_id'):return root(e['source_net_id'])
if e.get('source_trace_id') in st:
s=st[e['source_trace_id']];return root((s['connected_source_port_ids']+s['connected_source_net_ids'])[0])
if e.get('pcb_trace_id') in pt and e['type']!='pcb_trace':return tnet(pt[e['pcb_trace_id']])
raise ValueError(e)
portnet={id:root(e['source_port_id']) for id,e in pp.items() if e.get('source_port_id')}
ground=root(next(e['source_net_id'] for e in j if e['type']=='source_net' and e['name']=='GND'))
v33=root(next(e['source_net_id'] for e in j if e['type']=='source_net' and e['name']=='V3V3'))
courts=[]
for e in j:
if e['type'] not in ['pcb_courtyard_rect','pcb_courtyard_outline'] or e['layer']!='top':continue
name=sc.get(pc[e['pcb_component_id']].get('source_component_id'),'')
if name.startswith('TP_'):continue
if e['type']=='pcb_courtyard_outline':g=Polygon([(p['x'],p['y']) for p in e['outline']])
else:g=translate(rotate(box(-e['width']/2,-e['height']/2,e['width']/2,e['height']/2),e.get('ccw_rotation',0)),e['center']['x'],e['center']['y'])
courts.append(g)
ct=STRtree(courts)
shapes=[];nets=[];viaflags=[];owners=[];protected=set()
for t in pt.values():
if max((p.get("width",0) for p in t["route"]),default=0)>=.35 or st.get(t.get("source_trace_id"),{}).get("name","").startswith(("XIN","XOUT","CXIN","CXOUT","CURRENT_A_SENSE","CURRENT_B_SENSE")):protected.add(t["pcb_trace_id"])
for e in j:
if e['type']=='pcb_trace':
for a,b,l,w in trace_segments(e['route']):
if l=='top':shapes.append(LineString([a,b]).buffer(w/2));nets.append(tnet(e));viaflags.append(False);owners.append(e["pcb_trace_id"])
elif e['type']=='pcb_via':
shapes.append(Point(e['x'],e['y']).buffer(e['outer_diameter']/2));nets.append(None);viaflags.append(True);owners.append(e.get("pcb_trace_id","manual"))
trace_tree=STRtree(shapes);candidates=[]
for angle in [0,90,180,270]:
base_court=rotate(box(-.93,-.47,.93,.47),angle)
base_pads=[rotate(translate(box(-.27,-.32,.27,.32),x,0),angle,origin=(0,0)) for x in [-.51,.51]]
for xi in range(-140,171):
for yi in range(-200,101):
x,y=xi*.05,yi*.05
px=x-.51*math.cos(math.radians(angle));py=y-.51*math.sin(math.radians(angle))
if math.hypot(px-.635,py+2.7051)>7.3:continue
court=translate(base_court,x,y)
if any(court.intersects(courts[i]) for i in ct.query(court)):continue
valid=True;gap=1;hits=set()
for pad,net in zip(base_pads,[v33,ground]):
g=translate(pad,x,y)
for i in trace_tree.query(g.buffer(.151)):
if nets[i]==net:continue
d=g.distance(shapes[i]);limit=.126 if viaflags[i] else .106
if d<limit:
if owners[i] in protected or owners[i]=='manual':valid=False;break
hits.add(owners[i])
gap=min(gap,d)
if not valid:break
if valid:
score=100*len(hits)+((px-.635)**2+(py+2.7051)**2)/100
candidates.append((score,x,y,angle,court,sorted(hits)))
candidates.sort(key=lambda p:p[0]);candidates=candidates[:600];print('legal slots',len(candidates))
pairs=[]
for a in candidates:
for b in candidates:
if b[0]<a[0] or a[4].distance(b[4])<.04:continue
pairs.append((100*len(set(a[5]+b[5]))+(a[0]%100)+(b[0]%100),a[:4]+(a[5],),b[:4]+(b[5],)))
print('best pairs',sorted(pairs)[:15])
Path('reports/cap-slot-candidates.json').write_text(json.dumps({'candidates':[p[:4]+(p[5],) for p in candidates],'pairs':sorted(pairs)[:30]},indent=2))
