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/search-swap-protection.py
"""Read-only nearby protection placement, using actual emitted baseline copper."""
from route_geometry import *
from shapely.strtree import STRtree
import sys
c=load_circuit('routing/review-placement.circuit.json');root=connectivity(c)
m=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'}
physical=json.loads(Path('routing/review-emitted-physical.json').read_text())
name=sys.argv[1];base=next(e for e in c if e['type']=='pcb_component' and pc[e['pcb_component_id']]==name)
def court(e):
return geometry({**e,**e['center'],'shape':'rect'}) if e['type']=='pcb_courtyard_rect' else Polygon([(p['x'],p['y']) for p in e['outline']]).buffer(0)
courts=[court(e) for e in c if (e['type']=='pcb_courtyard_rect' or e['type']=='pcb_courtyard_outline' and len(e['outline'])>=3) and pc.get(e.get('pcb_component_id'))!=name and e['layer']==base['layer']]
pads=[geometry(e,'outer_' if e['type']=='pcb_plated_hole' else '') for e in c if e['type'] in ['pcb_smtpad','pcb_plated_hole'] and pc.get(e.get('pcb_component_id'))!=name and base['layer'] in e.get('layers',[e.get('layer')])]
ct=STRtree(courts);pt=STRtree(pads);vs=[Point(v['x'],v['y']) for v in physical['vias']];vt=STRtree(vs)
metal=[(w['net'],w['layer'],w['width'],LineString(w['points']).buffer(w['width']/2)) for w in physical['wires']];mt=STRtree([g for n,l,w,g in metal])
xy=base['center']['x'],base['center']['y'];local=translate(court(next(e for e in c if e['type'].startswith('pcb_courtyard') and e['pcb_component_id']==base['pcb_component_id'])),-xy[0],-xy[1])
lands=[(m[root(ports[e['pcb_port_id']]['source_port_id'])],translate(geometry(e),-xy[0],-xy[1])) for e in c if e['type']=='pcb_smtpad' and e['pcb_component_id']==base['pcb_component_id']]
result=[]
for angle in [0,90,180,270]:
delta=angle-base['rotation'];shape=rotate(local,delta,origin=(0,0));lp=[(n,rotate(g,delta,origin=(0,0))) for n,g in lands]
for ix in range(90,191):
for iy in range(-120,-29):
x,y=ix/10,iy/10;g=translate(shape,x,y)
if not box(-20.95,-20.95,20.95,20.95).covers(g):continue
if any(g.intersects(courts[int(j)]) for j in ct.query(g)):continue
if any(g.intersects(pads[int(j)]) for j in pt.query(g)):continue
if any(g.distance(Point(a,b))<(2.85 if base['layer']=='top' else 2.6) for a in [-15.5,15.5] for b in [-15.5,15.5]):continue
cuts=set();power=set();vias=set();bad=False
for n,land in lp:
h=translate(land,x,y)
if any(h.distance(pads[int(j)])<.1003 for j in pt.query(h.buffer(.1003))):bad=True;break
vias.update(int(j) for j in vt.query(h.buffer(.354)) if h.distance(vs[int(j)])<.354)
for j in mt.query(h.buffer(.1003)):
j=int(j);nn,l,w,other=metal[j]
if nn!=n and l==base['layer'] and h.distance(other)<.1003:
cuts.add(j)
if w>=.299:power.add(nn)
if not bad:result.append(dict(x=x,y=y,rotation=angle,layer=base['layer'],score=len(vias)*30+len(power)*15+len(cuts)*.1+math.dist((x,y),xy),vias=[physical['vias'][i] for i in vias],power=sorted(power),cuts=len(cuts)))
result.sort(key=lambda v:v['score'])
Path('reports/swap-search-'+name+'.json').write_text(json.dumps(result[:100],indent=2)+'\n')
print(json.dumps(result[:8]),flush=True)