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/plan-current-corridor.py
"""Find actual clear layer-transition sites for the manually fanned DAC net.
This is planning evidence only; it never changes runtime copper or DRC rules.
"""
from route_geometry import *
from shapely.ops import unary_union
from shapely import contains_xy,prepare
from scipy.ndimage import label
import numpy as np
from collections import defaultdict,deque
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'}
physical=json.loads(Path(os.environ.get('CURRENT_CORRIDOR_PHYSICAL','routing/software-current-partial-physical-candidate.json')).read_text())
net=os.environ.get('CURRENT_CORRIDOR_NET','N215')
width=float(os.environ.get('CURRENT_CORRIDOR_WIDTH','.15' if net in ['N161','N162'] else '.12'))
objects=[];pads=[];holes=[]
for e in c:
if e['type'] in ['pcb_smtpad','pcb_plated_hole']:
p=ports.get(e.get('pcb_port_id'));n=m.get(root(p['source_port_id'])) if p else None
item=(n,e.get('layers',[e.get('layer')]),geometry(e,'outer_' if e['type']=='pcb_plated_hole' else ''))
objects.append(item);pads.append(item)
elif e['type']=='pcb_hole':holes.append(geometry(e,'hole_'))
for w in physical['wires']:objects.append((w['net'],[w['layer']],LineString(w['points']).buffer(w['width']/2)))
for v in physical['vias']:objects.append((v['net'],LAYERS,Point(v['x'],v['y']).buffer(.225)))
axis=np.round(np.arange(-20.9,20.9001,.05),8);xx,yy=np.meshgrid(axis,axis)
def raster(gs):
g=unary_union(gs);prepare(g);return contains_xy(g,xx,yy)
vb=[g.buffer(.354) for n,ls,g in objects if n!=net]
vb += [g.buffer(.331) for n,ls,g in pads]
vb += [Point(v['x'],v['y']).buffer(.554) for v in physical['vias']]
vb += [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]]
via_ok=~raster(vb)&(abs(xx)<20.72)&(abs(yy)<20.72)
if os.environ.get('CURRENT_CORRIDOR_MAX_VIA_Y'):
via_ok &= yy <= float(os.environ['CURRENT_CORRIDOR_MAX_VIA_Y'])
components=[]
for layer in LAYERS:
blockers=[g.buffer(.1003+width/2) for n,ls,g in objects if n!=net and layer in ls]
# Use the routing utility's stronger via-to-wire exclusion too.
blockers += [Point(v['x'],v['y']).buffer(.354+width/2) for v in physical['vias'] if v['net']!=net]
blockers += [h.buffer(.2603) for h in holes]
if layer in ['top','bottom']:
radius=(2.8503 if layer=='top' else 2.6003)+.06
blockers += [Point(x,y).buffer(radius) for x in [-15.5,15.5] for y in [-15.5,15.5]]
free=~raster(blockers)&(abs(xx)<20.8897)&(abs(yy)<20.8897)
cc,count=label(free)
components.append(cc);print(layer,count,flush=True)
def index(xy):return tuple(int(round((a+20.9)/.05)) for a in xy[::-1])
start=tuple(map(float,os.environ.get('CURRENT_CORRIDOR_START','-4.15,-15.1').split(',')))
target=tuple(map(float,os.environ.get('CURRENT_CORRIDOR_TARGET','10.15,-9').split(',')))
starts={(l,int(cc[index(start)])) for l,cc in enumerate(components) if cc[index(start)]}
goals={(l,int(cc[index(target)])) for l,cc in enumerate(components) if cc[index(target)]}
if os.environ.get('CURRENT_CORRIDOR_SCAN'):
from shapely.strtree import STRtree
fixed=[g.buffer(.331) for n,ls,g in pads]
fixed += [Point(v['x'],v['y']).buffer(.554) for v in physical['vias']]
fixed += [h.buffer(.429) for h in holes]
fixed += [Point(x,y).buffer(3.1) for x in [-15.5,15.5] for y in [-15.5,15.5]]
scan_layer=LAYERS.index(os.environ.get('CURRENT_CORRIDOR_SCAN_LAYER','top'))
allowed=~raster(fixed)&(components[scan_layer]==components[scan_layer][index(start)])
allowed &= (abs(xx-start[0])<4)&(abs(yy-start[1])<4)
fw=[w for w in physical['wires'] if w['net']!=net]
fg=[LineString(w['points']).buffer(w['width']/2) for w in fw];tree=STRtree(fg)
sites=[]
for j,i in zip(*np.where(allowed)):
xy=(float(axis[i]),float(axis[j]));point=Point(xy)
bad=[fw[int(k)] for k in tree.query(point.buffer(.354)) if fg[int(k)].distance(point)<.354]
if any(w['width']>=.275 for w in bad):continue
score=len({w['net'] for w in bad})*100+math.dist(xy,start)
sites.append(dict(via=xy,score=score,blockingWires=bad))
sites.sort(key=lambda a:a['score'])
Path('reports/software-current-transition-rework.json').write_text(json.dumps(sites[:30],indent=2)+'\n')
if os.environ.get('CURRENT_CORRIDOR_START_LAYERS'):
starts={node for node in starts if LAYERS[node[0]] in os.environ['CURRENT_CORRIDOR_START_LAYERS'].split(',')}
if os.environ.get('CURRENT_CORRIDOR_GOAL_LAYERS'):
goals={node for node in goals if LAYERS[node[0]] in os.environ['CURRENT_CORRIDOR_GOAL_LAYERS'].split(',')}
edges=defaultdict(dict)
for v in physical['vias']:
if v['net']!=net:continue
j,i=index((v['x'],v['y']))
nodes=[(l,int(cc[j,i])) for l,cc in enumerate(components) if cc[j,i]]
for a in nodes:
for b in nodes:
if a!=b:edges[a][b]=(0,(v['x'],v['y']))
for j,i in zip(*np.where(via_ok)):
nodes=[(l,int(cc[j,i])) for l,cc in enumerate(components) if cc[j,i]]
xy=(float(axis[i]),float(axis[j]))
score=2*math.dist(xy,start)+math.dist(xy,target)
for a in nodes:
for b in nodes:
if a==b:continue
old=edges[a].get(b)
if old is None or score<old[0]:edges[a][b]=(score,xy)
queue=deque(starts);prev={n:None for n in starts};found=None
while queue:
a=queue.popleft()
if a in goals:found=a;break
for b,(_,xy) in sorted(edges[a].items(),key=lambda item:item[1][0]):
if b not in prev:prev[b]=(a,xy);queue.append(b)
path=[];end=found
while end and prev[end]:
a,xy=prev[end];path.append(dict(fromLayer=LAYERS[a[0]],toLayer=LAYERS[end[0]],via=xy));end=a
path.reverse()
report=dict(start=start,target=target,startComponents=sorted(starts),goalComponents=sorted(goals),
reachableComponents=len(prev),found=found is not None,transitions=path)
if os.environ.get('CURRENT_CORRIDOR_BRIDGE'):
from shapely.strtree import STRtree
reverse=defaultdict(set)
for a,neighbors in edges.items():
for b in neighbors:reverse[b].add(a)
goal_reached=set(goals);todo=list(goals)
while todo:
for a in reverse[todo.pop()]:
if a not in goal_reached:goal_reached.add(a);todo.append(a)
fixed=[g.buffer(.331) for n,ls,g in pads]
fixed += [Point(v['x'],v['y']).buffer(.554) for v in physical['vias']]
fixed += [h.buffer(.429) for h in holes]
fixed += [Point(x,y).buffer(3.1) for x in [-15.5,15.5] for y in [-15.5,15.5]]
allowed=~raster(fixed)&(abs(xx)<20.72)&(abs(yy)<20.72)
a_mask=np.zeros_like(allowed);b_mask=np.zeros_like(allowed)
for l,cc in enumerate(components):
a_mask |= np.isin(cc,[node[1] for node in prev if node[0]==l])
b_mask |= np.isin(cc,[node[1] for node in goal_reached if node[0]==l])
fw=[w for w in physical['wires'] if w['net']!=net]
fg=[LineString(w['points']).buffer(w['width']/2) for w in fw];tree=STRtree(fg)
sites=[]
for j,i in zip(*np.where(allowed&a_mask&b_mask)):
xy=(float(axis[i]),float(axis[j]));point=Point(xy)
bad=[fw[int(k)] for k in tree.query(point.buffer(.354)) if fg[int(k)].distance(point)<.354]
if any(w['width']>=.275 for w in bad):continue
score=len({w['net'] for w in bad})*100+math.dist(xy,start)+math.dist(xy,target)
sites.append(dict(via=xy,score=score,blockingWires=bad,
fromLayers=[LAYERS[l] for l,cc in enumerate(components) if (l,int(cc[j,i])) in prev],
toLayers=[LAYERS[l] for l,cc in enumerate(components) if (l,int(cc[j,i])) in goal_reached]))
sites.sort(key=lambda a:a['score'])
Path('reports/software-current-bridge-rework.json').write_text(json.dumps(sites[:30],indent=2)+'\n')
report['referenceCandidates']=[dict(site=s,reachableInputLayers=[LAYERS[l] for l,cc in enumerate(components)
if (l,int(cc[index(s['inputVia'])])) in prev])
for s in json.loads(Path('reports/reference-via-first-sites.json').read_text())]
Path('reports/software-current-corridor-'+net+'.json').write_text(json.dumps(report,indent=2)+'\n')
print(json.dumps(report),flush=True)