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-round6-xi-shortcut.py

"""Candidate-only XI fanout reroute; keep the clock length/via limits intact.

Join the relocated through-via directly to a clear point on the existing inner
clock trunk instead of retaining the generic relocation tool's return stub.
No files used by a normal build are modified.
"""
from route_geometry import *
from shapely.strtree import STRtree

c = load_circuit("dist/index/circuit.json")
root = connectivity(c)
sources = {e["source_trace_id"]: e for e in c if e["type"] == "source_trace"}
def net(e):
    s = sources[e["source_trace_id"]]
    return root((s.get("connected_source_port_ids", []) + s.get("connected_source_net_ids", []))[0])
clock = net({"source_trace_id": "source_trace_157"})
ports = {e["pcb_port_id"]: e for e in c if e["type"] == "pcb_port"}
traces = {e["pcb_trace_id"]: e for e in c if e["type"] == "pcb_trace"}
metal = []
for e in c:
    if e["type"] == "pcb_trace" and e.get("source_trace_id") and net(e) != clock:
        for a, b in zip(e["route"], e["route"][1:]):
            if a["route_type"] == b["route_type"] == "wire" and a["layer"] == b["layer"] == "inner1":
                if math.dist((a["x"], a["y"]), (b["x"], b["y"])) > 1e-8:
                    metal.append(LineString([(a["x"], a["y"]), (b["x"], b["y"])])
                                 .buffer(max(a["width"], b["width"])/2 + .1602))
    elif e["type"] == "pcb_plated_hole":
        p = ports.get(e.get("pcb_port_id"))
        if p and root(p["source_port_id"]) == clock:
            continue
        metal += [geometry(e, "outer_").buffer(.1602), geometry(e, "hole_").buffer(.3602)]
    elif e["type"] == "pcb_via":
        t = traces.get(e.get("pcb_trace_id"), {})
        if net({"source_trace_id": e.get("source_trace_id", t.get("source_trace_id"))}) != clock:
            metal.append(Point(e["x"], e["y"]).buffer(.4102))
    elif e["type"] == "pcb_hole":
        metal.append(geometry(e, "hole_").buffer(.2602))
tree = STRtree(metal)
def clear(points):
    g = LineString(points)
    return not any(g.intersects(metal[int(i)]) for i in tree.query(g))

routes = json.loads(Path("routing/relocated-vias-candidate.json").read_text())
report = json.loads(Path("reports/relocated-vias-candidate.json").read_text())
move = next(m for m in report["accepted"] if m["old"] == [1.75, -.2])
start = tuple(move["new"])
original = next(t for t in c if t["type"] == "pcb_trace" and t["pcb_trace_id"] == "whole_board_1506")
end_index = next(i for i, p in enumerate(original["route"]) if p["route_type"] == "via")
trunk = [(p["x"], p["y"]) for p in original["route"][:end_index]]
trunk = [p for i,p in enumerate(trunk) if i == 0 or p != trunk[i-1]]
candidates = []
corners = [(start[0]+ix*.1, start[1]+iy*.1) for ix in range(-12,13) for iy in range(-12,13)]
for segment, (a,b) in enumerate(zip(trunk, trunk[1:])):
    for t in range(101):
        q = (a[0]+(b[0]-a[0])*t/100, a[1]+(b[1]-a[1])*t/100)
        if not clear([q, b]):
            continue
        prefixes = [[start,q]] if clear([start,q]) else []
        for corner in corners:
            if clear([start,corner,q]):
                prefixes.append([start,corner,q])
        for prefix in prefixes:
            points = prefix + trunk[segment+1:]
            length = sum(math.dist(x,y) for x,y in zip(points,points[1:]))
            candidates.append((length, points))
assert candidates, "no clear clock shortcut; do not install"
length, points = min(candidates)
target = next(t for t in routes if t["pcb_trace_id"] == original["pcb_trace_id"])
target["route"] = [dict(route_type="wire", x=round(x,8), y=round(y,8), layer="inner1", width=.12) for x,y in points] + original["route"][end_index:]
# This fanout originally ended at the via; it has no inner-layer terminal
# at the old drill position. The new trunk supplies that connection, so do
# not retain a dead-end return stub which would add oscillator capacitance.
fanout = next(t for t in routes if t["pcb_trace_id"] == "whole_board_1505")
new_via_index = next(i for i,p in enumerate(fanout["route"]) if p["route_type"] == "via" and (p["x"],p["y"]) == start)
assert not any(p.get("end_pcb_port_id") for p in fanout["route"][new_via_index+1:])
fanout["route"] = fanout["route"][:new_via_index+1]
Path("routing/round6-xi-candidate.json").write_text(json.dumps(routes, indent=2)+"\n")
result = dict(notInstalled=True, viaFrom=move["old"], viaTo=move["new"], innerTrunkLengthMm=length,
              points=points, unchangedClockLimitMm=15, unchangedClockViaLimit=2)
Path("reports/round6-xi-shortcut.json").write_text(json.dumps(result, indent=2)+"\n")
print(json.dumps(result))