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