imrishabh18/nema-23-stepper-controller

A four-layer NEMA23 stepper motor controller PCB featuring an RP2040 MCU, DRV8452 dual-phase driver, USB‑C PD power input/protection, current and temperature sensing, status LED/buzzer, and optional magnetic encoder.

Version
1.3.2
License
unset
Stars
1

scripts/test-copper-audit.py

"""Fault-injection checks for the fabrication continuity audit."""
import copy
import json
import subprocess
import sys
import tempfile
from pathlib import Path

base = []
for n in ['A', 'B']:
    base.append({'type': 'source_net', 'source_net_id': n, 'name': n})
for i, x, y, layer in [(1,-2,0,'top'), (2,2,0,'bottom'), (3,2,3,'top'), (4,4,3,'top'), (5,-4,4,'top')]:
    base.extend([
        {'type':'pcb_port','pcb_port_id':f'p{i}','source_port_id':f's{i}'},
        {'type':'pcb_smtpad','pcb_smtpad_id':f'pad{i}','pcb_port_id':f'p{i}','shape':'rect','width':1,'height':1,'x':x,'y':y,'layer':layer},
    ])
base.extend([
    {'type':'source_trace','source_trace_id':'ta','connected_source_port_ids':['s1','s2'],'connected_source_net_ids':['A']},
    {'type':'source_trace','source_trace_id':'tb','connected_source_port_ids':['s3','s4'],'connected_source_net_ids':['B']},
    {'type':'pcb_trace','pcb_trace_id':'a','source_trace_id':'ta','route':[
        {'route_type':'wire','x':-2,'y':0,'layer':'top','width':0.3},
        {'route_type':'via','x':0,'y':0},
        {'route_type':'wire','x':0,'y':0,'layer':'bottom','width':0.3},
        {'route_type':'wire','x':2,'y':0,'layer':'bottom','width':0.3}]},
    {'type':'pcb_trace','pcb_trace_id':'b','source_trace_id':'tb','route':[
        {'route_type':'wire','x':2,'y':3,'layer':'top','width':0.3},
        {'route_type':'wire','x':4,'y':3,'layer':'top','width':0.3}]},
    {'type':'pcb_via','pcb_via_id':'via','source_net_id':'A','x':0,'y':0,'outer_diameter':0.45,'hole_diameter':0.3,'layers':['top','inner1','inner2','bottom']},
])
cases = [('connected-with-intentional-NC',base,None)]
missing = [e for e in base if e['type']!='pcb_via']
cases.append(('missing-plated-barrel',missing,'opens'))
shorted = copy.deepcopy(base)
shorted.append({'type':'pcb_trace','pcb_trace_id':'short','source_trace_id':'ta','route':[{'route_type':'wire','x':-2,'y':0,'layer':'top','width':0.3},{'route_type':'wire','x':2,'y':3,'layer':'top','width':0.3}]})
cases.append(('cross-net-copper-bridge',shorted,'shorts'))
island = copy.deepcopy(base)
island.append({'type':'pcb_copper_pour','pcb_copper_pour_id':'island','source_net_id':'A','layer':'top','brep_shape':{'outer_ring':{'vertices':[{'x':x,'y':y} for x,y in [(-6,-6),(-5,-6),(-5,-5),(-6,-5)]]},'inner_rings':[]}})
cases.append(('floating-pour',island,'floatingCopper'))
drilled = copy.deepcopy(base)
drilled.append({'type':'pcb_hole','pcb_hole_id':'cut','hole_shape':'circle','x':3,'y':3,'hole_diameter':0.5})
cases.append(('drill-severs-track',drilled,'opens'))
erased = copy.deepcopy(base)
erased.append({'type':'pcb_hole','pcb_hole_id':'erase','hole_shape':'circle','x':-4,'y':4,'hole_diameter':2})
cases.append(('drill-erases-pad',erased,'padsErasedByDrilling'))
results=[]
with tempfile.TemporaryDirectory() as tmp:
    for name, circuit, failure in cases:
        src=Path(tmp)/f'{name}.json'; out=Path(tmp)/f'{name}-result.json'
        src.write_text(json.dumps(circuit))
        r=subprocess.run([sys.executable,'scripts/audit-all-net-copper.py',str(src),f'--output={out}'],capture_output=True,text=True)
        result=json.loads(out.read_text())
        assert (r.returncode==0) == (failure is None), (name,r.stdout,r.stderr)
        if failure: assert result[failure], (name,result)
        results.append({'case':name,'status':'passed','expectedFinding':failure})
Path('checks/copper-audit-fault-injection.json').write_text(json.dumps(results,indent=2)+'\n')
print(json.dumps(results,indent=2))