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/widen-5v-candidate.py

"""Size existing 5 V copper against real obstacles; never move a centerline.

Candidate only. Keep narrow unavoidable escape sections rather than relaxing
clearance. Native build, independent DFM and continuity must pass before use.
"""
from route_geometry import *
from shapely.strtree import STRtree
from collections import Counter
import hashlib

c=load_circuit("dist/index/circuit.json");root=connectivity(c)
src={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(t):
    s=src[t["source_trace_id"]]
    return root((s.get("connected_source_port_ids",[])+s.get("connected_source_net_ids",[]))[0])
target=net(next(e for e in src.values() if e.get("name")=="BUCK_OUT"))
routes=json.loads(Path("routing/complete-routes.json").read_text())
metal=[];pths=[];vias=[];holes=[]
for t in routes:
    if net(t)==target: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:
            metal.append((a["layer"],LineString([(a["x"],a["y"]),(b["x"],b["y"])]).buffer(max(a["width"],b["width"])/2,quad_segs=20)))
traces={e["pcb_trace_id"]:e for e in c if e["type"]=="pcb_trace"}
for e in c:
    if e["type"] in ["pcb_smtpad","pcb_plated_hole"]:
        p=ports.get(e.get("pcb_port_id"));n=root(p["source_port_id"]) if p else None
        if n==target:continue
        for l in e.get("layers",[e.get("layer")]):metal.append((l,geometry(e,"outer_" if e["type"]=="pcb_plated_hole" else "")))
        if e["type"]=="pcb_plated_hole":pths.append(geometry(e,"hole_"))
    elif e["type"]=="pcb_via":
        source=e.get("source_trace_id",traces.get(e.get("pcb_trace_id"),{}).get("source_trace_id"))
        if net({"source_trace_id":source})!=target:vias.append(Point(e["x"],e["y"]))
    elif e["type"]=="pcb_hole":holes.append(geometry(e,"hole_"))
trees={l:STRtree([g for ll,g in metal if ll==l]) for l in LAYERS}
by_layer={l:[g for ll,g in metal if ll==l] for l in LAYERS}
vtrees=STRtree(vias);htree=STRtree(holes);ptree=STRtree(pths)
def valid(a,b,width):
    g=LineString([(a["x"],a["y"]),(b["x"],b["y"])]).buffer(width/2,quad_segs=24)
    l=a["layer"]
    if max(abs(x) for x in g.bounds)>20.9499:return False
    if any(g.distance(by_layer[l][int(i)])<.1001 for i in trees[l].query(g.buffer(.101))):return False
    if any(g.distance(vias[int(i)])<.3501 for i in vtrees.query(g.buffer(.351))):return False
    if any(g.distance(holes[int(i)])<.2001 for i in htree.query(g.buffer(.201))):return False
    required=.3001 if l.startswith("inner") else .2801
    if any(g.distance(pths[int(i)])<required for i in ptree.query(g.buffer(required+.001))):return False
    radius=2.85 if l=="top" else 2.6 if l=="bottom" else 0
    if radius and any(g.distance(Point(x,y))<radius for x in [-15.5,15.5] for y in [-15.5,15.5]):return False
    return True
changed={};lengths=Counter()
def xy(p):return round(p["x"],6),round(p["y"],6)
for t in routes:
    if net(t)!=target:continue
    out=[]
    for a,b in zip(t["route"],t["route"][1:]):
        out.append(a)
        if a["route_type"]!=b["route_type"] or a["route_type"]!="wire" or a["layer"]!=b["layer"] or math.dist(xy(a),xy(b))<1e-8:continue
        old=max(a["width"],b["width"])
        if old>=.8:continue
        key=a["layer"],tuple(sorted([xy(a),xy(b)]))
        if key not in changed:
            new=next((w for w in [.8,.6,.4,.3,.25,.2,.15] if w>old+1e-7 and valid(a,b,w)),old)
            changed[key]=(old,new,math.dist(xy(a),xy(b)))
        new=changed[key][1]
        if new>old+1e-7:
            # Explicit zero-length width transitions prevent a widened
            # segment from silently widening either untouched neighbor.
            out.extend([{k:v for k,v in a.items() if k not in ["start_pcb_port_id","end_pcb_port_id"]}|{"width":new},
                        {k:v for k,v in b.items() if k not in ["start_pcb_port_id","end_pcb_port_id"]}|{"width":new}])
    out.append(t["route"][-1]);t["route"]=out
for old,new,length in changed.values():lengths[round(new,3)]+=length
Path("routing/round5-widened-5v-candidate.json").write_text(json.dumps(routes,indent=2)+"\n")
report=dict(notInstalled=True,circuitSha256=hashlib.sha256(Path("dist/index/circuit.json").read_bytes()).hexdigest(),
    widenedUniqueSegments=sum(new>old+1e-7 for old,new,d in changed.values()),lengthByWidthMm=dict(lengths),
    caveat="Exact centerlines retained; unresolved narrow sections are not a claimed current rating. Native and independent rebuilt checks remain mandatory.")
Path("reports/round5-5v-width-plan.json").write_text(json.dumps(report,indent=2)+"\n")
print(json.dumps(report))