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/shorten-round12-analog.py

"""Candidate-only visibility shortening of existing same-layer A-output traces.
No new via, component move or whole-board rerouting. Exact foreign copper and
mounting keepouts veto every shortcut; full emitted checks are still required.
"""
from route_geometry import *
from shapely.strtree import STRtree
c=load_circuit('dist/index/circuit.json');root=connectivity(c)
mapping=json.loads(Path('routing/net-map.json').read_text())['roots']
sources={e['source_trace_id']:e for e in c if e['type']=='source_trace'}
source=next(s for s in sources.values() if s.get('name')=='CURRENT_A_OUTPUT')
net=mapping[root(source['connected_source_port_ids'][0])]
ports={e['pcb_port_id']:e for e in c if e['type']=='pcb_port'}
physical=json.loads(Path('routing/physical-copper.json').read_text())
objects={l:[] for l in LAYERS}
for e in c:
    if e['type'] not in ['pcb_smtpad','pcb_plated_hole']:continue
    p=ports.get(e.get('pcb_port_id'))
    if p and mapping[root(p['source_port_id'])]==net:continue
    for l in e.get('layers',[e.get('layer')]):objects[l].append(geometry(e,'outer_' if e['type']=='pcb_plated_hole' else ''))
for w in physical['wires']:
    if w['net']!=net:objects[w['layer']].append(LineString(w['points']).buffer(w['width']/2,quad_segs=16))
for v in physical['vias']:
    if v['net']!=net:
        for l in LAYERS:objects[l].append(Point(v['x'],v['y']).buffer(.225,quad_segs=16))
for e in c:
    if e['type']=='pcb_hole':
        for l in LAYERS:objects[l].append(geometry(e,'hole_').buffer(.1003))
for x in [-15.5,15.5]:
    for y in [-15.5,15.5]:
        objects['top'].append(Point(x,y).buffer(2.75,quad_segs=32))
        objects['bottom'].append(Point(x,y).buffer(2.5,quad_segs=32))
trees={l:STRtree(items) for l,items in objects.items()}
def safe(path,layer,width):
    shape=LineString(path).buffer(width/2,quad_segs=16)
    if max(abs(v) for v in shape.bounds)>20.95+1e-6:return False
    return all(shape.distance(objects[layer][i])>=.1-1e-6 for i in trees[layer].query(shape.buffer(.1001)))
def options(a,b):
    dx=b[0]-a[0];dy=b[1]-a[1];sx=1 if dx>=0 else -1;sy=1 if dy>=0 else -1
    d=min(abs(dx),abs(dy))
    return [[a,(a[0]+sx*d,a[1]+sy*d),b],[a,(b[0]-sx*d,b[1]-sy*d),b]]
def simplify(chunk):
    points=[]
    for p in chunk:
        if not points or math.dist(points[-1],(p['x'],p['y']))>1e-8:points.append((p['x'],p['y']))
    if len(points)<3:return chunk
    layer=chunk[0]['layer'];width=min(p['width'] for p in chunk)
    cost=[0]+[math.inf]*(len(points)-1);prev={}
    for j in range(1,len(points)):
        for i in range(j):
            for path in ([[points[i],points[j]]] if i==j-1 else options(points[i],points[j])):
                d=sum(math.dist(a,b) for a,b in zip(path,path[1:]))
                if cost[i]+d>=cost[j]-1e-8:continue
                if i!=j-1 and not safe(path,layer,width):continue
                cost[j]=cost[i]+d;prev[j]=(i,path)
    path=[];j=len(points)-1
    while j:
        i,segment=prev[j];path=segment[1:]+path;j=i
    path=[points[0]]+path
    result=[dict(route_type='wire',x=x,y=y,layer=layer,width=width) for x,y in path]
    if chunk[0].get('start_pcb_port_id'):result[0]['start_pcb_port_id']=chunk[0]['start_pcb_port_id']
    if chunk[-1].get('end_pcb_port_id'):result[-1]['end_pcb_port_id']=chunk[-1]['end_pcb_port_id']
    return result
replay=json.loads(Path('routing/complete-routes.json').read_text())
length=lambda route:sum(math.hypot(a['x']-b['x'],a['y']-b['y']) for a,b in zip(route,route[1:]) if a['route_type']==b['route_type']=='wire' and a['layer']==b['layer'])
old=0;new=0
for t in replay:
    if t['source_trace_id']!=source['source_trace_id']:continue
    old+=length(t['route']);result=[];chunk=[]
    for p in t['route']:
        if p['route_type']=='wire' and (not chunk or p['layer']==chunk[-1]['layer']):chunk.append(p);continue
        if chunk:result.extend(simplify(chunk));chunk=[]
        if p['route_type']=='wire':chunk=[p]
        else:result.append(p)
    if chunk:result.extend(simplify(chunk))
    new+=length(result);t['route']=result
Path('routing/round12-analog-shortened-routes.json').write_text(json.dumps(replay,indent=2)+'\n')
report=dict(source=source['source_trace_id'],net=net,beforeMm=old,afterMm=new,removedMm=old-new,newVias=0,otherCopperChanged=False,notInstalled=True)
Path('reports/round12-analog-shortening.json').write_text(json.dumps(report,indent=2)+'\n')
print(json.dumps(report))