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/preserve-software-current-session.py
"""Preserve verified session metal pruned by shortest-path source import.
Only restores actual session segments/vias. It never invents connectivity or
adds copper outside the independently preflighted physical candidate.
"""
from route_geometry import *
from shapely.ops import unary_union
from collections import defaultdict
import hashlib
c=load_circuit('routing/review-placement.circuit.json');r=connectivity(c)
m=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'}
def net(source):
s=sources[source];return m[r((s.get('connected_source_port_ids',[])+s.get('connected_source_net_ids',[]))[0])]
by_net=defaultdict(list)
for sid in sources:by_net[net(sid)].append(sid)
routes=json.loads(Path('routing/software-current-import/complete-routes.json').read_text())
physical=json.loads(Path('routing/software-current-physical-candidate.json').read_text())
metal=defaultdict(list);via_set=set()
for e in c:
if e['type'] not in ['pcb_smtpad','pcb_plated_hole']:continue
p=ports.get(e.get('pcb_port_id'))
if not p:continue
n=m[r(p['source_port_id'])];g=geometry(e,'outer_' if e['type']=='pcb_plated_hole' else '')
for layer in e.get('layers',[e.get('layer')]):metal[n,layer].append(g)
for t in routes:
n=net(t['source_trace_id'])
for p in t['route']:
if p['route_type']=='via':
via_set.add((n,round(p['x'],6),round(p['y'],6)))
for layer in LAYERS:metal[n,layer].append(Point(p['x'],p['y']).buffer(.225,quad_segs=16))
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:
metal[n,a['layer']].append(LineString([(a['x'],a['y']),(b['x'],b['y'])]).buffer(max(a['width'],b['width'])/2,quad_segs=16))
union={k:unary_union(gs) for k,gs in metal.items()};added=[];obsolete=[]
old_cap_fanout={frozenset([(3.2499,-9.2861),(4,-9.8)]),frozenset([(4,-9.8),(5.1,-10.4)]),
frozenset([(5.1,-10.4),(5.7,-9.8)]),frozenset([(5.7,-9.8),(5.71,-9.8)])}
for w in physical['wires']:
key=w['net'],w['layer'];g=LineString(w['points']).buffer(w['width']/2,quad_segs=16)
if union.get(key,Polygon()).buffer(.0002).covers(g):continue
# Shortest-path import already proves every real terminal connected.
# Do not reintroduce dangling low-current ground cutoffs, or the obsolete
# feed ending at the former C_ETH_1 land after moving that capacitor.
if (w['net']=='N62' and w['width']<.275) or (w['net']=='N118' and
frozenset(tuple(round(v,6) for v in xy) for xy in w['points']) in old_cap_fanout):
obsolete.append(w);continue
route=[dict(route_type='wire',layer=w['layer'],width=w['width'],x=x,y=y) for x,y in w['points']]
sid=by_net[w['net']][0]
routes.append(dict(type='pcb_trace',source_trace_id=sid,route=route))
union[key]=union.get(key,Polygon()).union(g)
added.append(dict(source_trace_id=sid,**w))
missing_vias=[]
native=json.loads(Path('routing/native-features.json').read_text())
native_vias={(v['net'],round(v['x'],6),round(v['y'],6)) for v in native['thermal_vias']+native.get('auxiliary_vias',[])}
for v in physical['vias']:
key=v['net'],round(v['x'],6),round(v['y'],6)
if key in via_set or key in native_vias:continue
sid=by_net[v['net']][0]
routes.append(dict(type='pcb_trace',source_trace_id=sid,route=[dict(route_type='via',x=v['x'],y=v['y'],from_layer='top',to_layer='bottom',hole_diameter=.3,outer_diameter=.45)]))
missing_vias.append(dict(source_trace_id=sid,**v));via_set.add(key)
out=Path('routing/software-current-import/full-copper-replay.json')
out.write_text(json.dumps(routes,indent=2)+'\n')
report=dict(physicalCopperSha256=hashlib.sha256(Path('routing/software-current-physical-candidate.json').read_bytes()).hexdigest(),
outputSha256=hashlib.sha256(out.read_bytes()).hexdigest(),restoredSegments=added,restoredVias=missing_vias,
removedObsoleteLowCurrentFanouts=obsolete,
outsideSessionCopperAdded=False,syntheticConnectivityLinks=0,toleranceMm=.0002)
Path('reports/software-current-session-preservation.json').write_text(json.dumps(report,indent=2)+'\n')
print(json.dumps(dict(restoredSegments=len(added),restoredVias=len(missing_vias),outsideSessionCopperAdded=False)))