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/validate-placement.py

"""Check real copper on both assembly faces, including opposite-face holes."""
from route_geometry import *
from shapely.strtree import STRtree
import sys

circuit=load_circuit(sys.argv[1] if len(sys.argv)>1 else "routing/placement.json")
root=connectivity(circuit)
ports={e["pcb_port_id"]:e for e in circuit if e["type"]=="pcb_port"}
components={e["pcb_component_id"]:e for e in circuit if e["type"]=="pcb_component"}
names={e["source_component_id"]:e["name"] for e in circuit if e["type"]=="source_component"}
board=next(e for e in circuit if e["type"]=="pcb_board")
outline=Polygon([(p["x"],p["y"]) for p in board["outline"]])
pads=[]
for e in circuit:
    if e["type"] not in ["pcb_smtpad","pcb_plated_hole"]:continue
    port=ports.get(e.get("pcb_port_id"))
    pads.append((geometry(e,"outer_" if e["type"]=="pcb_plated_hole" else ""),
                 e.get("layers",[e.get("layer")]),root(port["source_port_id"]) if port else e[e["type"]+"_id"],
                 names[components[e["pcb_component_id"]]["source_component_id"]],e[e["type"]+"_id"]))
tree=STRtree([g for g,ls,n,name,id in pads])
holes=[(geometry(e,"hole_"),e["pcb_hole_id"]) for e in circuit if e["type"]=="pcb_hole"]
failures=[]
for i,(g,layers,net,name,id) in enumerate(pads):
    for j in tree.query(g.buffer(.1)):
        if j<=i:continue
        h,ls,nn,other,oid=pads[j]
        if net!=nn and set(layers)&set(ls) and g.distance(h)<.1-1e-6:
            failures.append(("pad-pad",name,other,id,oid,round(g.distance(h),6)))
    for h,hid in holes:
        if g.distance(h)<.2-1e-6:
            failures.append(("pad-NPTH",name,id,hid,round(g.distance(h),6)))
    if not outline.covers(g) or outline.boundary.distance(g)<.2-1e-6:
        failures.append(("pad-edge",name,id,round(outline.boundary.distance(g),6)))
    for x in [-15.5,15.5]:
        for y in [-15.5,15.5]:
            for layer,radius in [("top",2.85),("bottom",2.6)]:
                if layer in layers and Point(x,y).distance(g)<radius-1e-6:
                    failures.append(("screw-land",name,id,layer,x,y))
Path("routing/placement-clearance-report.json").write_text(json.dumps(failures,indent=2)+"\n")
print(f"Placement: {len(pads)} copper pads, {len(holes)} NPTHs, {len(failures)} cross-layer/mechanical violations")
for failure in failures[:30]:print(failure)
assert not failures