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.
← Commit history
0.9.341: Release-day fixes: stock library path resolution (#80), screenshot_all sizeKB (#81), readiness re-detect (#82), routing DRC completeness (PR 82), verified source pushes (#79), full verb-test coverage (#78)
2 files changed
+27−5
kicad_detect.py+8−5@@ -516,7 +516,7 @@ def _parse_lib_table_entries(table_path: Path) -> list[dict]: return entries -def _resolve_kicad_var(uri: str, version: str) -> Path:+def _resolve_kicad_var(uri: str, version: str, base_dir: str | None = None) -> Path: """Resolve KiCad path variables in a library URI. KiCad uses `${VAR_NAME}` syntax for things like `${KICAD_USER_TEMPLATE_DIR}`, `${KIPRJMOD}`, `${KICAD9_FOOTPRINT_DIR}`, etc.@@ -528,12 +528,15 @@ def _resolve_kicad_var(uri: str, version: str) -> Path: userprofile = os.environ.get("USERPROFILE", "") documents = Path(userprofile) / "Documents" if userprofile else None # Common KiCad path vars + their typical resolved values+ # The stock libraries live under the INSTALLED KiCad, which is not always+ # C:/Program Files (per-user installs sit in %LOCALAPPDATA%/Programs/KiCad).+ share = Path(base_dir) / "share" / "kicad" if base_dir else Path("C:/Program Files/KiCad") / version / "share" / "kicad" substitutions = {- "KICAD_USER_TEMPLATE_DIR": str(documents / "KiCad" / version) if documents else "",+ "KICAD_USER_TEMPLATE_DIR": str(documents / "KiCad" / version / "template") if documents else "", "KICAD_3RD_PARTY": str(Path(os.environ.get("APPDATA", "")) / "kicad" / version / "3rdparty") if os.environ.get("APPDATA") else "",- f"KICAD{version.split('.')[0]}_SYMBOL_DIR": str(Path("C:/Program Files/KiCad") / version / "share" / "kicad" / "symbols"),- f"KICAD{version.split('.')[0]}_FOOTPRINT_DIR": str(Path("C:/Program Files/KiCad") / version / "share" / "kicad" / "footprints"),- f"KICAD{version.split('.')[0]}_3DMODEL_DIR": str(Path("C:/Program Files/KiCad") / version / "share" / "kicad" / "3dmodels"),+ f"KICAD{version.split('.')[0]}_SYMBOL_DIR": str(share / "symbols"),+ f"KICAD{version.split('.')[0]}_FOOTPRINT_DIR": str(share / "footprints"),+ f"KICAD{version.split('.')[0]}_3DMODEL_DIR": str(share / "3dmodels"), } resolved = uri for var, val in substitutions.items():
server.py+19@@ -258,6 +258,7 @@ def _handle_list_versions(kicad_info: dict, args: dict) -> dict: Returns: {success, versions:[full info dict + default flag], default, activeVersion, count, runningInstances, _hint} """+ _refresh_versions_if_stale() if not all_kicad_versions: return { "success": True,@@ -298,6 +299,23 @@ def _handle_list_versions(kicad_info: dict, args: dict) -> dict: } +def _refresh_versions_if_stale() -> bool:+ """Re-detect when the startup cache names a kicad.exe that no longer exists+ (issue #82: KiCad uninstalled while the bridge ran kept readiness at+ detected:true until a bridge restart). Cheap: one stat per cached version,+ a full re-detect only when something is gone. Returns True if refreshed."""+ global all_kicad_versions+ stale = [v for v in all_kicad_versions+ if v.get("kicad_exe") and not os.path.isfile(str(v["kicad_exe"]))]+ if not stale:+ return False+ try:+ all_kicad_versions = detect_all_kicad_versions() or []+ except Exception:+ all_kicad_versions = [v for v in all_kicad_versions if v not in stale]+ return True++ def _handle_readiness(kicad_info: dict, args: dict) -> dict: """Read-only readiness probe — 'is the KiCad host app present?' @@ -310,6 +328,7 @@ def _handle_readiness(kicad_info: dict, args: dict) -> dict: for "what EDA tools do I have" across all bridges use the Bridge-level `bridge_readiness`. """+ _refresh_versions_if_stale() detected = bool(all_kicad_versions) default_version = all_kicad_versions[0]["version"] if detected else None versions = [