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/scan-current-dac.py

"""Find an actual package/pad/drill-clear pocket, without moving old parts."""
from route_geometry import *
from shapely.strtree import STRtree
from shapely.prepared import prep

c=load_circuit('baseline/v055-circuit.json');root=connectivity(c)
res_mode=os.environ.get('CURRENT_SCAN_PART')=='resistor'
part_names={e['source_component_id']:e['name'] for e in c if e['type']=='source_component'}
ref_pcb=next(e['pcb_component_id'] for e in c if e['type']=='pcb_component' and part_names[e['source_component_id']]=='R_REF_TOP')
m=json.loads(Path('../../work/nema17-clean-schematic-v054-IAQpFd/routing/net-map.json').read_text())['roots']
ss={e['source_trace_id']:e for e in c if e['type']=='source_trace'}
pp={e['pcb_port_id']:e for e in c if e['type']=='pcb_port'}
objects=[];vias=[]
def net(t):
    s=ss[t['source_trace_id']]
    return m[root((s.get('connected_source_port_ids',[])+s.get('connected_source_net_ids',[]))[0])]
for e in c:
    if res_mode and e.get('pcb_component_id')==ref_pcb:continue
    if e['type'] in ['pcb_smtpad','pcb_plated_hole']:
        p=pp.get(e.get('pcb_port_id'))
        objects.append((m.get(root(p['source_port_id'])) if p else None,e.get('layers',[e.get('layer')]),geometry(e,'outer_' if e['type']=='pcb_plated_hole' else '')))
    elif e['type']=='pcb_via':vias.append(Point(e['x'],e['y']))
    elif e['type']=='pcb_trace' and e.get('source_trace_id') and ss[e['source_trace_id']].get('name')!='DRV_REF_FEED':
        n=net(e)
        for a,b in zip(e['route'],e['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-8:
                objects.append((n,[a['layer']],LineString([(a['x'],a['y']),(b['x'],b['y'])]).buffer(max(a['width'],b['width'])/2)))
drills=STRtree(vias)
parts={e['source_component_id']:e['name'] for e in c if e['type']=='source_component'}
bodies={l:[] for l in ['top','bottom']}
for e in c:
    if res_mode and e.get('pcb_component_id')==ref_pcb:continue
    if e['type']=='pcb_component' and e['layer'] in bodies:
        bodies[e['layer']].append(box(e['center']['x']-e['width']/2-.2,e['center']['y']-e['height']/2-.2,
            e['center']['x']+e['width']/2+.2,e['center']['y']+e['height']/2+.2))
    elif e['type']=='pcb_courtyard_outline' and e['layer'] in bodies:
        if len(e['outline'])>=3:
            bodies[e['layer']].append(Polygon([(p['x'],p['y']) for p in e['outline']]))
    elif e['type']=='pcb_courtyard_rect' and e['layer'] in bodies:
        g=box(-e['width']/2,-e['height']/2,e['width']/2,e['height']/2)
        bodies[e['layer']].append(translate(rotate(g,e.get('ccw_rotation',0),origin=(0,0)),e['center']['x'],e['center']['y']))
    elif e['type']=='pcb_plated_hole':
        for layer in bodies:bodies[layer].append(geometry(e,'outer_').buffer(.1))
if res_mode:
    new=load_circuit('routing/review-placement.circuit.json')
    newids={e['source_component_id'] for e in new if e['type']=='source_component' and e['name'] in ['U_CURRENT_DAC','C_CURRENT_DAC']}
    for e in new:
        if e['type']=='pcb_component' and e['source_component_id'] in newids:
            bodies[e['layer']].append(box(e['center']['x']-e['width']/2-.25,e['center']['y']-e['height']/2-.25,
                e['center']['x']+e['width']/2+.25,e['center']['y']+e['height']/2+.25))
for layer in bodies:
    for x in [-15.5,15.5]:
        for y in [-15.5,15.5]:bodies[layer].append(Point(x,y).buffer(2.95 if layer=='top' else 2.7))
trees={l:STRtree([g for n,ls,g in objects if l in ls]) for l in bodies}
ob={l:[(n,g) for n,ls,g in objects if l in ls] for l in bodies}
bodytrees={l:STRtree(v) for l,v in bodies.items()}
# Pad nets: VOUT, GND, VDDA, SDA, SCL, GND. Only shared ground contacts may overlap.
local=[(-.94996,-1.149096,'OUT'),(0,-1.149096,'N62'),(.94996,-1.149096,'VDD'),
       (.94996,1.149096,'SDA'),(0,1.149096,'SCL'),(-.94996,1.149096,'N62')]
if res_mode:local=[(-.51,0,'N215'),(.51,0,'N189')]
results=[]
for layer in ['bottom','top']:
    for rotation in [0,90]:
        for ix in range(-74,75):
            for iy in range(-74,75):
                x,y=ix*.25,iy*.25
                b=rotate(box(-1.03,-.57,1.03,.57) if res_mode else box(-1.7,-1.936,1.7,1.936),rotation,origin=(0,0));b=translate(b,x,y)
                if len(bodytrees[layer].query(b,predicate='intersects')):continue
                count=0;touched=set();via_count=0
                for px,py,n in local:
                    # The bottom placement reflects X before PCB rotation.
                    g=box(px-.27,py-.32,px+.27,py+.32) if res_mode else box(px-.2660015,py-.5360035,px+.2660015,py+.5360035)
                    if layer=='bottom':g=rotate(g,180,origin=(0,0));g=__import__('shapely').affinity.scale(g,xfact=1,yfact=-1,origin=(0,0))
                    g=translate(rotate(g,rotation,origin=(0,0)),x,y)
                    hits=[ob[layer][int(j)][0] for j in trees[layer].query(g.buffer(.1002)) if ob[layer][int(j)][0]!=n and g.distance(ob[layer][int(j)][1])<.1001]
                    count+=len(hits);touched.update(hits)
                    via_count+=len(drills.query(g.buffer(.331),predicate='intersects'))
                if via_count==0 and (not res_mode or count==0):
                    penalty=sum(10000 if n in ['N62','N101','N179','N57','N63'] else 200 for n in touched)
                    results.append(dict(x=x,y=y,rotation=rotation,layer=layer,conflicts=count,nets=sorted(touched,key=str),score=penalty+count+math.hypot(x-8,y+14.8)))
results.sort(key=lambda r:r['score'])
Path('reports/current-'+('resistor' if res_mode else 'dac')+'-clear-pockets.json').write_text(json.dumps(results,indent=2)+'\n')
print(json.dumps(dict(clearPockets=len(results),nearest=results[:12])))