app
AI Flow
Public Made by Adomby adom
Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board.
main
John Lauer
source at 0.1.25: the whole crate workspace, the flow, the skills and the docs
f31cc25
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
#!/usr/bin/env python3
"""Land a pours plan on the live board through kicad_add_zone, one undo step per group, then measure.
Usage: pours_live.py --target ConfRoomROG --board "C:/.../board.kicad_pcb" --pours pours.json
[--thread kicad-bridge-esc-routing] [--log pours-log.json] [--dry-run] [--pause 1.0]
The JSON is the tools/add_pours.py shape ({"zones": [...]}); "outline": true zones get the board
outline (inset by "inset" mm) resolved here from the local copy of the board given with --local,
because the verb takes explicit polygons. Zones are grouped by net into one kicad_add_zone call
each (one native undo step per net), lowest priority first. kicad_zone_state {refill:true} is read
before and after, so the log carries the copper kept per layer, the number this round is about.
"""
import argparse, json, subprocess, sys, time, os
ap = argparse.ArgumentParser()
ap.add_argument("--target", required=True); ap.add_argument("--board", required=True); ap.add_argument("--pours", required=True)
ap.add_argument("--local", help="local copy of the board, for outline: true zones")
ap.add_argument("--thread", default="kicad-bridge-esc-routing"); ap.add_argument("--log", default="pours-log.json")
ap.add_argument("--dry-run", action="store_true"); ap.add_argument("--pause", type=float, default=1.0)
a = ap.parse_args()
T = ["adom-bridge", "--ai-thread", a.thread, "--target", a.target]
def call(verb, args, timeout=300):
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
spec = json.load(open(a.pours, encoding="utf-8"))
zones = spec["zones"] if isinstance(spec, dict) else spec
outline = None
if any(z.get("outline") for z in zones):
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from escboard import Board, polygon_area
from pours import inset_polygon
b = Board(a.local)
outline = list(b.outline)
if polygon_area(outline) < 0:
outline.reverse()
groups = {}
for z in zones:
zz = {k: v for k, v in z.items() if k not in ("outline", "inset")}
if z.get("outline"):
zz["polygon"] = [[round(x, 3), round(y, 3)] for x, y in inset_polygon(outline, float(z.get("inset", 0.5)))]
groups.setdefault(z["net"], []).append(zz)
order = sorted(groups, key=lambda n: min(z.get("priority", 0) for z in groups[n]))
before = call("kicad_zone_state", {"filePath": a.board, "refill": True})
print("before:", before.get("filledAreaByLayerMm2"), before.get("filledCoverageByLayerPercent"))
st = call("kicad_routing_state", {"filePath": a.board})
rev = st.get("revision")
if not rev:
print("routing_state failed", json.dumps(st)[:600]); sys.exit(1)
log = {"before": {k: before.get(k) for k in ("filledAreaByLayerMm2", "filledCoverageByLayerPercent", "zoneCount")}, "calls": []}
t_start = time.time()
for net in order:
args = {"filePath": a.board, "expectedRevision": rev, "zones": groups[net]}
if a.dry_run:
args["dryRun"] = True
r = call("kicad_add_zone", args)
ok = r.get("success") is True
print("%-10s %d zone(s): %s %s ids %s refilled %s %.1fs %s" % (net, len(groups[net]), "ok" if ok else "FAILED", r.get("errorCode") or "", len(r.get("itemIds") or []), r.get("refilled"), r["_dt"], (r.get("postCommitError") or "")[:80]))
log["calls"].append({"net": net, "zones": groups[net], "reply": {k: v for k, v in r.items() if k not in ("_hint",)}})
if not ok:
print(json.dumps(r)[:1500]); break
rev = r.get("revision") or rev
time.sleep(a.pause)
after = call("kicad_zone_state", {"filePath": a.board, "refill": True})
print("after:", after.get("filledAreaByLayerMm2"), after.get("filledCoverageByLayerPercent"))
v = call("kicad_routing_validate", {"filePath": a.board}) if not a.dry_run else {}
print("validate:", {k: v.get(k) for k in ("errors", "warnings", "unconnected", "clean", "zonesRefilled")})
log["after"] = {k: after.get(k) for k in ("filledAreaByLayerMm2", "filledCoverageByLayerPercent", "zoneCount", "zones")}
log["validate"] = v; log["wall_seconds"] = round(time.time() - t_start, 1)
json.dump(log, open(a.log, "w"), indent=1)