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/plan-usb-gate-sites.py

"""Read-only placement search for the six-part self-powered USB gate.

No routes or placements are installed. Existing copper, full-stack drills,
physical courtyards, board edge and both screw/standoff faces are obstacles.
"""
from route_geometry import *
from shapely.strtree import STRtree
from shapely.geometry import MultiPoint
import sys
c=load_circuit("dist/index/circuit.json")
kind=sys.argv[1] if len(sys.argv)>1 else "switch"
excluded=None
if kind in ["cap","gatecap"]:
    sid=next(e["source_component_id"] for e in c if e["type"]=="source_component" and e["name"]==("C_U_3V3_OUT" if kind=="cap" else "C_USB_GATE"))
    excluded=next(e["pcb_component_id"] for e in c if e["type"]=="pcb_component" and e["source_component_id"]==sid)
objects=[];courts={};parts={e["pcb_component_id"]:e for e in c if e["type"]=="pcb_component"}
for e in c:
    if e.get("pcb_component_id")==excluded and excluded:continue
    if e["type"] in ["pcb_smtpad","pcb_plated_hole"]:
        objects.append((e.get("layers",[e.get("layer")]),geometry(e,"outer_" if e["type"]=="pcb_plated_hole" else ""),.151))
    elif e["type"]=="pcb_via":objects.append((LAYERS,Point(e["x"],e["y"]),.351))
    elif e["type"]=="pcb_hole":objects.append((LAYERS,geometry(e,"hole_"),.201))
    elif e["type"]=="pcb_trace":
        for a,b in zip(e["route"],e["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-7:
                objects.append(([a["layer"]],LineString([(a["x"],a["y"]),(b["x"],b["y"])]).buffer(max(a["width"],b["width"])/2),.1002))
    elif e["type"].startswith("pcb_courtyard_") and e.get("pcb_component_id"):
        pid=e["pcb_component_id"]
        if e["type"]=="pcb_courtyard_outline":g=MultiPoint([(p["x"],p["y"]) for z in c if z["type"]=="pcb_courtyard_outline" and z["pcb_component_id"]==pid for p in z["outline"]]).convex_hull
        elif e["type"]=="pcb_courtyard_circle":g=Point(e["center"]["x"],e["center"]["y"]).buffer(e["radius"])
        else:
            x,y=e["center"]["x"],e["center"]["y"]
            g=rotate(box(x-e["width"]/2,y-e["height"]/2,x+e["width"]/2,y+e["height"]/2),e.get("ccw_rotation",0),origin=(x,y))
        courts[pid]=courts.get(pid,Polygon()).union(g)
if kind in ["cap","gatecap"] and Path("routing/round7-usb-physical-candidate.json").exists():
    physical=json.loads(Path("routing/round7-usb-physical-candidate.json").read_text())
    for v in physical["vias"]:
        objects.append((LAYERS,Point(v["x"],v["y"]),.351))
    if kind=="gatecap":
        for w in physical["wires"]:objects.append(([w["layer"]],LineString(w["points"]).buffer(w["width"]/2),.1003))
barrels=[geometry(e,"outer_") for e in c if e["type"]=="pcb_plated_hole"]
tree=STRtree([g for ls,g,d in objects]);court_items=[(parts[p]["layer"],g) for p,g in courts.items()];ctree=STRtree([g for l,g in court_items])
if kind=="switch":
    pads=[box(x-.275,y-h/2,x+.275,y+h/2) for x in [-.675,.675] for y,h in [(-.5,.25),(0,.2),(.5,.25)]]
    pads += [box(-.15,y-.3,.15,y+.3) for y in [-.65,.65]];court=box(-1,-1,1,1)
elif kind=="npn":
    pads=[box(.5,y-.325,1.5,y+.325) for y in [-.95,.95]]+[box(-1.5,-.325,-.5,.325)];court=box(-1.75,-1.75,1.75,1.75)
elif kind in ["cap","gatecap"]:
    at=parts[excluded]["center"];angle=parts[excluded]["rotation"]
    pads=[rotate(translate(geometry(e),-at["x"],-at["y"]),-angle,origin=(0,0)) for e in c if e["type"]=="pcb_smtpad" and e["pcb_component_id"]==excluded]
    court=box(-1.68,-.95,1.68,.95) if kind=="cap" else box(-.93,-.47,.93,.47)
else:pads=[box(x-.32,-.27,x+.32,.27) for x in [-.51,.51]];court=box(-.98,-.45,.98,.45)
sites=[]
grid=sorted((math.dist((x/5,y/5),(-10.4,10.75) if kind=="gatecap" else (-7.5,11)),x/5,y/5) for x in range(-100,101) for y in range(15,100))
for _,x,y in grid:
    for layer in ["top","bottom"]:
        for angle in [0,90,180,270]:
            transform=lambda g:translate(rotate(g,angle,origin=(0,0)),x,y)
            cc=transform(court)
            if max(abs(z) for z in cc.bounds)>20.95:continue
            if any(cc.intersection(g).area>1e-8 for g in barrels):continue
            if any(court_items[i][0]==layer and cc.intersection(court_items[i][1]).area>1e-8 for i in ctree.query(cc)):continue
            if any(cc.distance(Point(xx,yy))<(2.85 if layer=="top" else 2.6) for xx in [-15.5,15.5] for yy in [-15.5,15.5]):continue
            if any(any(layer in objects[i][0] and g.distance(objects[i][1])<objects[i][2] for i in tree.query(g.buffer(.352))) for g in map(transform,pads)):continue
            if any(l==layer and math.dist((x,y),(xx,yy))<.8 for xx,yy,r,l in sites):continue
            sites.append((x,y,angle,layer))
    if len(sites)>=16:break
report=dict(kind=kind,sites=sites,notInstalled=True)
Path(f"reports/round7-usb-{kind}-sites.json").write_text(json.dumps(report,indent=2)+"\n")
print(json.dumps(report))