← Commit history

install_library: footprint installs carry a bundle's 3D models (0.9.251)

Colby Knox ·39e1177c84 ·1mo ago ·parent 5524915
Cloud Library bundles ship 3dmodels/ beside the .pretty with relative model refs, which KiCad resolves against the PROJECT dir, so installed footprints lost their 3D. Footprint installs now take modelsPath or auto-detect the 3dmodels/ sibling: models copy into 3dmodels/<lib>.3dshapes and the COPIED footprints' matching refs rewrite to the installed absolute paths (matching the absolute fp-lib-table URI convention). Source .pretty never modified; stock-var refs untouched.
5 files changed +75−10
BRIDGE_VERSION+1−1
@@ -1 +1 @@-0.9.250\ No newline at end of file+0.9.251\ No newline at end of file
adom-bridge-kicad-manifest.json+5−5
@@ -1,15 +1,15 @@ {   "manifest_version": 1,   "name": "kicad",-  "version": "0.9.250",-  "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.250/adom-bridge-kicad-v0.9.250.zip",-  "sha256": "7abccebea835d2bd79517c361d48adbfee8ee615f99c4315590071eea5ec6dce",-  "size": 1012007,+  "version": "0.9.251",+  "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.251/adom-bridge-kicad-v0.9.251.zip",+  "sha256": "f84e87f08c6e8881d93e50053f0416608cdb5292c5a58c89968d499e9f8ab805",+  "size": 449044,   "verbPrefixes": [     "kicad_"   ],   "healthEndpoint": "/status",-  "released_at": "2026-08-24T11:09:20Z",+  "released_at": "2026-08-26T20:34:19Z",   "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.250",+  "version": "0.9.251",   "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/install_library.py+65
@@ -110,6 +110,39 @@ def _install_footprint(kicad_info: dict, args: dict) -> dict:             if source.suffix == ".kicad_mod":                 _add_model_to_footprint(pretty_dir / source.name, model_installed_path) +    # Library-bundle 3D models (Adom wiki Cloud Library bundles): a directory+    # of model files (modelsPath arg), or auto-detected as a `3dmodels/`+    # sibling of the source .pretty (the bundle layout). The bundle's+    # footprints reference models by RELATIVE `3dmodels/<file>` paths, which+    # KiCad resolves against the PROJECT dir, not the library — so after+    # copying the models into the user's 3dmodels/<lib>.3dshapes dir, rewrite+    # the COPIED footprints' matching refs to the absolute installed path+    # (forward slashes). Absolute matches this installer's existing+    # convention: the fp-lib-table URI it writes is absolute already. The+    # SOURCE .pretty is never modified.+    models_installed = 0+    refs_rewritten = 0+    models_src = args.get("modelsPath", "")+    if not models_src and source.is_dir() and source.suffix == ".pretty":+        sibling = source.parent / "3dmodels"+        if sibling.is_dir():+            models_src = str(sibling)+    if models_src:+        models_src_dir = Path(models_src)+        if models_src_dir.is_dir():+            models_dir = Path(user_dir) / "3dmodels" / f"{lib_name}.3dshapes"+            models_dir.mkdir(parents=True, exist_ok=True)+            installed_by_name = {}+            for mf in sorted(models_src_dir.iterdir()):+                if not mf.is_file():+                    continue+                dest_model = models_dir / mf.name+                shutil.copy2(str(mf), str(dest_model))+                installed_by_name[mf.name] = str(dest_model).replace("\\", "/")+                models_installed += 1+            if installed_by_name:+                refs_rewritten = _rewrite_model_refs(pretty_dir, installed_by_name)+     # Register in fp-lib-table     table_path = Path(config_dir) / "fp-lib-table"     table = LibTable.parse_file(str(table_path))@@ -150,10 +183,42 @@ def _install_footprint(kicad_info: dict, args: dict) -> dict:     if model_installed_path:         result["data"]["model3dPath"] = model_installed_path         result["output"] += f". 3D model installed to {model_installed_path}"+    if models_installed:+        result["data"]["modelsInstalled"] = models_installed+        result["data"]["modelRefsRewritten"] = refs_rewritten+        result["output"] += (+            f". {models_installed} 3D model(s) installed to 3dmodels/{lib_name}.3dshapes; "+            f"{refs_rewritten} footprint model ref(s) rewritten to the installed paths"+        )      return result  +def _rewrite_model_refs(pretty_dir: Path, installed_by_name: dict) -> int:+    """Rewrite (model "...") refs in every .kicad_mod under pretty_dir whose+    BASENAME matches an installed model file, pointing them at the absolute+    installed path. Refs to models we did not install are left untouched+    (they may be stock ${KICAD*_3DMODEL_DIR} refs that already resolve)."""+    rewritten = 0++    def _sub(match):+        nonlocal rewritten+        ref = match.group(2)+        base = ref.replace("\\", "/").rsplit("/", 1)[-1]+        dest = installed_by_name.get(base)+        if not dest:+            return match.group(0)+        rewritten += 1+        return f'{match.group(1)}"{dest}"'++    for mod in sorted(pretty_dir.glob("*.kicad_mod")):+        text = mod.read_text(encoding="utf-8")+        new_text = re.sub(r'(\(model\s+)"([^"]+)"', _sub, text)+        if new_text != text:+            mod.write_text(new_text, encoding="utf-8")+    return rewritten++ def _add_model_to_footprint(footprint_path: Path, model_path: str):     """Add a (model ...) reference to a .kicad_mod file."""     text = footprint_path.read_text(encoding="utf-8")
server.py+3−3
@@ -353,10 +353,10 @@ _VERB_CATALOG = {     "window_info": {"summary": "Report geometry/state for open KiCad windows (auto-expires benign OpenGL dialogs).", "long": False,         "hint": "Read-only; returns each KiCad window's position/size/state. Use before click/send_key to target the right window.",         "related": ["kicad_open_editors", "kicad_click", "kicad_send_key"], "pitfalls": ["coordinates are screen-absolute — re-read after a window moves/resizes"]},-    "install_library": {"summary": "Register a symbol/footprint library in the user's sym-/fp-lib-table.", "long": False,-        "hint": "Adds a library to the selected KiCad version's lib table so its parts are usable.",+    "install_library": {"summary": "Register a symbol/footprint library in the user's sym-/fp-lib-table (footprint installs also carry a bundle's 3D models).", "long": False,+        "hint": "Adds a library to the selected KiCad version's lib table so its parts are usable. Footprint installs take modelsPath (a directory of STEP/WRL files) or auto-detect a 3dmodels/ sibling of the source .pretty (the Adom wiki library-bundle layout): models copy into 3dmodels/<lib>.3dshapes and the COPIED footprints' matching (model ...) refs are rewritten to the installed absolute paths, so 3D previews resolve.",         "related": ["kicad_install_symbol", "kicad_install_footprint", "kicad_list_versions"],-        "pitfalls": ["each KiCad version has its OWN lib tables — install under every version the user needs", "restart/reload KiCad to see a newly-added library"]},+        "pitfalls": ["each KiCad version has its OWN lib tables — install under every version the user needs", "restart/reload KiCad to see a newly-added library", "model-ref rewriting touches only the INSTALLED copy, never the source, and only refs whose basename matches an installed model (stock ${KICAD*_3DMODEL_DIR} refs stay)"]},     "install_symbol": {"summary": "Install a .kicad_sym into the user's sym-lib-table.", "long": False,         "hint": "Absolute path to a .kicad_sym; registers it in the sym-lib-table.",         "related": ["kicad_open_symbol_editor", "kicad_install_library", "kicad_install_footprint"],