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))