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/analyze-round10-widths.py
"""Read-only width provenance and exact-clearance analysis for round 10."""
from route_geometry import *
from shapely.ops import unary_union
from shapely.strtree import STRtree
import hashlib
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'}
ports={e['pcb_port_id']:e for e in c if e['type']=='pcb_port'}
names={e['source_component_id']:e['name'] for e in c if e['type']=='source_component'}
parts={e['pcb_component_id']:names[e['source_component_id']] for e in c if e['type']=='pcb_component'}
def net(s):return mapping[root((s.get('connected_source_port_ids',[])+s.get('connected_source_net_ids',[]))[0])]
vm=net(sources['source_trace_305']);eth=net(sources['source_trace_190'])
physical=json.loads(Path('routing/physical-copper.json').read_text())
objects=[]
for e in c:
if e['type'] in ['pcb_smtpad','pcb_plated_hole']:
p=ports.get(e.get('pcb_port_id'));n=mapping.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 ''),
dict(kind='pad',part=parts.get(e.get('pcb_component_id')),port=e.get('pcb_port_id'))))
for w in physical['wires']:
objects.append((w['net'],[w['layer']],LineString(w['points']).buffer(w['width']/2,quad_segs=16),dict(kind='wire',**w)))
for v in physical['vias']:
objects.append((v['net'],LAYERS,Point(v['x'],v['y']).buffer(.225,quad_segs=16),dict(kind='via',**v)))
tree=STRtree([o[2] for o in objects])
def linekey(l,points):return l,tuple(sorted(tuple(round(q,6) for q in p) for p in points))
ethkeys=set()
for t in c:
if t['type']!='pcb_trace' or t.get('source_trace_id')!='source_trace_190':continue
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-8:
ethkeys.add(linekey(a['layer'],[(a['x'],a['y']),(b['x'],b['y'])]))
candidates=[]
for i,w in enumerate(physical['wires']):
width=.8 if w['net']==vm and abs(w['width']-.5)<1e-6 else .25 if w['net']==eth and linekey(w['layer'],w['points']) in ethkeys and w['width']<.25 else None
if not width:continue
g=LineString(w['points']).buffer(width/2,quad_segs=16)
conflicts=[]
for j in tree.query(g.buffer(.1)):
n,ls,h,description=objects[int(j)]
if n!=w['net'] and w['layer'] in ls and g.distance(h)<.1-2e-4:
conflicts.append(dict(**description,clearance=round(g.distance(h),6)))
candidates.append(dict(index=i,**w,proposedWidth=width,conflicts=conflicts))
bc=[t for t in c if t['type']=='pcb_trace' and t.get('source_trace_id')=='source_trace_135']
real_bc_min=min(p['width'] for t in bc for p in t['route'] if p['route_type']=='wire')
report=dict(circuitSha256=hashlib.sha256(Path('dist/index/circuit.json').read_bytes()).hexdigest(),vmNet=vm,ethernetNet=eth,
ethernetMainPathMinimumMm=real_bc_min,ethernetBranchRequestedMm=sources['source_trace_190'].get('min_trace_thickness'),
ethernetWarningTargetsDifferentTrace=True,candidates=candidates)
Path('reports/round10-width-analysis.json').write_text(json.dumps(report,indent=2)+'\n')
for label,n in [('VM',vm),('ETH',eth)]:
chosen=[x for x in candidates if x['net']==n]
print(json.dumps(dict(kind=label,net=n,segments=len(chosen),length=sum(LineString(x['points']).length for x in chosen),
conflicts=[dict(points=x['points'],layer=x['layer'],against=x['conflicts']) for x in chosen if x['conflicts']])))
print(json.dumps(dict(ethernetMainPathMinimumMm=real_bc_min,ethernetBranchRequestedMm=sources['source_trace_190'].get('min_trace_thickness'))))