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
John Lauer
0.9.340: Live routing verbs through KiCad's IPC API (routing_state, route_net, remove_route, routing_validate), offline…
66ec283
2mo ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
"""kicad_ipc_api: read or flip KiCad's IPC API server switch (Preferences > Plugins).
The live routing verbs need `api.enable_server` in kicad_common.json. KiCad reads
that file at start, so a change needs KiCad closed and relaunched; this verb
never restarts KiCad itself and says so in needsRestart.
"""
from __future__ import annotations
import json
import os
import shutil
import sys
from datetime import datetime, timezone
from pathlib import Path
def _settings_roots() -> list[Path]:
if sys.platform == "win32":
base = os.environ.get("APPDATA")
return [Path(base) / "kicad"] if base else []
if sys.platform == "darwin":
return [Path.home() / "Library" / "Preferences" / "kicad"]
return [Path(os.environ.get("XDG_CONFIG_HOME") or Path.home() / ".config") / "kicad"]
def _common_files() -> list[Path]:
found = []
for root in _settings_roots():
if root.is_dir():
found += sorted(root.glob("*/kicad_common.json"))
def key(p):
try:
return tuple(int(x) for x in p.parent.name.split("."))
except ValueError:
return (0,)
return sorted(found, key=key, reverse=True)
def handle_ipc_api(info: dict, args: dict) -> dict:
files = _common_files()
if not files:
return {"success": False, "errorCode": "settings_not_found",
"error": "No kicad_common.json found; start KiCad once so it writes its settings",
"_hint": "KiCad creates <settings>/<version>/kicad_common.json on first launch."}
target = files[0]
try:
settings = json.loads(target.read_text(encoding="utf-8"))
except Exception as exc:
return {"success": False, "errorCode": "settings_unreadable", "error": f"{target}: {exc}"}
api = settings.get("api") if isinstance(settings.get("api"), dict) else {}
before = bool(api.get("enable_server"))
enable = args.get("enable")
result = {"success": True, "file": str(target), "kicadSettingsVersion": target.parent.name,
"enabled": before, "changed": False, "needsRestart": False}
if enable is not None and not isinstance(enable, bool):
return {"success": False, "errorCode": "invalid_arg", "error": "enable must be true or false"}
if enable is None or enable == before:
result["_hint"] = ("IPC API server is " + ("enabled" if before else "disabled") +
". Pass enable:true|false to change it (KiCad must then be restarted).")
return result
backup = target.with_name(target.name + ".adom-bak")
shutil.copy2(target, backup)
settings.setdefault("api", {})["enable_server"] = enable
tmp = target.with_name(target.name + ".adom-tmp")
tmp.write_text(json.dumps(settings, indent=2) + "\n", encoding="utf-8")
os.replace(tmp, target)
result.update(enabled=enable, changed=True, needsRestart=True, backup=str(backup),
changedAt=datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
_hint="Written to kicad_common.json. KiCad reads it at start: kicad_close, then launch "
"again, then kicad_routing_state will connect. A running KiCad that saves its "
"preferences on exit overwrites this change, so close KiCad first when in doubt.")
return result