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/author-fab-buck-routes.py

"""Local feed-forward branch relocation, candidate only.

The 5 V pickup lands on the existing inductor output pad. The independent
emitted-copper graph must still prove the original R_TOP-to-R_FF source net.
The existing capacitor-to-FB return and all other routing are retained.
"""
from route_geometry import *
import copy, hashlib

c = load_circuit("routing/fab-buck-pocket.circuit.json")
traces = {e["name"]: e for e in c if e["type"] == "source_trace" and e.get("name")}
root = connectivity(c)
mapping = json.loads(Path("routing/net-map.json").read_text())["roots"]
by_id = {t["source_trace_id"]: t for t in traces.values()}
parts = {e["name"]: e for e in c if e["type"] == "source_component"}
sp = {(e["source_component_id"], e["pin_number"]): e for e in c if e["type"] == "source_port" and "pin_number" in e}
ports = {e["source_port_id"]: e for e in c if e["type"] == "pcb_port"}
def terminal(name, pin):
    return ports[sp[parts[name]["source_component_id"], pin]["source_port_id"]]
plans = {
    "BUCK_FB_TOP": (("L_BUCK", 2), ("R_BUCK_FF", 1), [(-16.1, -6.05), (-16.1, -4.95), (-12.9, -4.95)]),
    "BUCK_FF_CAP": (("R_BUCK_FF", 2), ("C_BUCK_FF", 1), [(-12.3, -6.41), (-12.01, -6.7)]),
}
affected = {traces[name]["source_trace_id"] for name in plans}
original = json.loads(Path("routing/complete-routes.json").read_text())
routes = [t for t in original if t["source_trace_id"] not in affected]
for name, (a, b, points) in plans.items():
    start, end = terminal(*a), terminal(*b)
    path = [(start["x"], start["y"])] + points + [(end["x"], end["y"])]
    route = [dict(route_type="wire", layer="bottom", width=.15, x=x, y=y) for x, y in path]
    route[0]["start_pcb_port_id"] = start["pcb_port_id"]
    route[-1]["end_pcb_port_id"] = end["pcb_port_id"]
    source = traces[name]
    routes.append(dict(type="pcb_trace", pcb_trace_id="fab_"+name.lower(),
                       source_trace_id=source["source_trace_id"], subcircuit_id=source["subcircuit_id"],
                       subcircuit_connectivity_map_key=source.get("subcircuit_connectivity_map_key"), route=route))

result = [e for e in c if e["type"] not in ["pcb_trace", "pcb_via", "pcb_copper_pour"]]
result += copy.deepcopy(routes)
result += [copy.deepcopy(e) for e in c if e["type"] == "pcb_trace" and e.get("pcb_component_id")]
physical = dict(wires=[], vias=[])
vias = {}
for t in routes:
    s = by_id.get(t["source_trace_id"])
    if not s: s = next(e for e in c if e["type"] == "source_trace" and e["source_trace_id"] == t["source_trace_id"])
    net = mapping[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"]:
            if math.dist((a["x"], a["y"]), (b["x"], b["y"])) > 1e-8:
                physical["wires"].append(dict(net=net, layer=a["layer"], width=max(a["width"], b["width"]), points=[[a["x"], a["y"]], [b["x"], b["y"]]]))
    for p in t["route"]:
        if p["route_type"] != "via": continue
        key = round(p["x"], 6), round(p["y"], 6)
        if key in vias:
            assert vias[key]["net"] == net
            continue
        vias[key] = dict(net=net, x=p["x"], y=p["y"])
        result.append(dict(type="pcb_via", pcb_via_id="fab_candidate_"+str(len(vias)),
                           pcb_trace_id=t["pcb_trace_id"], source_trace_id=t["source_trace_id"], subcircuit_id=t["subcircuit_id"],
                           x=p["x"], y=p["y"], hole_diameter=.3, outer_diameter=.45, layers=LAYERS,
                           tented_on_top=True, tented_on_bottom=True))
native = json.loads(Path("routing/native-features.json").read_text())
for v in native["thermal_vias"] + native.get("auxiliary_vias", []):
    key = round(v["x"], 6), round(v["y"], 6)
    if key in vias: continue
    vias[key] = dict(net=v["net"], x=v["x"], y=v["y"])
    existing = next(e for e in c if e["type"] == "pcb_via" and math.dist((e["x"], e["y"]), key) < 1e-6)
    result.append(copy.deepcopy(existing))
physical["vias"] = list(vias.values())
write = lambda p, value: Path(p).write_text(json.dumps(value, indent=2)+"\n")
write("routing/fab-buck-routes.json", routes)
write("routing/fab-buck-physical.json", physical)
write("reports/fab-buck-candidate.circuit.json", result)
def length(ts, sid):
    return sum(math.dist((a["x"], a["y"]), (b["x"], b["y"])) for t in ts if t["source_trace_id"] == sid
               for a, b in zip(t["route"], t["route"][1:]) if a["route_type"] == b["route_type"] == "wire" and a["layer"] == b["layer"])
report = dict(candidateOnly=True, installed=False, changes=[dict(name=name, beforeMm=length(original, traces[name]["source_trace_id"]),
              afterMm=length(routes, traces[name]["source_trace_id"])) for name in plans])
write("reports/fab-buck-route-delta.json", report)
print(json.dumps(report))