shibosoftwaredev/f1c100s-linux-nema17-stepper-controller
A four-layer NEMA 17 motor-controller PCB combining an Allwinner F1C100S Linux computer, USB‑PD/buck power system, W5500 Ethernet, STM32 real-time motion control, DRV8825 stepper driver, protection/temperature/current telemetry, USB interfaces, and local LED/buzzer diagnostics.
- Version
- 0.4.56
- License
- unset
- Stars
- 0
scripts/prepare-swap-copper.py
"""Local authored protection rework from hash-verified emitted copper.
Only colliding signal segments are released. The 0.50 mm raw-PD replacement
and 0.80 mm motor/ground extensions are explicit outer-layer conductors.
This creates an unaccepted routing candidate, never installs board routes.
"""
from route_geometry import *
from shapely.strtree import STRtree
import hashlib
c=load_circuit('routing/review-placement.circuit.json');root=connectivity(c)
assert not [e for e in c if e['type'].endswith('_error')]
assert hashlib.sha256(Path('routing/review-emitted-physical.json').read_bytes()).hexdigest()=='013fb40895d67c433baedb06ce6f8c3ef222669ebc1659aeea064e2055c68292'
physical=json.loads(Path('routing/review-emitted-physical.json').read_text())
mapping=json.loads(Path('routing/net-map.json').read_text())['roots'];ports={e['pcb_port_id']:e for e in c if e['type']=='pcb_port'}
names={e['source_component_id']:e['name'] for e in c if e['type']=='source_component'}
pc={e['pcb_component_id']:names[e['source_component_id']] for e in c if e['type']=='pcb_component'}
moved=set(json.loads(Path('review-placement.json').read_text()))
def wire(n,l,w,p):return dict(net=n,layer=l,width=w,points=p)
manual=[
wire('N101','top',.5,[(20.65,3.7),(20.65,.4),(20.05,.4)]),
wire('N101','bottom',.5,[(20.05,.4),(18.7,.4),(18.7,-.75)]),
wire('N101','top',.5,[(18.7,-.75),(17.8,-1.39),(17.1,-2),(15.695,-2),(15.695,-5.7),(13.3,-5.7),(13.3,-6.5)]),
wire('N178','top',.5,[(14.6,-4.807598),(13.4,-4.807598),(12.607598,-5)]),
wire('N179','top',.8,[(14.6,-8.192402),(15.2,-9.3),(19.25,-9.3)]),
wire('N179','top',.8,[(18.4,-11.252419),(19.25,-11.7),(18,-13.05)]),
wire('N179','bottom',.8,[(18,-13.05),(18,-12.2)]),
wire('N179','bottom',.8,[(12.35,-9.7),(10.147581,-10)]),
wire('N62','bottom',.8,[(16.65,-9.7),(16.852419,-10)]),
wire('N62','top',.8,[(18.4,-4.547581),(19.8,-4.547581),(19.8,-7.61),(20.35,-7.61)]),
wire('N166','top',.15,[(17.54,-2.65),(17.1,-2.95)]),
wire('N166','top',.15,[(20.3,-2.41),(19.9,-2.9),(19.7,-3.18),(17.54,-3.18),(17.54,-2.65)]),
wire('N209','top',.15,[(18.56,-2.65),(19.5,-2.65),(19.65,-2),(19.6,-1.15)]),
wire('N62','top',.15,[(20.3,-1.39),(20.85,-1.39),(20.85,-3.7),(20.35,-4.547581)]),
wire('N167','bottom',.15,[(17.925064,-1.870962),(18.35,-2.9),(19.05,-2.9),(19.05,-1.3)]),
wire('N157','top',.15,[(20.51,-14.6),(20.51,-12.7)]),
wire('N170','top',.15,[(19.49,-14.6),(18.75,-14.6),(18.45,-15.2)]),
wire('N159','top',.15,[(20.0425,-.2121),(19.6,-.25),(19.6,1.5),(19.95,1.5)]),
wire('N159','bottom',.15,[(19.95,1.5),(20.0425,2.9592)]),
wire('N159','top',.15,[(17.1,2.5),(17.4,1.8),(19.95,1.8),(19.95,1.5)]),
wire('N57','top',.15,[(16.435086,.249938),(17.3,.45)]),
wire('N57','bottom',.15,[(16.65,-5.5),(17.3,-6.1)]),
wire('N57','bottom',.15,[(16.65,-5.5),(18.2,-5.5),(18.85,-6.12)]),
wire('N57','bottom',.15,[(20,-2.025),(19.8,-2.025),(19.65,-3.16),(17.7,-3.16),(17.1,-3.7),(16.65,-3.6)]),
wire('N57','bottom',.15,[(20,-2.025),(20.5,-2.025),(20.8,-2.7),(20.8,-6.3),(20.65,-6.8)]),
]
vias=[dict(net='N101',x=20.05,y=.4),dict(net='N101',x=18.7,y=-.75),dict(net='N167',x=19.05,y=-1.3),dict(net='N209',x=19.6,y=-1.15),dict(net='N179',x=18,y=-13.05),dict(net='N159',x=19.95,y=1.5),dict(net='N159',x=17.1,y=2.5),dict(net='N57',x=17.3,y=.45),dict(net='N57',x=17.3,y=-6.1),dict(net='N57',x=20.65,y=-6.8),dict(net='N57',x=18.85,y=-6.12)]
newpads=[]
for e in c:
if e['type']=='pcb_smtpad' and pc.get(e['pcb_component_id']) in moved:
newpads.append((mapping[root(ports[e['pcb_port_id']]['source_port_id'])],[e['layer']],geometry(e)))
authored=[(w['net'],[w['layer']],LineString(w['points']).buffer(w['width']/2)) for w in manual]
shapes=newpads+authored
removed_vias=[];retained_vias=[]
for v in physical['vias']:
point=Point(v['x'],v['y'])
relocated=(v['net']=='N179' and v['x']==18 and v['y']==-12.5) or (v['net']=='N209' and v['x']==19.61 and v['y']==-1.25)
if relocated or any(n!=v['net'] and g.distance(point)<.354 for n,ls,g in shapes):removed_vias.append(v)
else:retained_vias.append(v)
removed=[];retained=[];power=[]
for w in physical['wires']:
g=LineString(w['points']).buffer(w['width']/2)
old_raw=(w['net']=='N101' and w['layer']=='top' and min(p[0] for p in w['points'])>=13.299 and max(p[1] for p in w['points'])<=-1.85)
clash=any(n!=w['net'] and w['layer'] in ls and g.distance(h)<.1003 for n,ls,h in shapes)
clash|=any(v['net']!=w['net'] and g.distance(Point(v['x'],v['y']))<.354 for v in vias)
old_ground_spur=(w['net']=='N62' and w['layer']=='top' and w['points']==[[14.95,-9.3],[14.95,-10.9]])
old_motor_spur=(w['net']=='N179' and w['layer']=='top' and w['points']==[[15.992402,-5],[19.25,-9.3]])
old_raw_feed=(w['net']=='N101' and w['layer']=='top' and w['points']==[[20.65,3.7],[20.65,-1.85]])
relocated_motor_via=(w['net']=='N179' and any(p==[18,-12.5] for p in w['points']))
if old_raw or old_raw_feed or old_ground_spur or old_motor_spur or relocated_motor_via or clash:
removed.append(w)
if w['width']>=.299 and not (old_raw or old_raw_feed or old_ground_spur or old_motor_spur or relocated_motor_via):power.append(w)
else:retained.append(w)
report=dict(moved=sorted(moved),authoredWires=manual,authoredVias=vias,removedWires=removed,removedVias=removed_vias,unplannedPowerConflicts=power)
Path('reports/swap-preparation.json').write_text(json.dumps(report,indent=2)+'\n')
print(json.dumps(dict(removed=len(removed),removedVias=removed_vias,powerConflicts=power)),flush=True)
assert not power,'Power conflict needs explicit redesign, not automatic deletion'
physical=dict(wires=retained+manual,vias=retained_vias+vias)
Path('routing/swap-seed.json').write_text(json.dumps(physical,indent=2)+'\n')
Path('routing/swap-no-patches.json').write_text(json.dumps(dict(wires=[],vias=[]))+'\n')
Path('reports/bulk-top-physical-islands.json').write_text(json.dumps(dict(removedForManualEscapes=removed))+'\n')