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/inspect-review-route-pocket.py
"""Read-only geometric diagnosis of the unfinished termination fanout."""
from route_geometry import *
from shapely.ops import unary_union
c=load_circuit('routing/review-placement.circuit.json');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'}
physical=json.loads(Path(os.environ.get('PHYSICAL_INPUT','routing/bulk-top-closure-partial-physical-candidate.json')).read_text())
blocked={l:[] for l in LAYERS};via_blocks=[];n=os.environ.get('REVIEW_NET','N152');width=.15
origin=tuple(json.loads(os.environ.get('REVIEW_FROM','[15.9,-5.95]')))
destination=tuple(json.loads(os.environ.get('REVIEW_TO','[-7.196846,-12.067678]')))
for e in c:
if e['type'] not in ['pcb_smtpad','pcb_plated_hole']:continue
p=ports.get(e.get('pcb_port_id'));nn=mapping[root(p['source_port_id'])] if p else 'NC'
g=geometry(e,'outer_' if e['type']=='pcb_plated_hole' else '')
if nn!=n:
for l in e.get('layers',[e.get('layer')]):blocked[l].append(g.buffer(.1003+width/2))
via_blocks.append(g.buffer(.354))
for w in physical['wires']:
if w['net']==n:continue
g=LineString(w['points']).buffer(w['width']/2)
blocked[w['layer']].append(g.buffer(.1003+width/2));via_blocks.append(g.buffer(.354))
for v in physical['vias']:
p=Point(v['x'],v['y']);via_blocks.append(p.buffer(.554))
if v['net']!=n:
for l in LAYERS:blocked[l].append(p.buffer(.354+width/2))
for x in [-15.5,15.5]:
for y in [-15.5,15.5]:
p=Point(x,y);via_blocks.append(p.buffer(3.1))
blocked['top'].append(p.buffer(2.8503+width/2))
blocked['bottom'].append(p.buffer(2.6003+width/2))
free={}
for l in LAYERS:
free[l]=box(-20.87,-20.87,20.87,20.87).difference(unary_union(blocked[l]))
polygons=list(free[l].geoms) if hasattr(free[l],'geoms') else [free[l]]
for point in [origin,destination]:
pp=[g for g in polygons if g.buffer(.00001).covers(Point(point))]
print(json.dumps(dict(layer=l,point=point,components=[dict(area=g.area,bounds=g.bounds) for g in pp])))
via_bad=unary_union(via_blocks);land=Point(15.2232,-5.4495);choices=[]
for ix in range(275,345):
for iy in range(-145,-78):
q=Point(ix*.05,iy*.05)
if q.distance(land)>2 or via_bad.covers(q):continue
line=LineString([land,q])
if not free['bottom'].covers(line):continue
areas=[]
for l in ['inner1','inner2']:
polygons=list(free[l].geoms) if hasattr(free[l],'geoms') else [free[l]]
own=[g for g in polygons if g.buffer(.00001).covers(q)]
areas.append(max([g.area for g in own] or [0]))
choices.append(dict(x=q.x,y=q.y,area=max(areas),areas=areas,distance=q.distance(land)))
choices.sort(key=lambda q:(-q['area'],q['distance']))
print(json.dumps(dict(candidateStitches=choices[:12])))
def component(layer,xy):
polygons=list(free[layer].geoms) if hasattr(free[layer],'geoms') else [free[layer]]
return next(g for g in polygons if g.buffer(.00001).covers(Point(xy)))
right=component(os.environ.get('REVIEW_FROM_LAYER','inner2'),origin)
for l in ['inner1','inner2','top','bottom']:
crossing=right.intersection(component(l,destination)).difference(via_bad)
polygons=list(crossing.geoms) if hasattr(crossing,'geoms') else [crossing]
found=[]
for g in sorted(polygons,key=lambda g:-g.area):
if g.is_empty:continue
p=g.representative_point();xy=(round(p.x*20)/20,round(p.y*20)/20)
if g.covers(Point(xy)):found.append(dict(x=xy[0],y=xy[1],area=g.area))
if len(found)==8:break
print(json.dumps(dict(layerChangeFromInner2=l,candidates=found)))
from shapely.strtree import STRtree
from collections import defaultdict,deque
regions=[]
for l in LAYERS:
polygons=list(free[l].geoms) if hasattr(free[l],'geoms') else [free[l]]
regions += [(l,g) for g in polygons]
tree=STRtree([g for l,g in regions]);edges=defaultdict(list)
available=box(-20.7,-20.7,20.7,20.7).difference(via_bad)
areas=list(available.geoms) if hasattr(available,'geoms') else [available]
for g in areas:
if g.is_empty:continue
q=g.representative_point()
touching=[int(i) for i in tree.query(q) if regions[int(i)][1].covers(q)]
for i in touching:
for j in touching:
if i!=j:edges[i].append((j,(q.x,q.y)))
starts=[int(i) for i in tree.query(Point(origin)) if regions[int(i)][1].covers(Point(origin))]
goals={int(i) for i in tree.query(Point(destination)) if regions[int(i)][1].covers(Point(destination))}
queue=deque(starts);previous={i:None for i in starts};found=None
while queue:
i=queue.popleft()
if i in goals:found=i;break
for j,q in edges[i]:
if j not in previous:previous[j]=(i,q);queue.append(j)
path=[]
while found is not None and previous[found] is not None:
old,q=previous[found]
path.append(dict(layerFrom=regions[old][0],layerTo=regions[found][0],via=q))
found=old
print(json.dumps(dict(continuousFreeSpacePath=list(reversed(path)),reachableRegions=len(previous),totalRegions=len(regions))))