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/repair-top-local.py
"""Repair local fragments while preserving all unaffected route backbones."""
import json, math, subprocess, re, sys
from pathlib import Path
folder=Path('dist/top-assembly')
plan=json.loads((folder/'route-plan.json').read_text())
traces={t['pcb_trace_id']:t for t in plan['traces']}
changed={e['id'] for e in json.loads((folder/'changed-routes.json').read_text())}
leaves=set(json.loads((folder/'leaf-routes.json').read_text()))
bad=json.loads(Path('reports/top-pad-conflict-indices.json').read_text())
j=json.loads(Path('dist/pd-control/unrouted.json').read_text())
sc={e['source_component_id']:e['name'] for e in j if e['type']=='source_component'}
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'}
moved={'R_BUZZER_PD','R_ADC_B','U_CURRENT_A','R_PD_ENABLE_PU','R_STATUS_R','R_ADC_A','Q_PD_CFG1','C_ENCODER','C_ENCODER_BULK','U_PD_MON','C_PD_MON'}
parent={}
def root(id):
parent.setdefault(id,id)
if parent[id]!=id:parent[id]=root(parent[id])
return parent[id]
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 id in ids[1:]:parent[root(id)]=root(ids[0])
terminals={'J_MOTOR_USB':{'A4B9'},'F_MOTOR_VBUS':{'pin2'},'DRIVER':{'VMA','AOUT1','AOUT2','BOUT1','BOUT2','ISENA','ISENB'},'R_PHASE_A':{'pin2'},'R_PHASE_B':{'pin2'}}
motor_nets={root(id) for id,p in sp.items() if p['name'] in terminals.get(sc.get(p['source_component_id']),set())}
assert len(motor_nets)==11
sources={e['source_trace_id']:e for e in j if e['type']=='source_trace'}
def is_motor(t):
s=sources.get(t.get('source_trace_id'),{})
ids=s.get('connected_source_port_ids',[])+s.get('connected_source_net_ids',[]) or ([t['source_net_id']] if t.get('source_net_id') else [])
return bool(ids) and root(ids[0]) in motor_nets
manifest={}
for id in sorted(changed|set(bad)):
route=traces[id]['route'];n=len(route)
if id in leaves or n<4:
manifest[id]=[0,n-1];continue
lo,hi=n-1,0
for index in bad.get(id,[]): lo=min(lo,index);hi=max(hi,index)
for endpoint_index in [0,n-1]:
endpoint=route[endpoint_index]
if id not in changed:continue
for port in traces[id].get('connectsTo',[]):
p=pp[port];source=sp.get(p.get('source_port_id'),{})
if sc.get(source.get('source_component_id')) not in moved:continue
if math.hypot(endpoint['x']-p['x'],endpoint['y']-p['y'])>1e-5:continue
candidates=[k for k in range(1,n-1) if route[k]['route_type']=='wire']
if not candidates:lo,hi=0,n-1;continue
anchor=min(candidates,key=lambda k:math.hypot(route[k]['x']-endpoint['x'],route[k]['y']-endpoint['y']))
if endpoint_index==0:lo=0;hi=max(hi,anchor)
else:lo=min(lo,anchor);hi=n-1
if lo>hi:lo,hi=0,n-1
# Extend past nearby old vias and pad escapes before fixing the cut points.
for direction in [-1,1]:
k=lo if direction==-1 else hi;distance=0
while 0<=k+direction<n and (distance<1.5 or route[k]['route_type']!='wire'):
other=k+direction
distance+=math.hypot(route[k]['x']-route[other]['x'],route[k]['y']-route[other]['y'])
k=other
if direction==-1:lo=k
else:hi=k
while lo>0 and route[lo]['route_type']!='wire':lo-=1
while hi<n-1 and route[hi]['route_type']!='wire':hi+=1
if lo==hi:lo=max(0,lo-1);hi=min(n-1,hi+1)
manifest[id]=[lo,hi]
extra_path=Path('reports/local-extra-patches.json')
if extra_path.exists():manifest.update(json.loads(extra_path.read_text()))
Path('reports/local-patch-manifest.json').write_text(json.dumps(manifest,indent=2))
done=set(re.findall(r'^Routed (\S+)',Path('reports/local-patch-repair.log').read_text(),re.M)) if '--resume' in sys.argv else set()
pending=sorted((id for id in manifest if id not in done),key=lambda id:(id not in ['top_assembly_256'],id in leaves,math.hypot(traces[id]['route'][manifest[id][0]]['x']-traces[id]['route'][manifest[id][1]]['x'],traces[id]['route'][manifest[id][0]]['y']-traces[id]['route'][manifest[id][1]]['y'])))
with Path('reports/local-patch-repair.log').open('a' if '--resume' in sys.argv else 'w',buffering=1) as log:
while pending:
id=pending.pop(0);a,b=manifest[id];r=traces[id]['route']
width=min(p['width'] for p in r[a:b+1] if p['route_type']=='wire')
if id=='top_assembly_060':width=.6
if id=='top_assembly_000':width=.15
args=['.venv/bin/python','scripts/route-local.py',str(folder),id,str(width),'--shorten','--via-cost=35',f'--patch-from={a}',f'--patch-to={b}','--ripup='+','.join(pending),'--patch-manifest=reports/local-patch-manifest.json','--report-dir=reports/top-local']
if id in ['top_assembly_142','top_assembly_256','top_assembly_258']:args.append('--debug')
if id=='top_assembly_171':args += ['--debug','--grid-step=.025']
if is_motor(traces[id]):args.append('--outer-only')
if id=='top_assembly_000':args += ['--escape-width=.1','--escape-radius=.6']
if id=='top_assembly_060':args += ['--escape-width=.3','--escape-radius=.7']
print('LOCAL',id,'bounds',a,b,'of',len(r),'remaining',len(pending),file=log,flush=True)
result=subprocess.run(args,stdout=log,stderr=subprocess.STDOUT)
if result.returncode:raise SystemExit('Local repair failed: '+id)
print('All local repairs complete',file=log,flush=True)
