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-review-filter-pair.py
"""Read-only search for a real two-part filter island, including copper/drills."""
from route_geometry import *
from shapely.strtree import STRtree
c=load_circuit('routing/review-placement.circuit.json')
names={e['source_component_id']:e['name'] for e in c if e['type']=='source_component'}
pc={e['pcb_component_id']:names[e['source_component_id']] for e in c if e['type']=='pcb_component'}
ignored={'R_CURRENT_A_FILTER','C_CURRENT_A_FILTER'}
physical=json.loads(Path('routing/review-emitted-physical.json').read_text())
courts=[];pads=[]
for e in c:
if pc.get(e.get('pcb_component_id')) in ignored:continue
if e['type']=='pcb_courtyard_rect':courts.append((e['layer'],geometry({**e,**e['center'],'shape':'rect'})))
if e['type']=='pcb_courtyard_outline' and len(e['outline'])>=3:courts.append((e['layer'],Polygon([(p['x'],p['y']) for p in e['outline']]).buffer(0)))
if e['type'] in ['pcb_smtpad','pcb_plated_hole']:pads.append((e.get('layers',[e.get('layer')]),geometry(e,'outer_' if e['type']=='pcb_plated_hole' else '')))
ct=STRtree([g for l,g in courts]);pt=STRtree([g for ls,g in pads])
vs=[Point(v['x'],v['y']) for v in physical['vias']];vt=STRtree(vs)
metal=[(w['layer'],w['width'],LineString(w['points']).buffer(w['width']/2)) for w in physical['wires']]
mt=STRtree([g for l,w,g in metal]);board=box(-20.95,-20.95,20.95,20.95)
def footprint(xy,angle):
x,y=xy
court=translate(rotate(box(-.93,-.47,.93,.47),angle,origin=(0,0)),x,y)
lands=[translate(rotate(box(dx-.27,-.32,dx+.27,.32),angle,origin=(0,0)),x,y) for dx in [-.51,.51]]
return court,lands
options=[]
for layer in ['bottom','top']:
for angle in [0,90,180,270]:
dx,dy=([0,-1.3],[1.3,0],[0,1.3],[-1.3,0])[angle//90]
for ix in range(-199,201,2):
for iy in range(-199,141,2):
r=(ix/10,iy/10);cap=(r[0]+dx,r[1]+dy)
rc,rp=footprint(r,180+angle);cc,cp=footprint(cap,angle);allpads=rp+cp
if not board.covers(rc) or not board.covers(cc):continue
if any(rc.intersects(courts[int(j)][1]) or cc.intersects(courts[int(j)][1]) for j in set(map(int,ct.query(rc)))|set(map(int,ct.query(cc))) if courts[int(j)][0]==layer):continue
if any(min(rc.distance(Point(x,y)),cc.distance(Point(x,y)))<(2.85 if layer=='top' else 2.6) for x in [-15.5,15.5] for y in [-15.5,15.5]):continue
bad=False;cuts=set()
for g in allpads:
if any(layer in pads[int(j)][0] and g.distance(pads[int(j)][1])<.1005 for j in pt.query(g.buffer(.1005))):bad=True;break
if any(g.distance(vs[int(j)])<.354 for j in vt.query(g.buffer(.354))):bad=True;break
for j in mt.query(g.buffer(.1005)):
j=int(j);ll,w,h=metal[j]
if ll!=layer or g.distance(h)>=.1005:continue
if w>=.299:bad=True;break
cuts.add(j)
if bad:break
if bad:continue
score=math.dist(r,(17.275,-1.87))+len(cuts)*.5
options.append(dict(score=score,layer=layer,r=r,cap=cap,rRotation=(180+angle)%360,cRotation=angle,thinSegmentsToReroute=len(cuts)))
options.sort(key=lambda o:o['score'])
Path('reports/review-filter-pair-options.json').write_text(json.dumps(options[:60],indent=2)+'\n')
print(json.dumps(options[:8]))