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/scan-current-prefix.py

"""Exact manual fanout planning: pad → authored prefix → legal through-via."""
from route_geometry import *
from shapely.ops import unary_union
from shapely import contains_xy,prepare
from shapely.strtree import STRtree
import numpy as np
c=load_circuit('routing/review-placement.circuit.json');root=connectivity(c)
m=json.loads(Path('routing/net-map.json').read_text())['roots']
pp={e['pcb_port_id']:e for e in c if e['type']=='pcb_port'}
cu=json.loads(Path('routing/software-current-retained-physical-candidate.json').read_text())
objects=[];pads=[];holes=[]
for e in c:
    if e['type'] in ['pcb_smtpad','pcb_plated_hole']:
        p=pp.get(e.get('pcb_port_id'));n=m.get(root(p['source_port_id'])) if p else None
        o=(n,e.get('layers',[e.get('layer')]),geometry(e,'outer_' if e['type']=='pcb_plated_hole' else ''))
        objects.append(o);pads.append(o)
    elif e['type']=='pcb_hole':holes.append(geometry(e,'hole_'))
for w in cu['wires']:objects.append((w['net'],[w['layer']],LineString(w['points']).buffer(w['width']/2)))
for v in cu['vias']:objects.append((v['net'],LAYERS,Point(v['x'],v['y']).buffer(.225)))
blocked=[g.buffer(.354) for n,ls,g in objects]+[g.buffer(.331) for n,ls,g in pads]
blocked += [Point(v['x'],v['y']).buffer(.554) for v in cu['vias']]
blocked += [h.buffer(.429) for h in holes]
blocked += [Point(x,y).buffer(3.1) for x in [-15.5,15.5] for y in [-15.5,15.5]]
g=unary_union(blocked);prepare(g)
axis=np.arange(-20.65,20.66,.05);xx,yy=np.meshgrid(axis,axis)
free=~contains_xy(g,xx,yy);points=[Point(x,y) for x,y in zip(xx[free],yy[free])];tree=STRtree(points)
jobs=[
    dict(name='DRV_REF_FEED',net='N215',width=.12,prefix=[(-1.899096,-15.19996),(-3.3,-15.2),(-4.0,-15.1)]),
    dict(name='CURRENT_DAC_SCL',net='N162',width=.15,prefix=[(.399096,-14.25),(1.15,-14.5),(6.0,-14.5)]),
    dict(name='CURRENT_DAC_SDA',net='N161',width=.15,prefix=[(.399096,-13.30004),(1.3,-13.6),(1.4,-14.15),(6.0,-14.15)]),
    dict(name='CURRENT_DAC_GROUND',net='N62',width=.15,prefix=[(-1.899096,-14.25),(-.8,-13.6)]),
]
results=[]
for job in jobs:
    # Same-net copper is not a clearance obstacle; pad/drill and existing
    # drill-to-drill exclusions still apply without exception.
    vb=[s.buffer(.354) for n,ls,s in objects if n!=job['net']]+[s.buffer(.331) for n,ls,s in pads]
    vb += [Point(v['x'],v['y']).buffer(.554) for v in cu['vias']]+[h.buffer(.429) for h in holes]
    vb += [Point(x,y).buffer(3.1) for x in [-15.5,15.5] for y in [-15.5,15.5]]
    vg=unary_union(vb);prepare(vg)
    mask=~contains_xy(vg,xx,yy);vp=[Point(x,y) for x,y in zip(xx[mask],yy[mask])];vt=STRtree(vp)
    h=unary_union([s.buffer(.1003+job['width']/2) for n,ls,s in objects if n!=job['net'] and 'bottom' in ls]);prepare(h)
    line=LineString(job['prefix']);hits=[]
    if h.intersects(line):
        hits=[dict(net=n,bounds=s.bounds,distance=s.distance(line)) for n,ls,s in objects if n!=job['net'] and 'bottom' in ls and s.distance(line)<.1003+job['width']/2]
    candidates=[];end=Point(job['prefix'][-1])
    for j in vt.query(end.buffer(3)):
        p=vp[int(j)]
        if not h.intersects(LineString([end,p])):candidates.append(dict(via=list(p.coords)[0],distance=p.distance(end)))
    candidates.sort(key=lambda a:a['distance']);row={**job,'prefixClear':not hits,'collisions':hits,'viaCandidates':candidates[:80]};results.append(row)
    print(json.dumps({**row,'viaCandidates':candidates[:8]}),flush=True)
Path('reports/software-current-fanout-sites.json').write_text(json.dumps(results,indent=2)+'\n')