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-round7-output-cap.py

"""Find ordinary, drill-free output-capacitor sites near the actual LDO.

Read-only: checks real emitted copper, pads, drills, courtyards and hardware.
Same-net copper is allowed under its own land; no drill is allowed in a land.
"""
from route_geometry import *
from shapely.geometry import MultiPoint
from shapely.strtree import STRtree

c=load_circuit("dist/index/circuit.json");root=connectivity(c)
mapping=json.loads(Path("routing/net-map.json").read_text())["roots"]
physical=json.loads(Path("routing/physical-copper.json").read_text())
names={s["source_component_id"]:s["name"] for s in c if s["type"]=="source_component"}
parts={p["pcb_component_id"]:p for p in c if p["type"]=="pcb_component"}
ports={p["pcb_port_id"]:p for p in c if p["type"]=="pcb_port"}
cap=next(p for p in parts.values() if names[p["source_component_id"]]=="C_U_3V3_OUT")
old=cap["center"];angle=cap["rotation"];cap_pads=[];objects=[];courts={}
for e in c:
    if e["type"] in ["pcb_smtpad","pcb_plated_hole"]:
        p=ports.get(e.get("pcb_port_id"));n=mapping[root(p["source_port_id"])] if p else "NC"
        g=geometry(e,"outer_" if e["type"]=="pcb_plated_hole" else "")
        if e["pcb_component_id"]==cap["pcb_component_id"]:
            cap_pads.append((n,rotate(translate(g,-old["x"],-old["y"]),-angle,origin=(0,0))))
        else:objects.append((e.get("layers",[e.get("layer")]),n,g,.1503))
        if e["type"]=="pcb_plated_hole":objects.append((LAYERS,"DRILL",geometry(e,"hole_"),.2803))
    elif e["type"]=="pcb_via":objects.append((LAYERS,"DRILL",Point(e["x"],e["y"]),.354))
    elif e["type"]=="pcb_hole":objects.append((LAYERS,"DRILL",geometry(e,"hole_"),.2003))
    elif e["type"].startswith("pcb_courtyard_") and e.get("pcb_component_id")!=cap["pcb_component_id"]:
        pid=e.get("pcb_component_id")
        if not pid or parts[pid].get("do_not_place"):continue
        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:
            at=e["center"];g=rotate(box(at["x"]-e["width"]/2,at["y"]-e["height"]/2,at["x"]+e["width"]/2,at["y"]+e["height"]/2),e.get("ccw_rotation",0),origin=(at["x"],at["y"]))
        courts[pid]=courts.get(pid,Polygon()).union(g)
for w in physical["wires"]:objects.append(([w["layer"]],w["net"],LineString(w["points"]).buffer(w["width"]/2),.1003))
tree=STRtree([o[2] for o in objects]);barrels=[geometry(e,"outer_") for e in c if e["type"]=="pcb_plated_hole"]
sites=[]
grid=sorted((math.dist((x/5,y/5),(-3,7.9)),x/5,y/5) for x in range(-80,81) for y in range(38,96))
for distance,x,y in grid:
    for rotation in [0,90,180,270]:
        move=lambda g:translate(rotate(g,rotation,origin=(0,0)),x,y)
        court=move(box(-1.68,-.95,1.68,.95))
        if max(abs(z) for z in court.bounds)>20.95:continue
        if any(parts[pid]["layer"]=="bottom" and court.intersection(g).area>1e-8 for pid,g in courts.items()):continue
        if any(court.intersection(g).area>1e-8 for g in barrels):continue
        if any(court.distance(Point(a,b))<2.6 for a in [-15.5,15.5] for b in [-15.5,15.5]):continue
        good=True
        for n,g in cap_pads:
            pad=move(g)
            for i in tree.query(pad.buffer(.355)):
                layers,other,shape,margin=objects[int(i)]
                if "bottom" in layers and other!=n and pad.distance(shape)<margin:good=False;break
            if not good:break
        if good:
            if any(math.dist((x,y),(s["x"],s["y"]))<.6 for s in sites):continue
            sites.append(dict(x=x,y=y,rotation=rotation,layer="bottom",distanceToLdoOutput=round(distance,3)))
    if len(sites)>=12:break
result=dict(sites=sites,notInstalled=True)
Path("reports/round7-output-cap-sites.json").write_text(json.dumps(result,indent=2)+"\n")
print(json.dumps(result))