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-component-location.py

"""Read-only mechanical, solder-land, via and power-copper placement search."""
from route_geometry import *
from shapely.affinity import scale
from shapely.strtree import STRtree
import sys
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'}
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'}
physical=json.loads(Path('../../checkpoints/nema17-before-rishabh-review-v053-DuFOGi/restore-verified/routing/physical-copper.json').read_text())
ignored={'F_VM','Q_PD_ENABLE'};courts=[];pads=[]
def court(e):
    return geometry({**e,**e['center'],'shape':'rect'}) if e['type']=='pcb_courtyard_rect' else Polygon([(p['x'],p['y']) for p in e['outline']]).buffer(0)
for e in c:
    if pc.get(e.get('pcb_component_id')) in ignored:continue
    if e['type']=='pcb_courtyard_rect' or e['type']=='pcb_courtyard_outline' and len(e['outline'])>=3:courts.append((e['layer'],court(e)))
    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['net'],w['layer'],w['width'],LineString(w['points']).buffer(w['width']/2)) for w in physical['wires'] if w['net']!='N178'];mt=STRtree([g for n,l,w,g in metal]);board=box(-20.95,-20.95,20.95,20.95)
for name in sys.argv[1:]:
    base=next(e for e in c if e['type']=='pcb_component' and names[e['source_component_id']]==name);xy=(base['center']['x'],base['center']['y']);base_angle=base['rotation']
    ownpads=[]
    for e in c:
        if e['type']=='pcb_smtpad' and e['pcb_component_id']==base['pcb_component_id']:
            g=rotate(translate(geometry(e),-xy[0],-xy[1]),-base_angle,origin=(0,0));ownpads.append((mapping[root(ports[e['pcb_port_id']]['source_port_id'])],g))
    owncourt=next(e for e in c if e['type'].startswith('pcb_courtyard') and e['pcb_component_id']==base['pcb_component_id'])
    local=rotate(translate(court(owncourt),-xy[0],-xy[1]),-base_angle,origin=(0,0));options=[]
    original={'F_VM':(10.4,-8.85),'Q_PD_ENABLE':(10.7,-13.1388)}[name]
    for layer in ['top','bottom']:
        flip=layer!=base['layer']
        for angle in [0,90,180,270]:
            shaped=rotate(scale(local,xfact=-1 if flip else 1,origin=(0,0)),angle,origin=(0,0))
            lands=[(n,rotate(scale(g,xfact=-1 if flip else 1,origin=(0,0)),angle,origin=(0,0))) for n,g in ownpads]
            for ix in range(-199,201,2):
                for iy in range(-199,201,2):
                    x,y=ix/10,iy/10;g=translate(shaped,x,y)
                    if not board.covers(g):continue
                    if any(courts[int(j)][0]==layer and g.intersects(courts[int(j)][1]) for j in ct.query(g)):continue
                    if any(layer in pads[int(j)][0] and g.intersects(pads[int(j)][1]) for j in pt.query(g)):continue
                    if any(g.distance(Point(a,b))<(2.85 if layer=='top' else 2.6) for a in [-15.5,15.5] for b in [-15.5,15.5]):continue
                    bad=False;cuts=set();vias=set()
                    for n,land in lands:
                        h=translate(land,x,y)
                        vias.update(int(j) for j in vt.query(h.buffer(.354)) if h.distance(vs[int(j)])<.354)
                        for j in mt.query(h.buffer(.1005)):
                            j=int(j);nn,ll,w,other=metal[j]
                            if nn==n or ll!=layer or h.distance(other)>=.1005:continue
                            if w>=.299:bad=True;break
                            cuts.add(j)
                        if bad:break
                    if not bad:options.append(dict(x=x,y=y,rotation=angle,layer=layer,score=math.dist((x,y),original)+len(cuts)*.5+len(vias)*3,thinSegmentsToReroute=len(cuts),viasToRelocate=[physical['vias'][i] for i in sorted(vias)]))
    options.sort(key=lambda o:o['score']);Path('reports/review-location-'+name+'.json').write_text(json.dumps(options[:80],indent=2)+'\n');print(json.dumps(dict(name=name,candidates=options[:8])),flush=True)