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-pg-placement.py
"""Search real top-side courtyard space and report copper that must be rerouted."""
import json, math
from pathlib import Path
from shapely.geometry import Point, Polygon, LineString, box
from shapely.affinity import rotate, translate
from shapely.strtree import STRtree
from copper_geometry import trace_segments
j=json.loads(Path('reports/pg-sense-2026-10-22/baseline/circuit.json').read_text())
board=next(e for e in j if e['type']=='pcb_board')
outline=Polygon([(p['x'],p['y']) for p in board['outline']]).buffer(-.3)
courts=[]
for e in j:
if e['type'] not in ['pcb_courtyard_outline','pcb_courtyard_rect'] or e.get('layer')!='top':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)
for e in j:
if e['type']=='pcb_plated_hole':
if e.get('shape',e.get('hole_shape'))=='circle':courts.append(Point(e['x'],e['y']).buffer(e.get('outer_diameter',5.6)/2+.2))
else:courts.append(translate(rotate(box(-e['outer_width']/2-.2,-e['outer_height']/2-.2,e['outer_width']/2+.2,e['outer_height']/2+.2),e.get('ccw_rotation',0)),e['x'],e['y']))
elif e['type']=='pcb_hole':
if e.get('shape',e.get('hole_shape','circle'))=='circle':courts.append(Point(e['x'],e['y']).buffer(e.get('hole_diameter',e.get('diameter',.5))/2+.2))
ct=STRtree(courts)
shapes=[];owners=[];fixed=[]
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+.108));owners.append(e['pcb_trace_id']);fixed.append(False)
elif e['type']=='pcb_via':
shapes.append(Point(e['x'],e['y']).buffer(e['outer_diameter']/2+.133));owners.append(e.get('pcb_trace_id','manual'));fixed.append(not e.get('pcb_trace_id'))
tree=STRtree(shapes)
def scan(court,pads,target,limit=50):
choices=[]
for angle in [0,90,180,270]:
bc=rotate(court,angle);bp=[rotate(p,angle,origin=(0,0)) for p in pads]
for xi in range(-80,81):
for yi in range(-80,81):
x,y=xi*.25,yi*.25;c=translate(bc,x,y)
if not outline.covers(c) or any(c.distance(courts[i])<.04 for i in ct.query(c.buffer(.04))):continue
hits=set();valid=True
for p in bp:
g=translate(p,x,y)
for i in tree.query(g):
if not g.intersects(shapes[i]):continue
if fixed[i]:valid=False;break
hits.add(owners[i])
if not valid:break
if valid:choices.append((len(hits)*100+math.hypot(x-target[0],y-target[1]),x,y,angle,sorted(hits)))
return sorted(choices)[:limit]
if __name__ == '__main__':
import sys
if '--passives' not in sys.argv:
chip=scan(box(-1.05005,-1.230085,1.05005,1.230085),[translate(box(-.140005,-.340005,.140005,.340005),x,y) for x in [-.5,0,.5] for y in [-.64008,.64008]],(-10,-3))
print(json.dumps(chip[:20],indent=2))
Path('reports/pg-sense-2026-10-22/placement-candidates.json').write_text(json.dumps(chip,indent=2))
else:
p=Path('mechanical/next-placement.json');j=json.loads(p.read_text())
q=j['U_PD_PG'];courts.append(translate(rotate(box(-1.05005,-1.230085,1.05005,1.230085),q['pcbRotation']),q['pcbX'],q['pcbY']));ct=STRtree(courts)
for name,target in [('C_PD_PG',(-19.5,-15)),('R_PD_VDD_PD',(-19.5,-11)),('R_PD_GOOD_PD',(-18,-14))]:
opts=scan(box(-.95,-.5,.95,.5),[translate(box(-.27,-.32,.27,.32),x,0) for x in [-.51,.51]],target,10)
best=opts[0];_,x,y,angle,hits=best;print(name,best,flush=True);j[name]={'pcbX':x,'pcbY':y,'pcbRotation':angle}
courts.append(translate(rotate(box(-.95,-.5,.95,.5),angle),x,y));ct=STRtree(courts)
p.write_text(json.dumps(j,indent=2)+'\n')
