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/check-manual-additions.py

"""Check explicitly authored staging polylines; does not search for routes."""
from route_geometry import *
from shapely.strtree import STRtree
import sys

c = load_circuit()
root = connectivity(c)
m = 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'}
parts = {e['source_component_id']: e['name'] for e in c if e['type'] == 'source_component'}
pcb = {e['pcb_component_id']: parts[e['source_component_id']] for e in c if e['type'] == 'pcb_component'}
objects, drills, holes = [], [], []
for e in c:
    if e['type'] in ['pcb_smtpad', 'pcb_plated_hole']:
        p = ports.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 ''), 'pad '+pcb[e['pcb_component_id']]))
    elif e['type']=='pcb_hole':
        holes.append(geometry(e, 'hole_'))
cu = json.loads(Path('routing/physical-copper.json').read_text())
for w in cu['wires']:
    objects.append((w['net'], [w['layer']], LineString(w['points']).buffer(w['width']/2, quad_segs=16), 'track '+str(w['points'])))
for v in cu['vias']:
    g = Point(v['x'], v['y'])
    objects.append((v['net'], LAYERS, g.buffer(.225, quad_segs=16), 'via '+str((v['x'],v['y']))))
    drills.append((v['net'], g))
proposals=json.loads(Path(sys.argv[1]).read_text())
added=[]
for i,t in enumerate(proposals):
    n=t['connection_name']
    for a,b in zip(t['route'],t['route'][1:]):
        if a['route_type']==b['route_type']=='wire' and a['layer']==b['layer'] and math.dist((a['x'],a['y']),(b['x'],b['y']))>1e-7:
            added.append((i,n,[a['layer']],LineString([(a['x'],a['y']),(b['x'],b['y'])]).buffer(max(a['width'],b['width'])/2,quad_segs=16),False))
    for p in t['route']:
        if p['route_type']=='via':added.append((i,n,LAYERS,Point(p['x'],p['y']).buffer(.225,quad_segs=16),True))
all_objects=objects+[(n,ls,g,'proposal '+str(i)) for i,n,ls,g,v in added]
tree=STRtree([g for n,ls,g,k in all_objects])
failures=[]
for i,n,ls,g,via in added:
    for j in tree.query(g.buffer(.125)):
        nn,ll,h,k=all_objects[j]
        if n!=nn and set(ls)&set(ll):
            d=g.distance(h)
            required=.125 if via else .1
            # Via-hole to foreign metal: .15 radius + .20 clearance;
            # the .225 annulus adds .125 mm rather than the copper .10 rule.
            if d<required-2e-4:failures.append(dict(route=i,name=proposals[i].get('name'),otherNet=nn,other=k,clearance=round(d,6),bounds=[round(z,4) for z in g.bounds],otherBounds=[round(z,4) for z in h.bounds]))
    for nn,h in drills:
        if nn!=n and g.distance(h)<.35-2e-4:failures.append(dict(route=i,name=proposals[i].get('name'),other='drill',otherNet=nn,clearance=round(g.distance(h)-.15,6),bounds=[round(z,4) for z in g.bounds],at=[h.x,h.y]))
    if any(g.distance(h)<.2-2e-4 for h in holes):failures.append(dict(route=i,other='NPTH'))
    if max(abs(v) for v in g.bounds)>20.95+1e-4:failures.append(dict(route=i,other='edge'))
Path('reports/manual-additions-feedback.json').write_text(json.dumps(failures,indent=2)+'\n')
bad={f['route'] for f in failures}
print(json.dumps(dict(routes=len(proposals),clear=[i for i in range(len(proposals)) if i not in bad],failures=len(failures))))
for f in failures[:int(sys.argv[2]) if len(sys.argv)>2 else 25]:print(json.dumps(f))
if failures:raise SystemExit(1)