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.279: Wiki #71 second cause, measured on ConfRoomROG with KiCad's own parser: one malformed symbol makes KiCad refuse a whole .kicad_sym (Symbol Editor lists the library EMPTY) while the bridge's structural reader called it clean. New handlers/kicad_parse_check.py runs kicad-cli sym export svg (read-only) as KiCad's verdict and inventory. kicad_install_symbol and kicad_install_library refuse a file KiCad refuses before writing (kicad_refuses_symbol / kicad_refuses_library); kicad_adom_library_status, kicad_lint_library and the kicad_show_library error carry kicadParser; kicad_adom_library_heal mode:prune drops the symbols KiCad refuses (dryRun names them, original kept as .pruned.bak). Lint's checkmark now says whether KiCad was consulted. Also measured: KiCad's library index is process-wide; a reopened editor frame keeps the old symbol set until kicad.exe fully restarts.
2 files changed
+64−10
handlers/open_symbol_editor.py+42−9@@ -202,18 +202,51 @@ def _stale_explanation(kicad_info: dict, name: str) -> dict: "registered file. Re-run kicad_install_symbol and the part lands where " "KiCad reads; kicad_adom_library_status shows both paths."), }+ # wiki #71 (2026-09-03, ConfRoomROG): the paths agreed and the symbol was in+ # the file, and KiCad still showed the library EMPTY, because ONE malformed+ # symbol makes KiCad's parser refuse the whole file. Our reader does not see+ # that. Ask KiCad before blaming navigation.+ try:+ from handlers import kicad_parse_check as _kpc+ _v = _kpc.verdict_file(kicad_info, rep.get("registeredPath")) if rep.get("registeredPath") else {}+ except Exception: # pylint: disable=broad-except+ _v = {}+ if _v.get("available") and not _v.get("ok"):+ return {+ "errorCode": "library_refused_by_kicad",+ "registeredPath": rep.get("registeredPath"),+ "kicadParser": _v,+ "error": (f"'{name}' is in the file KiCad reads, but KiCad's parser REFUSES that file "+ f"({_v.get('error')}), so the Symbol Editor lists the Adom library empty "+ f"(title: {title})"),+ "_hint": ("One malformed symbol hides the whole library. kicad_adom_library_heal "+ "{\"mode\":\"prune\",\"dryRun\":true} names the culprit(s) with KiCad's reason; "+ "without dryRun it rebuilds the file from the symbols KiCad accepts (original kept "+ "as .pruned.bak). Then reopen the editor."),+ }+ if _v.get("available") and _v.get("ok") and name.lower() not in {n.lower() for n in _v.get("symbols") or []}:+ return {+ "errorCode": "symbol_not_loaded_by_kicad",+ "registeredPath": rep.get("registeredPath"),+ "kicadParser": _v,+ "error": (f"KiCad's parser loads {len(_v.get('symbols') or [])} symbol(s) from the registered file "+ f"and '{name}' is not one of them, although the bridge's reader finds it there "+ f"(title: {title})"),+ "_hint": "Compare kicadParser.symbols with kicad_adom_library_status.symbols; report the file if they differ.",+ } return { "error": (f"'{name}' is in the Adom library KiCad reads ({rep.get('registeredPath') or 'registered path'}), "- f"but the open Symbol Editor did not load it in the background "+ f"KiCad's parser accepts that file"+ + (f" and lists '{name}'" if _v.get("ok") else "")+ + f", but the open Symbol Editor did not load it in the background " f"(title: {title})"),- "_hint": ("The file and the table agree, so this is not a path problem. What is NOT "- "known is why the editor did not load it: the bridge has no measurement "- "that separates 'the editor has not indexed this file yet' from 'the "- "background navigation missed'. kicad_rescan_libraries force:true restarts "- "KiCad (reported by one user as NOT sufficient on 10.0 for symbols, wiki #71); "- "kicad_screenshot_all shows what the tree actually lists. If you can "- "reproduce with force:true failing while paths agree, report it - that is "- "the unmeasured case."),+ "kicadParser": _v or None,+ "_hint": ("Path, table and KiCad's parser all agree, so the remaining cause is KiCad's "+ "PROCESS-WIDE library index: measured on ConfRoomROG (wiki #71), an editor frame "+ "reopened while kicad.exe stayed alive kept listing the OLD symbol set; only a "+ "full close of KiCad and a cold reopen re-read the file. kicad_rescan_libraries "+ "force:true does that restart (refused when a human launched KiCad). "+ "kicad_screenshot_all shows what the tree actually lists."), }
server.py+22−1@@ -207,13 +207,34 @@ def _handle_adom_library_status(kicad_info: dict, args: dict) -> dict: "registeredPath from 0.9.274.") except Exception: pass+ # wiki #71 (2026-09-03): the count above is the BRIDGE's reader. Ask KiCad+ # too: kicad-cli refused a library our reader called clean, and KiCad then+ # shows the whole library empty. kicadParser is KiCad's own inventory.+ try:+ from handlers import kicad_parse_check as _kpc+ reg = result.get("registeredPath")+ if reg:+ v = _kpc.verdict_file(kicad_info, reg)+ result["kicadParser"] = v+ if v.get("available") and not v.get("ok"):+ result["_hint"] = ("KICAD REFUSES THIS LIBRARY: kicad-cli says '%s'. The Symbol Editor will "+ "list the Adom library EMPTY no matter how many symbols the bridge counts "+ "(registeredSymbols=%s). One malformed symbol does this to the whole file. "+ "kicad_adom_library_heal {\"mode\":\"prune\",\"dryRun\":true} names the "+ "culprit(s); without dryRun it rebuilds the file from the symbols KiCad accepts."+ % (v.get("error"), result.get("registeredSymbols")))+ elif v.get("available") and v.get("ok") and result.get("registeredSymbols") not in (None, len(v.get("symbols") or [])):+ result["_hint"] = ("KiCad loads %d symbol(s) from the registered file; the bridge's reader counts %s. "+ "Trust kicadParser.symbols." % (len(v.get("symbols") or []), result.get("registeredSymbols")))+ except Exception:+ pass return {"success": True, **result} def _handle_adom_library_heal(kicad_info: dict, args: dict) -> dict: """kicad_adom_library_heal - reconcile a split Adom library (wiki #71).- Human-chosen mode: merge (default) or repoint. dryRun:true previews."""+ Human-chosen mode: merge (default), repoint, or prune (drop symbols KiCad refuses). dryRun:true previews.""" return heal_split(kicad_info, mode=(args.get("mode") or "merge"), dry_run=bool(args.get("dryRun"))) def _handle_list_versions(kicad_info: dict, args: dict) -> dict: