app
KiCad - the KiCad Bridge
Public Made by Adomby adom
Drive your own KiCad from an AI, in the background: open every editor, read the whole board as data (the EDA Bridge Standard board model), stack-up, part heights, netlists, place, route, pour, DRC and exports.
master
Colby Knox
1.0.62: standard aliases for 16 of the 1.0.61 verbs, the standard-decision test, battery coverage for all 78 new verbs,…
fabfdcd
14d ago
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
#!/usr/bin/env python3
"""Write every verb into README.md between <!-- verbs:start --> and <!-- verbs:end -->, from the catalog.
python3 tools/readme_verbs.py describe.json # a saved kicad_describe reply
python3 tools/readme_verbs.py # runs the host build (rust/target/release/kicad-bridge) for it
The README is written for search: every verb is named with what it does, so a person or an AI looking for a job
finds the verb. tools/build_native_release.py runs this on every release.
"""
import json, subprocess, sys, time, urllib.request
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
START, END = "<!-- verbs:start (tools/readme_verbs.py writes this) -->", "<!-- verbs:end -->"
GROUPS = [
("Board model and reads (EDA Bridge Standard)", lambda n: n in {"routing_state", "layer_stack", "pour_state", "bodies", "netlist", "board_summary", "list_nets", "list_designators", "copper_layers", "layers_in_use", "component_data", "component_pins"}),
("Schematic", lambda n: n.startswith("sch_") or n in {"annotate", "update_pcb", "render_schematic", "design_review"}),
("Symbols, footprints and libraries", lambda n: n.startswith(("sym_", "fp_", "install_", "uninstall_library", "list_symbols", "list_footprints", "adom_library", "pcm_", "search_library", "rescan")) or n in {"create_symbol", "create_footprint", "read_symbol", "remove_symbol", "find_symbol", "make_part_project", "export_part", "model_check", "lint_library"}),
("Placement and board edits", lambda n: n in {"placement_state", "move_footprint", "placement_validate", "add_footprint", "delete_footprint", "flip_footprint", "edit_footprint", "align_footprints", "update_footprints_from_library", "add_mounting_hole", "board_outline", "set_layer_count", "delete_graphics", "footprint_graphics", "footprint_models", "import_svg", "place_decoupling", "score_placement", "check_clearance", "save_board", "save", "silk_text_batch", "silk_graphics_batch", "silk_fields_batch", "text_bounds", "board_pads", "selection", "get_selection", "select_items", "cross_probe", "select_net", "select_clear", "update_pcb_from_schematic"}),
("Routing, copper and rules", lambda n: n.startswith(("route", "add_track", "add_via", "remove_route", "autoroute", "freerouting")) or n in {"modify_track", "copy_routing", "plan_fanout", "add_zone", "apply_zone_plan", "zone_state", "remove_zone", "refill_zones", "repour", "netclasses", "net_classes", "board_rules", "custom_rules", "add_rule", "list_design_rules", "set_design_rules", "routing_validate"}),
("Checks, exports and manufacturing", lambda n: n.startswith(("run_", "lint_", "export_", "validate", "fab_")) or n in {"estimate_cost", "render_board", "format_upgrade", "errors"}),
("Projects", lambda n: n.startswith("project_")),
]
def describe():
if len(sys.argv) > 1:
return json.loads(Path(sys.argv[1]).read_text())
exe = ROOT / "rust/target/release/kicad-bridge"
p = subprocess.Popen([str(exe), "--port", "47744"], stderr=subprocess.DEVNULL)
try:
for _ in range(50):
try:
req = urllib.request.Request("http://127.0.0.1:47744/command", data=b'{"command":"kicad_describe"}', headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=2) as r:
return json.load(r)
except Exception:
time.sleep(0.1)
sys.exit("the host build never answered kicad_describe")
finally:
p.kill()
def main():
d = describe()
verbs = sorted(d["verbs"], key=lambda v: v["name"])
groups = {g: [] for g, _ in GROUPS}
other = []
for v in verbs:
n = v["name"].removeprefix("kicad_")
for g, test in GROUPS:
if test(n):
groups[g].append(v)
break
else:
other.append(v)
out = [START, "", f"Every verb of this build ({len(verbs)}, `kicad_describe` is the live catalog with inputs, hints and pitfalls). "
"A name marked *standard* is the EDA Bridge Standard's name for the verb after the arrow.", ""]
for g, vs in list(groups.items()) + [("Windows, editors, install and the bridge", other)]:
if not vs:
continue
out += [f"**{g}** ({len(vs)})", "", "| Verb | What it does |", "|---|---|"]
for v in vs:
summ = v["summary"].replace("|", "/").replace("\n", " ")
if v.get("aliasOf"):
summ = f"*standard* → `{v['aliasOf']}`"
out.append(f"| `{v['name']}` | {summ} |")
out.append("")
out.append(END)
readme = (ROOT / "README.md").read_text()
block = "\n".join(out)
if START in readme:
a, b = readme.index(START), readme.index(END) + len(END)
readme = readme[:a] + block + readme[b:]
else:
anchor = "## Verb reference"
i = readme.index(anchor)
j = readme.index("\n---", i)
readme = readme[:i] + "## Verb reference\n\n" + block + "\n" + readme[j:]
(ROOT / "README.md").write_text(readme)
print(f"README.md: {len(verbs)} verbs, {len(other)} under windows/editors/install")
if __name__ == "__main__":
main()