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/find-fab-buck-sites.py

"""Bounded placement preflight only. Does not route or install any changes."""
from route_geometry import *
from shapely.strtree import STRtree
from shapely.affinity import rotate, translate

c = load_circuit("dist/index/circuit.json")
root = connectivity(c)
sources = {e["source_component_id"]: e for e in c if e["type"] == "source_component"}
parts = {e["pcb_component_id"]: e for e in c if e["type"] == "pcb_component"}
ports = {e["pcb_port_id"]: e for e in c if e["type"] == "pcb_port"}
traces = {e["source_trace_id"]: e for e in c if e["type"] == "source_trace"}
names = {"R_BUCK_FF", "C_BUCK_FF"}
moved = {pid for pid, p in parts.items() if sources[p["source_component_id"]]["name"] in names}
affected = {sid for sid, t in traces.items() if t.get("name") in {"BUCK_FB_TOP", "BUCK_FF_CAP", "BUCK_FF_RETURN"}}
obstacles = []
holes = [geometry(e, "hole_") for e in c if e["type"] == "pcb_hole"]
for hole in holes:
    obstacles.append(("NPTH", hole.buffer(.1001)))
for e in c:
    if e["type"] not in ["pcb_smtpad", "pcb_plated_hole"] or e.get("pcb_component_id") in moved:
        continue
    if "bottom" not in e.get("layers", [e.get("layer")]): continue
    p = ports.get(e.get("pcb_port_id"))
    obstacles.append((root(p["source_port_id"]) if p else "NC", geometry(e, "outer_" if e["type"] == "pcb_plated_hole" else "")))
for t in json.loads(Path("routing/complete-routes.json").read_text()):
    if t["source_trace_id"] in affected: continue
    s = traces[t["source_trace_id"]]
    n = root((s["connected_source_port_ids"] + s["connected_source_net_ids"])[0])
    for a, b in zip(t["route"], t["route"][1:]):
        if a["route_type"] == b["route_type"] == "wire" and a["layer"] == b["layer"] == "bottom":
            obstacles.append((n, LineString([(a["x"], a["y"]), (b["x"], b["y"])]).buffer(max(a["width"], b["width"])/2)))
    for p in t["route"]:
        if p["route_type"] == "via": obstacles.append((n, Point(p["x"], p["y"]).buffer(.225)))
tree = STRtree([g for n, g in obstacles])
def body(p):
    return box(p["center"]["x"]-p["width"]/2, p["center"]["y"]-p["height"]/2,
               p["center"]["x"]+p["width"]/2, p["center"]["y"]+p["height"]/2)
bodies = [body(p) for pid, p in parts.items() if pid not in moved and p["layer"] == "bottom"]
bodies += [g.buffer(.0501) for g in holes]
btree = STRtree(bodies)
options = {}
for pid in moved:
    p = parts[pid]; name = sources[p["source_component_id"]]["name"]
    pads = [(root(ports[e["pcb_port_id"]]["source_port_id"]), geometry(e)) for e in c
            if e["type"] == "pcb_smtpad" and e["pcb_component_id"] == pid]
    candidates = []
    for angle in [0, 90, 180, 270]:
        rotated_body = rotate(body(p), angle-p["rotation"], origin=(p["center"]["x"], p["center"]["y"]))
        rotated_pads = [(n, rotate(g, angle-p["rotation"], origin=(p["center"]["x"], p["center"]["y"]))) for n, g in pads]
        for xi in range(-140, -91):
            for yi in range(-65, 21):
                x, y = xi/10, yi/10
                dx, dy = x-p["center"]["x"], y-p["center"]["y"]
                shape = translate(rotated_body, dx, dy)
                if any(shape.distance(bodies[int(i)]) < .15-1e-6 for i in btree.query(shape.buffer(.1501))): continue
                gs = [(n, translate(g, dx, dy)) for n, g in rotated_pads]
                if any(n != obstacles[int(i)][0] and g.distance(obstacles[int(i)][1]) < .1-1e-6
                       for n, g in gs for i in tree.query(g.buffer(.1001))): continue
                candidates.append((math.dist((x, y), (-12.422904, -2.02124)), dict(x=x, y=y, rotation=angle, layer="bottom"), shape, gs))
    candidates.sort(key=lambda a: a[0])
    options[name] = candidates
pairs = []
for a in options["R_BUCK_FF"][:300]:
    for b in options["C_BUCK_FF"][:300]:
        if a[2].distance(b[2]) < .15-1e-6: continue
        if any(n != nn and g.distance(h) < .1-1e-6 for n, g in a[3] for nn, h in b[3]): continue
        cost = a[0] + 2*b[0] + math.dist((a[1]["x"], a[1]["y"]), (b[1]["x"], b[1]["y"]))
        pairs.append((cost, {"R_BUCK_FF": a[1], "C_BUCK_FF": b[1]}))
pairs.sort(key=lambda a: a[0])
report = dict(candidateOnly=True, safeSites={name: len(v) for name, v in options.items()}, closestPairs=[p for cost, p in pairs[:12]])
Path("reports/fab-buck-safe-sites.json").write_text(json.dumps(report, indent=2)+"\n")
print(json.dumps(report))