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-via-first-reference.py
"""Choose a resistor site with TWO legal, direct, full-stack via escapes.
Pad/courtyard clearance alone does not establish that a dense board can route.
"""
from route_geometry import *
from shapely.strtree import STRtree
c=load_circuit('baseline/v055-circuit.json');root=connectivity(c)
m=json.loads(Path('../../work/nema17-clean-schematic-v054-IAQpFd/routing/net-map.json').read_text())['roots']
results=json.loads(Path('reports/current-resistor-clear-pockets.json').read_text())
parts={e['source_component_id']:e['name'] for e in c if e['type']=='source_component'}
pc={e['pcb_component_id']:parts[e['source_component_id']] for e in c if e['type']=='pcb_component'}
pp={e['pcb_port_id']:e for e in c if e['type']=='pcb_port'}
ss={e['source_trace_id']:e for e in c if e['type']=='source_trace'}
objects=[];vias=[];seen=set()
for e in c:
if e['type'] in ['pcb_smtpad','pcb_plated_hole'] and pc[e['pcb_component_id']]!='R_REF_TOP':
p=pp.get(e.get('pcb_port_id'));n=m.get(root(p['source_port_id'])) if p else None
objects.append((n,e.get('layers',[e.get('layer')]),geometry(e,'outer_' if e['type']=='pcb_plated_hole' else '')))
elif e['type']=='pcb_via':vias.append(Point(e['x'],e['y']))
elif e['type']=='pcb_trace' and e.get('source_trace_id') and ss[e['source_trace_id']].get('name')!='DRV_REF_FEED':
s=ss[e['source_trace_id']];n=m[root((s.get('connected_source_port_ids',[])+s.get('connected_source_net_ids',[]))[0])]
for a,b in zip(e['route'],e['route'][1:]):
if a['route_type']!=b['route_type'] or a['route_type']!='wire' or a['layer']!=b['layer']:continue
xy=[(a['x'],a['y']),(b['x'],b['y'])];w=max(a['width'],b['width'])
key=n,a['layer'],tuple(sorted(xy)),w
if n=='N101' and a['layer']=='bottom' and frozenset(xy) in [
frozenset([(6.4,-13.55),(-2.4,-13.55)]),
frozenset([(-2.4,-13.55),(-2.7,-12.55)])]:continue
if math.dist(*xy)<1e-8 or key in seen:continue
seen.add(key);objects.append((n,[a['layer']],LineString(xy).buffer(w/2)))
print('Loaded cached sites and unique copper objects: '+str(len(objects)),flush=True)
import numpy as np
from shapely.ops import unary_union
from shapely import contains_xy,prepare
new=load_circuit('routing/review-placement.circuit.json')
newroot=connectivity(new);newm=json.loads(Path('routing/net-map.json').read_text())['roots']
newports={e['pcb_port_id']:e for e in new if e['type']=='pcb_port'}
newids={e['source_component_id'] for e in new if e['type']=='source_component' and e['name'] in ['U_CURRENT_DAC','C_CURRENT_DAC']}
newpc={e['pcb_component_id'] for e in new if e['type']=='pcb_component' and e['source_component_id'] in newids}
for e in new:
if e['type']=='pcb_smtpad' and e['pcb_component_id'] in newpc:
p=newports[e['pcb_port_id']]
objects.append((newm[newroot(p['source_port_id'])],[e['layer']],geometry(e)))
# Include the newly drawn input-feed detour as a fixed obstacle too.
detour=[(6.4,-13.55),(2.2,-13.55),(2.2,-12.4),(-2.7,-12.4),(-2.7,-12.55)]
objects.append(('N101',['bottom'],LineString(detour).buffer(.25)))
blocked=[g.buffer(.354) for n,ls,g in objects]+[v.buffer(.554) for v in vias]
for x in [-15.5,15.5]:
for y in [-15.5,15.5]:blocked.append(Point(x,y).buffer(3.1))
for e in c:
if e['type']=='pcb_hole':blocked.append(geometry(e,'hole_').buffer(.429))
g=unary_union(blocked);prepare(g)
print('Full-stack via exclusion geometry prepared',flush=True)
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])]
print('Legal via points: '+str(len(points)),flush=True)
vt=STRtree(points)
foreign={}
for layer in ['top','bottom']:
for n,w in [('N215',.12),('N189',.15)]:
h=unary_union([g.buffer(.1003+w/2) for nn,ls,g in objects if layer in ls and nn!=n]);prepare(h)
foreign[n,layer]=h
if os.environ.get('CURRENT_FANOUT_ONLY'):
prefix=[(.399096,-13.30004),(-1.2,-13.775),(-2.6,-13.775),(-2.8,-14.2)]
h=foreign['N215','bottom']
assert not h.intersects(LineString(prefix)),'Resolve the actual under-body trace clearance first'
end=Point(prefix[-1]);found=[]
for j in vt.query(end.buffer(3)):
v=points[int(j)];line=LineString([end,v])
if not h.intersects(line):found.append(dict(via=list(v.coords)[0],distance=end.distance(v)))
found.sort(key=lambda r:r['distance'])
Path('reports/dac-output-fanout-sites.json').write_text(json.dumps(dict(prefix=prefix,sites=found[:50]),indent=2)+'\n')
print(json.dumps(dict(prefixClear=True,sites=found[:12])),flush=True)
raise SystemExit(0)
accepted=[]
for candidate_index,candidate in enumerate(results):
if candidate_index%100==0:print('Tested sites: '+str(candidate_index)+'; accepted: '+str(len(accepted)),flush=True)
if len(accepted)>=8:break
x,y,rot,layer=[candidate[k] for k in ['x','y','rotation','layer']]
pp=[];pads=[]
for px,n in [(-.51,'N215'),(.51,'N189')]:
if layer=='bottom':px=-px
point=translate(rotate(Point(px,0),rot,origin=(0,0)),x,y)
pad=translate(rotate(box(px-.27,-.32,px+.27,.32),rot,origin=(0,0)),x,y)
pp.append((point,n));pads.append(pad)
escapes=[]
for i,(p,n) in enumerate(pp):
available=[]
for j in vt.query(p.buffer(3.0)):
v=points[int(j)]
if v.distance(p)>3.0 or any(pad.distance(v)<.331 for pad in pads):continue
line=LineString([p,v])
if foreign[n,layer].intersects(line) or line.distance(pads[1-i])<.1003+(.12 if i==0 else .15)/2:continue
available.append((p.distance(v),v))
escapes.append(sorted(available,key=lambda r:r[0])[:25])
if not all(escapes):continue
pairs=[(a[0]+b[0],a[1],b[1]) for a in escapes[0] for b in escapes[1] if a[1].distance(b[1])>=.554]
if not pairs:continue
length,a,b=min(pairs,key=lambda r:r[0])
accepted.append({**candidate,'inputVia':list(a.coords)[0],'outputVia':list(b.coords)[0],
'escapeLength':length,'score':candidate['score']+length})
accepted.sort(key=lambda r:r['score'])
Path('reports/reference-via-first-sites.json').write_text(json.dumps(accepted,indent=2)+'\n')
print(json.dumps(dict(legalViaPoints=len(points),routableSites=len(accepted),best=accepted[:8])),flush=True)