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/list-local-component-sites.py
"""Placement-only clearance feedback for one part; never creates routes."""
from route_geometry import *
from shapely.affinity import rotate, translate
from shapely.strtree import STRtree
from shapely.geometry import MultiPoint
import sys
c = load_circuit()
root = connectivity(c)
m = json.loads(Path('routing/net-map.json').read_text())['roots']
names = {e['source_component_id']:e['name'] for e in c if e['type']=='source_component'}
parts = {e['pcb_component_id']:e for e in c if e['type']=='pcb_component'}
ports = {e['pcb_port_id']:e for e in c if e['type']=='pcb_port'}
part = next(e for e in parts.values() if names[e['source_component_id']]==sys.argv[1])
cx,cy = part['center']['x'],part['center']['y']
target = tuple(map(float,sys.argv[2:4]))
own,objects,drills,courts,holes = [],[],[],[],[]
def court(e):
if e['type']=='pcb_courtyard_outline':
points=[(p['x'],p['y']) for z in c if z['type']=='pcb_courtyard_outline' and z['pcb_component_id']==e['pcb_component_id'] for p in z['outline']]
return MultiPoint(points).convex_hull
if e['type']=='pcb_courtyard_circle':return Point(e['center']['x'],e['center']['y']).buffer(e['radius'])
x,y=e['center']['x'],e['center']['y']
return rotate(box(x-e['width']/2,y-e['height']/2,x+e['width']/2,y+e['height']/2),e.get('ccw_rotation',0),origin=(x,y))
own_court=None
for e in c:
if e['type'] in ['pcb_smtpad','pcb_plated_hole']:
p=ports.get(e.get('pcb_port_id'));net=m.get(root(p['source_port_id'])) if p else None
g=geometry(e,'outer_' if e['type']=='pcb_plated_hole' else '')
if e['pcb_component_id']==part['pcb_component_id']:own.append((net,g))
else:objects.append((net,e.get('layers',[e.get('layer')]),g))
elif e['type'].startswith('pcb_courtyard_') and 'warning' not in e['type']:
g=court(e)
if e['pcb_component_id']==part['pcb_component_id']:own_court=g
else:courts.append((parts[e['pcb_component_id']]['layer'],g))
elif e['type']=='pcb_hole':holes.append(geometry(e,'hole_'))
assert own_court is not None
physical=json.loads(Path('routing/physical-copper.json').read_text())
for w in physical['wires']:objects.append((w['net'],[w['layer']],LineString(w['points']).buffer(w['width']/2)))
for v in physical['vias']:
q=Point(v['x'],v['y']);objects.append((v['net'],LAYERS,q.buffer(.225)));drills.append((v['net'],q))
tree=STRtree([g for n,ls,g in objects]);dtree=STRtree([g for n,g in drills]);ctree=STRtree([g for l,g in courts])
grid=sorted((math.hypot(ix/5,iy/5),target[0]+ix/5,target[1]+iy/5) for ix in range(-20,21) for iy in range(-20,21))
out=[]
for distance,x,y in grid:
for layer in ['top','bottom']:
for angle in [0,90,180,270]:
transform=lambda g:translate(rotate(g,angle-part['rotation'],origin=(cx,cy)),x-cx,y-cy)
cc=transform(own_court)
if max(abs(v) for v in cc.bounds)>20.95:continue
if any(courts[j][0]==layer and cc.intersection(courts[j][1]).area>1e-8 for j in ctree.query(cc)):continue
if any(cc.distance(Point(xx,yy))<(2.85 if layer=='top' else 2.6) for xx in [-15.5,15.5] for yy in [-15.5,15.5]):continue
okay=True
for net,g in own:
h=transform(g)
if any(h.distance(q)<.2 for q in holes):okay=False;break
if any(net!=objects[j][0] and layer in objects[j][1] and h.distance(objects[j][2])<.1001 for j in tree.query(h.buffer(.101))):okay=False;break
if any(net!=drills[j][0] and h.distance(drills[j][1])<.3501 for j in dtree.query(h.buffer(.351))):okay=False;break
if okay and not any(l==layer and math.dist((x,y),(xx,yy))<.8 for xx,yy,r,l in out):
out.append((round(x,6),round(y,6),angle,layer))
if len(out)>=20:break
print(json.dumps({'part':sys.argv[1],'sites':out}))