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/audit-route-cleanup.py
"""Catch unused drilled vias, narrow local supply branches, and overlapping control labels."""
import contextlib,io,runpy,json,math,collections
from pathlib import Path
from shapely.strtree import STRtree
with contextlib.redirect_stdout(io.StringIO()):
e=runpy.run_path(str(Path(__file__).with_name('audit-copper.py')))
nodes=e['nodes'];c=e['c'];tree=STRtree([n['shape'] for n in nodes]);vias=[]
for n in nodes:
if n['kind']!='via':continue
layers=set()
for j0 in tree.query(n['shape'].buffer(1e-6)):
other=nodes[int(j0)]
if other['kind']=='via' or other['net']!=n['net'] or n['shape'].distance(other['shape'])>1e-6:continue
layers|=n['layers']&other['layers']
vias.append({'id':n['id'],'net':n['net'],'contactLayers':sorted(layers)})
unused=[v for v in vias if len(v['contactLayers'])<2]
net={r['source_net_id']:r['name'] for r in c if r['type']=='source_net'}
# These small-signal supply rails should have no thin offshoots, even when
# another wide path happens to make an endpoint-to-endpoint check pass.
railTargets={name:.2 for name in ['V3V3','V1V1','ADC_AVDD','LOGIC_IN','MCT_CPL','MCT_CPH','MCT_AVDD','MCT_DVDD','MCT_GVDD','PD_1V2','PD_2V7']}
railResults={name:{'minimumWidthMm':w,'copperLengthMm':0,'narrowSegments':[]} for name,w in railTargets.items()}
for t in c:
if t['type']!='pcb_trace':continue
name=net.get(t.get('connection_name'))
if name not in railTargets:continue
result=railResults[name]
for a,b in zip(t['route'],t['route'][1:]):
if a['route_type']!=b['route_type'] or a['route_type']!='wire' or a['layer']!=b['layer']:continue
d=math.dist((a['x'],a['y']),(b['x'],b['y']))
if d<1e-8:continue
result['copperLengthMm']+=d
if min(a['width'],b['width'])<railTargets[name]-1e-7:result['narrowSegments'].append({'id':t['pcb_trace_id'],'width':min(a['width'],b['width'])})
thin=[{'net':name,**r} for name,v in railResults.items() for r in v['narrowSegments']]
length=railResults['V3V3']['copperLengthMm']
texts={r['text']:r for r in c if r['type']=='pcb_silkscreen_text'};labels=[]
for function,reference in [('BOOT','SW1'),('RESET','SW2'),('ARM','SW3')]:
a,b=texts[function],texts[reference]
# These labels are stacked horizontally. Require an actual gap between the
# full font-height envelopes, which is stricter than most glyph outlines.
assert a.get('ccw_rotation',0)==b.get('ccw_rotation',0)==0
gap=abs(a['anchor_position']['y']-b['anchor_position']['y'])-(a['font_size']+b['font_size'])/2
labels.append({'labels':[function,reference],'verticalGapMm':gap,'pass':gap>=.15})
report={'sha256':e['out']['sha256'],'vias':len(vias),'singleLayerVias':unused,'v3v3MinimumWidthMm':.2,'v3v3CopperLengthMm':length,'narrowV3v3Segments':railResults['V3V3']['narrowSegments'],'localSupplyRails':railResults,'narrowLocalSupplySegments':thin,'controlLabelGaps':labels}
(e['R'].BASE/'route-cleanup.json').write_text(json.dumps(report,indent=2)+'\n')
print('Vias with fewer than two contact layers:',len(unused),'; narrow local supply segments:',len(thin),'; control labels:',sum(x['pass'] for x in labels),'/ 3')
if unused or thin or not all(x['pass'] for x in labels):raise SystemExit(1)