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