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
Update 40 file(s)
4 files changed
+66−12
BRIDGE_VERSION+1−1@@ -1 +1 @@-0.9.263+0.9.264
adom-bridge-kicad-manifest.json+5−5@@ -1,15 +1,15 @@ { "manifest_version": 1, "name": "kicad",- "version": "0.9.263",- "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.263/adom-bridge-kicad-v0.9.263.zip",- "sha256": "a02a7bab619328c8118a010a11a734c73f740a7c7139f0718cb9efe75b43b37c",- "size": 1029604,+ "version": "0.9.264",+ "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.264/adom-bridge-kicad-v0.9.264.zip",+ "sha256": "6f7179349c1454ad103f2de90b8ad4f87492207b03d960b854e9de7007b8f39b",+ "size": 1030598, "verbPrefixes": [ "kicad_" ], "healthEndpoint": "/status",- "released_at": "2026-09-01T01:36:06Z",+ "released_at": "2026-09-01T01:48:44Z", "hero": "https://wiki.adom.inc/blob/app/kicad-bridge/kicad-hero.png", "languages": [ "Python",
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "kicad", "displayName": "KiCad EDA",- "version": "0.9.263",+ "version": "0.9.264", "description": "Reverse bridge for KiCad \u2014 board/schematic introspection, lint via kicad-cli, plugin install, multi-instance probe, in-process DRC.", "homepage": "https://wiki.adom.inc/adom/adom-bridge", "author": "Adom Inc.",
handlers/pcm.py+59−5@@ -46,14 +46,66 @@ _MAX_ZIP = 512 * 1024 * 1024 _UA = "adom-kicad-bridge pcm" +def _shell_documents_dir() -> Path | None:+ """The Windows *shell* Documents folder (FOLDERID_Documents), which is what+ KiCad's PATHS::GetDefault3rdPartyPath() uses via wxStandardPaths. This is+ NOT always %USERPROFILE%/Documents: with OneDrive Known Folder Redirection+ it is %USERPROFILE%/OneDrive/Documents. Caught live 2026-09-01: the bridge+ installed a package under Documents/KiCad/10.0/3rdparty while KiCad was+ scanning OneDrive/Documents/KiCad/10.0/3rdparty, so PCM showed the package+ as Installed and no library ever appeared."""+ try:+ import ctypes+ from ctypes import wintypes+ FOLDERID_Documents = ctypes.c_char_p( # {FDD39AD0-238F-46AF-ADB4-6C85480369C7}+ b"\xd0\x9a\xd3\xfd\x8f\x23\xaf\x46\xad\xb4\x6c\x85\x48\x03\x69\xc7")+ class GUID(ctypes.Structure):+ _fields_ = [("Data1", wintypes.DWORD), ("Data2", wintypes.WORD),+ ("Data3", wintypes.WORD), ("Data4", wintypes.BYTE * 8)]+ guid = GUID(0xFDD39AD0, 0x238F, 0x46AF, (wintypes.BYTE * 8)(0xAD, 0xB4, 0x6C, 0x85, 0x48, 0x03, 0x69, 0xC7))+ out = ctypes.c_wchar_p()+ if ctypes.windll.shell32.SHGetKnownFolderPath(ctypes.byref(guid), 0, None, ctypes.byref(out)) == 0:+ p = Path(out.value)+ ctypes.windll.ole32.CoTaskMemFree(out)+ return p+ except Exception: # pylint: disable=broad-except+ pass+ return None+++def _third_party_dir(kicad_info: dict) -> tuple[Path | None, str]:+ """Resolve KICAD<n>_3RD_PARTY exactly as KiCad does, in priority order:+ 1. an explicit override in kicad_common.json environment.vars+ 2. <shell Documents>/KiCad/<major.minor>/3rdparty (OneDrive-aware)+ 3. the bridge's user_dir guess (last resort, and it was wrong once)+ Returns (path, how)."""+ ver = str(kicad_info.get("version") or "")+ major = ver.split(".")[0] if ver else ""+ config_dir = kicad_info.get("config_dir")+ if config_dir and major:+ try:+ common = json.loads((Path(config_dir) / "kicad_common.json").read_text(encoding="utf-8"))+ v = ((common.get("environment") or {}).get("vars") or {}).get(f"KICAD{major}_3RD_PARTY")+ if v:+ return Path(v), "kicad_common.json override"+ except (OSError, ValueError):+ pass+ docs = _shell_documents_dir()+ if docs and ver:+ return docs / "KiCad" / ver / "3rdparty", "shell Documents (OneDrive-aware)"+ if kicad_info.get("user_dir"):+ return Path(kicad_info["user_dir"]) / "3rdparty", "user_dir fallback"+ return None, "unresolved"++ def _paths(kicad_info: dict) -> tuple[Path | None, Path | None, str | None]:- """(3rdparty dir, installed_packages.json, KiCad major version) or Nones."""- user_dir = kicad_info.get("user_dir")+ """(3rdparty dir, installed_packages.json, KiCad version) or Nones.""" config_dir = kicad_info.get("config_dir") ver = str(kicad_info.get("version") or "")- if not user_dir or not config_dir:+ third, _how = _third_party_dir(kicad_info)+ if not third or not config_dir: return None, None, None- return Path(user_dir) / "3rdparty", Path(config_dir) / "installed_packages.json", ver+ return third, Path(config_dir) / "installed_packages.json", ver def _fetch(url: str, timeout: int = 120) -> bytes:@@ -207,6 +259,7 @@ def handle_pcm_install(kicad_info: dict, args: dict) -> dict: nick = lambda names: [f"{prefix}{n}" for n in names] return {"success": True, "identifier": ident, "version": vsel["version"], "filesInstalled": ext["files"], "installedTo": str(third / "*" / ext["packageDir"]),+ "thirdPartyDir": str(third), "thirdPartyResolvedBy": _third_party_dir(kicad_info)[1], "symbolLibraries": nick(ext["libraries"]["symbols"]), "footprintLibraries": nick(ext["libraries"]["footprints"]), "modelSets": ext["libraries"]["3dmodels"],@@ -231,7 +284,8 @@ def handle_pcm_list(kicad_info: dict, args: dict) -> dict: "repository": e.get("repository_name"), "onDisk": any((third / f / _pkgdir(p.get("identifier", ""))).exists() for f in _FOLDERS)}) return {"success": True, "count": len(out), "packages": out,- "thirdPartyDir": str(third), "kicadVersion": ver}+ "thirdPartyDir": str(third), "thirdPartyResolvedBy": _third_party_dir(kicad_info)[1],+ "kicadVersion": ver} def handle_pcm_uninstall(kicad_info: dict, args: dict) -> dict: