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/audit-local-copper.py

"""Preflight all session metal with exact pad, wire, via and hole geometry.
This is independent of the routing search; final native/Gerber checks follow.
"""
from route_geometry import *
from shapely.strtree import STRtree
import hashlib
c=load_circuit();root=connectivity(c);m=json.loads(Path('routing/net-map.json').read_text())
ports={e['pcb_port_id']:e for e in c if e['type']=='pcb_port'}
part={e['source_component_id']:e['name'] for e in c if e['type']=='source_component'}
pc={e['pcb_component_id']:part[e['source_component_id']] for e in c if e['type']=='pcb_component'}
physical_path=Path(os.environ.get('PHYSICAL_INPUT','routing/physical-copper.json'))
report_dir=Path(os.environ.get('COPPER_REPORT_DIR','reports'));report_dir.mkdir(parents=True,exist_ok=True)
physical=json.loads(physical_path.read_text());objects=[];holes=[]
for e in c:
    if e['type'] in ['pcb_smtpad','pcb_plated_hole']:
        p=ports.get(e.get('pcb_port_id'));n=m['roots'].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 '+pc.get(e.get('pcb_component_id'),'?')+' '+str(p and p['source_port_id'])))
    if e['type']=='pcb_hole':holes.append(geometry(e,'hole_' if 'hole_diameter' in e else ''))
pad_count=len(objects)
for w in physical['wires']:
    objects.append((w['net'],[w['layer']],LineString(w['points']).buffer(w['width']/2,quad_segs=12),
                    'wire '+str(w['points'])))
for v in physical['vias']:
    objects.append((v['net'],LAYERS,Point(v['x'],v['y']).buffer(.225,quad_segs=16),'via '+str((v['x'],v['y']))))
tree=STRtree([g for n,ls,g,name in objects]);fail=[]
for i,(n,ls,g,name) in enumerate(objects):
    for j in tree.query(g.buffer(.1)):
        if j<=i:continue
        nn,ll,h,other=objects[j]
        if n!=nn and set(ls)&set(ll) and g.distance(h)<.1-2e-4:
            fail.append({'net':n,'otherNet':nn,'layers':list(set(ls)&set(ll)),'a':name,'b':other,'clearance':round(g.distance(h),6)})
    if i>=pad_count:
        for h in holes:
            if g.distance(h)<.2-2e-4:fail.append({'net':n,'a':name,'b':'NPTH','clearance':g.distance(h)})
        if max(abs(v) for v in g.bounds)>20.95+1e-4:fail.append({'net':n,'a':name,'b':'board-edge'})
via_tree=STRtree([Point(v['x'],v['y']) for v in physical['vias']])
for i,v in enumerate(physical['vias']):
    g=Point(v['x'],v['y'])
    for j in via_tree.query(g.buffer(.55)):
        if j<=i:continue
        q=physical['vias'][j];d=math.hypot(v['x']-q['x'],v['y']-q['y'])
        if 1e-5<d<.55-2e-4:fail.append({'a':v,'b':q,'viaCentreDistance':d})
(report_dir/'coil-all-copper-preflight.json').write_text(json.dumps(fail,indent=2)+'\n')
(report_dir/'coil-all-copper-preflight-meta.json').write_text(json.dumps({
    'circuitSha256':hashlib.sha256(Path(os.environ.get('CIRCUIT_INPUT','routing/placement.json')).read_bytes()).hexdigest(),
    'physicalCopperSha256':hashlib.sha256(physical_path.read_bytes()).hexdigest(),
    'findingsSha256':hashlib.sha256((report_dir/'coil-all-copper-preflight.json').read_bytes()).hexdigest(),
    'failureCount':len(fail),
    'scope':'Independent emitted explicit pads/tracks/vias, 0.10 mm copper and 0.20 mm NPTH/edge checks; not a solder-mask/paste or complete manufacturer CAM signoff.'
},indent=2)+'\n')
print(json.dumps({'copperObjects':len(objects),'failures':len(fail)}))
for f in fail[:35]:print(json.dumps(f))
if fail:raise SystemExit(1)