app
KiCad - the KiCad Bridge
Public Made by Adomby adom
Reference implementation of the KiCad bridge: multi-instance Python server, forward path via kicad-cli, reverse path via in-process plugin. Most complex of the three bundled bridges.
master
John Lauer
ESC routing to 100%: the place-route loop (smaller signal vias, 13 parts moved, 114 commits, 0 unconnected), kicad-place-route-loop skill, video and stats; Astra pending
df61ffd
25d ago
#!/usr/bin/env python3
"""Land a placement adjustment on the live board: one kicad_move_footprint batch, one native undo step.
Usage: adjust_live.py --target ConfRoomROG --board "C:/.../esc-g431-fable-placed-planes.kicad_pcb" --moves moves.json
[--thread kicad-bridge-esc-routing] [--log adjust-log.json] [--dry-run]
moves.json is {REF: {"x", "y", "rotation"}} (what tools/pack_moves.py writes and tools/move_footprints.py
takes), so the offline fixture and the live board end up identical. The bridge refuses courtyard
overlaps and positions outside the outline and verifies every pose by read-back.
"""
import argparse, json, subprocess, sys, time
ap = argparse.ArgumentParser()
ap.add_argument("--target", required=True); ap.add_argument("--board", required=True); ap.add_argument("--moves", required=True)
ap.add_argument("--thread", default="kicad-bridge-esc-routing"); ap.add_argument("--log", default="adjust-log.json")
ap.add_argument("--dry-run", action="store_true")
a = ap.parse_args()
T = ["adom-bridge", "--ai-thread", a.thread, "--target", a.target]
def call(verb, args, timeout=240):
t0 = time.time()
out = subprocess.run(T + [verb, json.dumps(args)], capture_output=True, text=True, timeout=timeout).stdout
try:
r = json.loads(out)
except Exception:
r = {"status": "parse_error", "raw": out[:400]}
r["_t0"] = t0; r["_dt"] = round(time.time() - t0, 2); r["_at"] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(t0))
return r
moves = json.load(open(a.moves))
st = call("kicad_placement_state", {"filePath": a.board})
if st.get("status") != "ok":
print("placement_state failed", json.dumps(st)[:600]); sys.exit(1)
rev = st["revision"]
refs = [{"ref": r, "x": m["x"], "y": m["y"], "rotation": m.get("rotation", 0)} for r, m in moves.items()]
args = {"filePath": a.board, "expectedRevision": rev, "refs": refs}
if a.dry_run:
args["dryRun"] = True
r = call("kicad_move_footprint", args)
print("move_footprint:", r.get("status"), r.get("errorCode") or "", "revision", str(r.get("revision", ""))[:8], r["_dt"], "s")
if r.get("status") != "ok":
print(json.dumps(r)[:1500])
v = call("kicad_placement_validate", {"filePath": a.board}) if not a.dry_run else {}
print("validate:", {k: v.get(k) for k in ("status", "overlaps", "outside", "ratsnestMm", "errors")})
json.dump({"start": {k: x for k, x in st.items() if k != "footprints"}, "moves": moves, "reply": r, "validate": v}, open(a.log, "w"), indent=1)
sys.exit(0 if r.get("status") == "ok" else 2)