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/find-clearance-corridor.py
"""Bounded static-copper path advice; output is NOT imported automatically.
Existing tracks never move. Advice must pass the exact independent copper,
drill, connectivity, and Kelvin checks before being accepted as board copper.
All proposed new vias are full-through 0.30/0.45 mm. No router engine changes.
"""
from route_geometry import *
from shapely.ops import unary_union
from shapely import contains_xy, prepare
import numpy as np
import heapq, time, sys, signal
signal.alarm(55)
net = sys.argv[1]
width = float(sys.argv[8]) if len(sys.argv)>8 else .15
wire_clearance = .103 + width/2
drill_clearance = .353 + width/2
start = (float(sys.argv[2]),float(sys.argv[3]),LAYERS.index(sys.argv[4]))
end = (float(sys.argv[5]),float(sys.argv[6]),LAYERS.index(sys.argv[7]))
c=load_circuit();root=connectivity(c);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'}
obstacles=[[] for _ in LAYERS];via_obstacles=[];own_vias=[]
for e in c:
if e['type'] in ['pcb_smtpad','pcb_plated_hole']:
p=ports.get(e.get('pcb_port_id'));n=mapping.get(root(p['source_port_id'])) if p else None
g=geometry(e,'outer_' if e['type']=='pcb_plated_hole' else '')
ls=e.get('layers',[e.get('layer')])
if n!=net:
for l in ls:obstacles[LAYERS.index(l)].append(g.buffer(wire_clearance))
via_obstacles.append(g.buffer(.353))
# New drills cannot enter solder lands, even on the same net.
via_obstacles.append(g.buffer(.163))
if e['type']=='pcb_hole':
g=geometry(e,'hole_')
for o in obstacles:o.append(g.buffer(.203+width/2))
via_obstacles.append(g.buffer(.428))
physical=json.loads(Path('routing/physical-copper.json').read_text())
for w in physical['wires']:
if w['net']==net:continue
g=LineString(w['points']).buffer(w['width']/2)
obstacles[LAYERS.index(w['layer'])].append(g.buffer(wire_clearance))
via_obstacles.append(g.buffer(.353))
for v in physical['vias']:
g=Point(v['x'],v['y'])
via_obstacles.append(g.buffer(.578))
if v['net']!=net:
for o in obstacles:o.append(g.buffer(drill_clearance))
else:own_vias.append((v['x'],v['y']))
for x in [-15.5,15.5]:
for y in [-15.5,15.5]:
obstacles[0].append(Point(x,y).buffer(2.853+width/2))
obstacles[3].append(Point(x,y).buffer(2.603+width/2))
via_obstacles.append(Point(x,y).buffer(3.078))
pitch=float(sys.argv[9]) if len(sys.argv)>9 else .10
axis=np.arange(math.ceil(-20.8/pitch),math.floor(20.8/pitch)+1)*pitch;size=len(axis)
xx,yy=np.meshgrid(axis,axis)
def raster(polygons):
region=unary_union(polygons)
prepare(region)
return contains_xy(region,xx,yy)
blocked=np.array([raster(o) for o in obstacles])
via_blocked=raster(via_obstacles)
via_blocked|=(np.abs(xx)>20.725)|(np.abs(yy)>20.725)
def ix(x,y):return (int(round((x-axis[0])/pitch)),int(round((y-axis[0])/pitch)))
def point(i,j):return (float(axis[i]),float(axis[j]))
existing={ix(x,y):(x,y) for x,y in own_vias}
start_grid=(*ix(*start[:2]),start[2]);end_grid=(*ix(*end[:2]),end[2])
# Exact endpoints are connected explicitly and independently checked later.
for i,j,l in [start_grid,end_grid]:blocked[l,j,i]=False
def heuristic(s):return math.hypot(s[0]-end_grid[0],s[1]-end_grid[1])+abs(s[2]-end_grid[2])*0.01
queue=[(heuristic(start_grid),0,start_grid)];cost={start_grid:0};previous={};found=None;began=time.monotonic();expanded=0
moves=[(dx,dy,math.hypot(dx,dy)) for dx in [-1,0,1] for dy in [-1,0,1] if dx or dy]
while queue:
f,g,s=heapq.heappop(queue)
if g>cost[s]+1e-8:continue
if s==end_grid:found=s;break
expanded+=1
if expanded%1000==0 and time.monotonic()-began>40:break
i,j,l=s
options=[]
for di,dj,d in moves:
ii,jj=i+di,j+dj
if 0<=ii<size and 0<=jj<size and not blocked[l,jj,ii]:
if di and dj and (blocked[l,j,ii] or blocked[l,jj,i]):continue
options.append(((ii,jj,l),d))
if (i,j) in existing or not via_blocked[j,i]:
options.extend(((i,j,ll),12 if (i,j) in existing else 45) for ll in range(4) if ll!=l and not blocked[ll,j,i])
for n,d in options:
new=g+d
if new<cost.get(n,float('inf')):
cost[n]=new;previous[n]=s;heapq.heappush(queue,(new+heuristic(n),new,n))
if not found:
print(json.dumps({'net':net,'found':False,'expanded':expanded,'seconds':time.monotonic()-began}));raise SystemExit(1)
path=[found]
while path[-1]!=start_grid:path.append(previous[path[-1]])
path.reverse()
reduced=[path[0]]
for a,b,d in zip(path,path[1:],path[2:]):
if (b[0]-a[0],b[1]-a[1],b[2]-a[2])!=(d[0]-b[0],d[1]-b[1],d[2]-b[2]):reduced.append(b)
reduced.append(path[-1])
advice={'net':net,'width':width,'found':True,'seconds':round(time.monotonic()-began,3),'expanded':expanded,
'exactStart':[start[0],start[1],LAYERS[start[2]]],'exactEnd':[end[0],end[1],LAYERS[end[2]]],
'points':[[*existing.get((i,j),point(i,j)),LAYERS[l]] for i,j,l in reduced]}
Path('reports/corridor-'+net+'.json').write_text(json.dumps(advice,indent=2)+'\n')
print(json.dumps(advice))