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/prune-round9-moved-pad-crossings.py
"""Retire old wire branches crossing new component lands, with explicit bridges.
Only actual copper segments crossing a moved foreign-net land are removed.
Any surviving branch boundaries must be rejoined by real checked copper.
Single-boundary former terminal leaves may be discarded; full emitted-only
connectivity subsequently decides whether any electrical connection was lost.
"""
from route_geometry import *
from collections import defaultdict
from shapely.ops import unary_union
from shapely.strtree import STRtree
planpath=Path(os.environ.get('ROUND9_PLAN','routing/round9-block-plan.json'))
plan=json.loads(planpath.read_text())
c=load_circuit(plan['placement']);root=connectivity(c)
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'}
parts={e['pcb_component_id']:e for e in c if e['type']=='pcb_component'}
names={e['source_component_id']:e['name'] for e in c if e['type']=='source_component'}
moves=json.loads(Path(plan.get('movesFile','routing/round9-placement.json')).read_text())
routes=json.loads(Path(plan['retained']).read_text())
def net(t):
s=sources[t['source_trace_id']]
return root((s['connected_source_port_ids']+s['connected_source_net_ids'])[0])
def point(p):return round(p['x'],6),round(p['y'],6)
def signature(n,l,a,b):return n,l,tuple(sorted([a,b]))
pads=defaultdict(list)
for e in c:
if e['type']!='pcb_smtpad' or names[parts[e['pcb_component_id']]['source_component_id']] not in moves:continue
pads[e['layer']].append((root(ports[e['pcb_port_id']]['source_port_id']),geometry(e)))
segments={}
for t in routes:
n=net(t)
for a,b in zip(t['route'],t['route'][1:]):
if a['route_type']!=b['route_type'] or a['route_type']!='wire' or a['layer']!=b['layer'] or point(a)==point(b):continue
key=signature(n,a['layer'],point(a),point(b));w=max(a['width'],b['width'])
if key not in segments or w>segments[key]['width']:segments[key]=dict(width=w,source=t['source_trace_id'])
retire=set()
for key,s in segments.items():
n,l,(a,b)=key;g=LineString([a,b]).buffer(s['width']/2+.1003)
if any(nn!=n and g.intersects(h) for nn,h in pads[l]):retire.add(key)
adj=defaultdict(list)
for key in segments:
n,l,(a,b)=key
adj[n,l,a].append(key);adj[n,l,b].append(key)
# Extend through junctions buried by a new foreign pad, until every exposed
# boundary has room for an honest replacement wire, not a pad-short waiver.
queue=list(retire)
while queue:
key=queue.pop();n,l,ab=key
for p in ab:
touching=adj[n,l,p];width=max(segments[k]['width'] for k in touching)
if any(nn!=n and Point(p).distance(h)<width/2+.1003 for nn,h in pads[l]):
for k in touching:
if k not in retire:retire.add(k);queue.append(k)
jobs=[];groups=[];seen=set()
for key in sorted(retire):
if key in seen:continue
n,l,ab=key;todo=[key];group=set();points=set()
while todo:
k=todo.pop()
if k in group:continue
group.add(k);seen.add(k)
for p in k[2]:
points.add(p)
todo += [v for v in adj[n,l,p] if v in retire and v not in group]
boundary=sorted(p for p in points if any(k not in retire for k in adj[n,l,p]))
width=max(segments[k]['width'] for k in group)
source=segments[next(iter(group))]['source']
groups.append(dict(net=n,layer=l,retiredSegments=len(group),boundary=boundary,width=width))
for a,b in zip(boundary,boundary[1:]):
endpoint=lambda p,label:dict(x=p[0],y=p[1],layers=[l],pcb_port_id=label)
power_roots={net({'source_trace_id':s}) for s in ['source_trace_83','source_trace_84','source_trace_106','source_trace_110','source_trace_303','source_trace_304','source_trace_381','source_trace_439','source_trace_454'] if s in sources}
constraints=dict(min_trace_thickness=width)
if n in power_roots:constraints['allowed_layers']=['top','bottom']
jobs.append(dict(source=source,constraints=constraints,
start=endpoint(a,'retained_branch_a'),target={**endpoint(b,'retained_branch_b'),'connected_tree':True}))
out=[]
for t in routes:
n=net(t);run=[]
for i,a in enumerate(t['route']):
run.append(a)
b=t['route'][i+1] if i+1<len(t['route']) else None
if b and a['route_type']==b['route_type']=='wire' and a['layer']==b['layer'] and signature(n,a['layer'],point(a),point(b)) in retire:
if len(run)>1:out.append({**t,'route':run})
run=[]
if len(run)>1:out.append({**t,'route':run})
out=[{**t,'pcb_trace_id':f'round9_pruned_{i}'} for i,t in enumerate(out)]
Path(plan['retained']).write_text(json.dumps(out,indent=2)+'\n')
vm=[j for j in plan['jobs'] if j.get('start',{}).get('pcb_port_id')=='real_VM_west_barrel']
plan['jobs']=jobs+vm+[j for j in plan['jobs'] if j not in vm]
planpath.write_text(json.dumps(plan,indent=2)+'\n')
Path(f"reports/{plan.get('prefix','round9')}-pad-crossing-retirement.json").write_text(json.dumps(dict(candidateOnly=True,groups=groups,jobs=jobs),indent=2)+'\n')
print(json.dumps(dict(retiredPhysicalSegments=len(retire),branchGroups=len(groups),requiredBridges=len(jobs))))