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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
"""settings.py — kicad_get_settings / kicad_set_settings: the bridge's
user-visible preferences, persisted per box.
Born 2026-08-17 (John): "place adom favicon as overlay icons into all your
kicad windows taskbar icons ... but this should be a Setting in your settings
window in your dashboard." These verbs are the settings BACKEND; the dashboard
(kicad launch page, modeled on fusion-dashboard) renders them.
Settings live in %USERPROFILE%/.adom/kicad-bridge-settings.json.
Current settings:
overlayBadges (bool, default true) — stamp the Adom favicon as a taskbar
overlay on every bridge-created KiCad window, so the user can see at a
glance which windows Adom is driving.
"""
import json
import os
from pathlib import Path
_DEFAULTS = {
"overlayBadges": True,
# How the taskbar shows "Adom is driving KiCad" (John, 2026-08-22, adopted
# from the fusion bridge): "progress" paints a progress bar on the KiCad
# button from the measured verb timings and suppresses the OS orange flash;
# "flash" leaves Windows' attention flash alone and paints no bar; "both";
# "none". Default progress: a flash nags the user to click it away, while a
# bar ends by itself exactly when Adom stops driving.
"activityIndicator": "progress",
}
def _path() -> Path:
base = os.environ.get("USERPROFILE") or str(Path.home())
d = Path(base) / ".adom"
d.mkdir(parents=True, exist_ok=True)
return d / "kicad-bridge-settings.json"
def get_settings() -> dict:
out = dict(_DEFAULTS)
try:
out.update(json.loads(_path().read_text(encoding="utf-8")))
except Exception:
pass
return out
def handle_get_settings(kicad_info: dict, args: dict) -> dict:
s = get_settings()
return {
"success": True,
"settings": s,
"defaults": dict(_DEFAULTS),
"path": str(_path()),
"_hint": ("kicad_set_settings {\"overlayBadges\": false} to change; changes apply "
"immediately (overlay badges are stamped/cleared on live windows)."),
}
_CHOICES = {"activityIndicator": ("progress", "flash", "both", "none")}
def _coerce(key, raw):
"""Type the value by its DEFAULT's type. The first cut bool()'d everything,
so activityIndicator:"flash" persisted as True and the indicator silently
ran its fallback branch (caught 2026-08-23 rebuilding the settings modal)."""
if key in _CHOICES:
v = str(raw).strip().lower()
if v not in _CHOICES[key]:
raise ValueError(f"{key} must be one of {', '.join(_CHOICES[key])}")
return v
if isinstance(_DEFAULTS[key], bool):
if isinstance(raw, str):
return raw.strip().lower() in ("1", "true", "on", "yes")
return bool(raw)
return raw
def handle_set_settings(kicad_info: dict, args: dict) -> dict:
s = get_settings()
changed = {}
if args.get("restoreDefaults"):
for k, v in _DEFAULTS.items():
if s.get(k) != v:
changed[k] = v
s[k] = v
for k in _DEFAULTS:
if k in args:
try:
v = _coerce(k, args[k])
except ValueError as e:
return {"success": False, "error": str(e)}
if s.get(k) != v:
changed[k] = v
s[k] = v
if not changed:
return {"success": True, "settings": s, "changed": {},
"output": "no settings changed",
"_hint": f"Settable keys: {', '.join(_DEFAULTS)}."}
try:
_path().write_text(json.dumps(s, indent=2), encoding="utf-8")
except Exception as e:
return {"success": False, "error": f"could not persist settings: {e}"}
applied = {}
if "overlayBadges" in changed:
try:
from handlers.win_taskbar import rebadge_all
applied["rebadgedWindows"] = rebadge_all(changed["overlayBadges"])
except Exception:
pass
return {"success": True, "settings": s, "changed": changed, **applied,
"output": f"settings updated: {changed}"}