MustafaMulla29/rp2040-bldc-motor-controller-new

A USB‑C PD–powered RP2040 BLDC motor controller PCB with 3.3 V logic, MCT8329A sensorless commutation, three MOSFET half-bridges, protected power switching, USB data, controls, and motor connectors.

Version
0.3.82
License
unset
Stars
0

scripts/pcb-audit/check-pad-anchors.py

"""Check every PCB routing terminal lies on copper belonging to that terminal."""
import json, math, sys, hashlib
from pathlib import Path
import geometry as R
path=Path(sys.argv[1]); raw=path.read_bytes(); circuit=json.loads(raw)
of=lambda kind:[x for x in circuit if x['type']==kind]
components={x['source_component_id']:x['name'] for x in of('source_component')}
sources={x['source_port_id']:x for x in of('source_port')}
pads=of('pcb_smtpad')+of('pcb_plated_hole')
def contains(pad,x,y):
 if pad['shape']=='polygon':
  vertices=[(p['x'],p['y']) for p in pad['points']]; inside=False
  for (ax,ay),(bx,by) in zip(vertices,vertices[1:]+vertices[:1]):
   dx,dy=bx-ax,by-ay; norm=dx*dx+dy*dy
   t=max(0,min(1,((x-ax)*dx+(y-ay)*dy)/norm)) if norm else 0
   if math.hypot(x-ax-t*dx,y-ay-t*dy)<1e-7:return True
   if (ay>y)!=(by>y) and x<(bx-ax)*(y-ay)/(by-ay)+ax:inside=not inside
  return inside
 dx,dy=x-pad['x'],y-pad['y']
 if pad['shape']=='circle':return math.hypot(dx,dy)<=pad.get('outer_diameter',2*pad.get('radius',0))/2+1e-7
 if pad['shape']=='pill':
  angle=math.radians(-pad.get('ccw_rotation',0));dx,dy=dx*math.cos(angle)-dy*math.sin(angle),dx*math.sin(angle)+dy*math.cos(angle)
  w,h=pad.get('outer_width',pad.get('width')),pad.get('outer_height',pad.get('height')); r=min(w,h)/2
  return math.hypot(max(0,abs(dx)-max(0,w-h)/2),max(0,abs(dy)-max(0,h-w)/2))<=r+1e-7
 angle=math.radians(-pad.get('rect_ccw_rotation',pad.get('ccw_rotation',0)));dx,dy=dx*math.cos(angle)-dy*math.sin(angle),dx*math.sin(angle)+dy*math.cos(angle)
 return abs(dx)<=pad.get('rect_pad_width',pad.get('outer_width',pad.get('width')))/2+1e-7 and abs(dy)<=pad.get('rect_pad_height',pad.get('outer_height',pad.get('height')))/2+1e-7
failures=[];checked=0
for port in of('pcb_port'):
 matched=[p for p in pads if p.get('pcb_port_id')==port['pcb_port_id']]
 if not matched:continue
 checked+=1
 if not any(contains(p,port['x'],port['y']) for p in matched):
  source=sources[port['source_port_id']]
  failures.append({'terminal':components.get(source['source_component_id'],'?')+'.'+str(source.get('pin_number',source.get('name'))),'x':port['x'],'y':port['y']})
report={'input':str(path),'sha256':hashlib.sha256(raw).hexdigest(),'checked':checked,'status':'FAIL' if failures else 'PASS','failures':failures}
(R.BASE/'pad-anchor-audit.json').write_text(json.dumps(report,indent=2)+'\n')
print(json.dumps(report,indent=2));sys.exit(bool(failures))