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
v0.9.109: kicad_export_part (one-off single-part library exports: symbol-only .kicad_sym, footprint-only .pretty, bare model); make_part_project boards are now part-dominant (tight edge cuts) so the chip is visible in the 3D board view
8 files changed
+154−19
BRIDGE_VERSION+1−1@@ -1 +1 @@-0.9.108+0.9.109
adom-bridge-kicad-manifest.json+5−5@@ -1,15 +1,15 @@ { "manifest_version": 1, "name": "kicad",- "version": "0.9.108",- "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.108/adom-bridge-kicad-v0.9.108.zip",- "sha256": "648be128ecc2fd57b36dec6385b01a76efe96207ae18b59d1aee6c7c0d735545",- "size": 403548,+ "version": "0.9.109",+ "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.109/adom-bridge-kicad-v0.9.109.zip",+ "sha256": "1425028bec0a028a734e452dcd549cebac86b48ab024a62524e15704f75c32dc",+ "size": 406095, "verbPrefixes": [ "kicad_" ], "healthEndpoint": "/status",- "released_at": "2026-08-15T11:26:35Z",+ "released_at": "2026-08-15T12:30:08Z", "hero": "https://wiki.adom.inc/blob/app/kicad-bridge/kicad-hero.png", "languages": [ "Python",
adom-bridge-kicad-v0.9.109.zipadded⋯ 1 unchanged line ⋯
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "kicad", "displayName": "KiCad EDA",- "version": "0.9.108",+ "version": "0.9.109", "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/export_part.pyadded+125@@ -0,0 +1,125 @@+"""export_part.py — kicad_export_part: write ONE-OFF library files for a single+installed part: a .kicad_sym library containing ONLY that symbol, a .pretty+containing ONLY that footprint, and the bare 3D model file.++Why (John, 2026-08-15): "an lbr file that JUST has the specific symbol only, or+footprint only, or 3d chip only. this is not really useful in the day to day+world, but it proves you can do one-off of these" — the reverse direction of+install: the bridge can extract each asset of a part from the live Adom+library into a standalone, loadable, single-entry library file. (KiCad's+native equivalents of an EAGLE/Fusion .lbr: .kicad_sym for symbols, .pretty/+.kicad_mod for footprints, .step/.wrl for the 3D body.)+"""+import os+import re+import shutil+import sys+from pathlib import Path++sys.path.insert(0, str(Path(__file__).parent.parent))+++def handle_export_part(kicad_info: dict, args: dict) -> dict:+ """One-off single-part library exports.++ Args:+ partName (required) — the part in the Adom library.+ what (optional) — list from {"symbol","footprint","model"}; default all.+ footprintName — footprint name if it differs from partName.+ outputDir — default Documents/adom-part-exports/<partName>/.+ """+ part = (args.get("partName") or args.get("symbolName") or "").strip()+ if not part:+ return {"success": False, "error": "partName required"}+ fp_name = (args.get("footprintName") or part).strip()+ what = args.get("what") or ["symbol", "footprint", "model"]+ if isinstance(what, str):+ what = [what]+ what = [w.lower() for w in what]++ user_dir = kicad_info.get("user_dir")+ if not user_dir:+ return {"success": False, "error": "KiCad user directory not found"}++ out_dir = Path(args.get("outputDir") or (Path(os.environ.get("USERPROFILE", str(Path.home())))+ / "Documents" / "adom-part-exports" / part))+ out_dir.mkdir(parents=True, exist_ok=True)++ exports: dict = {}+ errors: list = []++ if "symbol" in what:+ sym_lib = Path(user_dir) / "symbols" / "Adom.kicad_sym"+ try:+ import adom_library+ blocks = adom_library._extract_top_level_symbols(sym_lib.read_text(encoding="utf-8"))+ hit = [b for n, b in blocks if n == part]+ if not hit:+ errors.append(f"symbol '{part}' not in {sym_lib}")+ else:+ one = ('(kicad_symbol_lib\n (version 20231120)\n (generator "adom-export-part")\n'+ + hit[0].rstrip() + "\n)\n")+ p = out_dir / f"{part}-symbol-only.kicad_sym"+ p.write_text(one, encoding="utf-8")+ names = [n for n, _ in adom_library._extract_top_level_symbols(one)]+ exports["symbol"] = {"path": str(p), "symbolsInLibrary": names,+ "single": names == [part]}+ except Exception as e:+ errors.append(f"symbol export: {e}")++ if "footprint" in what:+ src = Path(user_dir) / "footprints" / "Adom.pretty" / f"{fp_name}.kicad_mod"+ if not src.exists():+ errors.append(f"footprint '{fp_name}' not in Adom.pretty")+ else:+ try:+ pretty = out_dir / f"{part}-footprint-only.pretty"+ pretty.mkdir(parents=True, exist_ok=True)+ dst = pretty / f"{fp_name}.kicad_mod"+ shutil.copyfile(src, dst)+ mods = [f.name for f in pretty.glob("*.kicad_mod")]+ exports["footprint"] = {"path": str(dst), "library": str(pretty),+ "footprintsInLibrary": mods,+ "single": mods == [f"{fp_name}.kicad_mod"]}+ except Exception as e:+ errors.append(f"footprint export: {e}")++ if "model" in what:+ models_dir = Path(user_dir) / "3dmodels" / "Adom"+ src = None+ for ext in (".step", ".stp", ".wrl"):+ c = models_dir / f"{fp_name}{ext}"+ if c.exists():+ src = c+ break+ if src is None and "footprint" not in errors:+ # fall back to the model referenced by the footprint+ fp = Path(user_dir) / "footprints" / "Adom.pretty" / f"{fp_name}.kicad_mod"+ if fp.exists():+ m = re.search(r'\(model\s+"([^"]+)"', fp.read_text(encoding="utf-8"))+ if m and Path(m.group(1)).exists():+ src = Path(m.group(1))+ if src is None:+ errors.append(f"no 3D model for '{fp_name}' in 3dmodels/Adom")+ else:+ try:+ dst = out_dir / f"{part}-model-only{src.suffix}"+ shutil.copyfile(src, dst)+ exports["model"] = {"path": str(dst), "bytes": dst.stat().st_size}+ except Exception as e:+ errors.append(f"model export: {e}")++ ok = len(exports) == len(what) and not errors+ return {+ "success": ok,+ "partName": part,+ "outputDir": str(out_dir),+ "exports": exports,+ "errors": errors,+ "output": f"one-off exports for {part}: " + ", ".join(sorted(exports)) ++ (f" | {len(errors)} error(s)" if errors else ""),+ "_hint": ("Each file is a standalone single-entry library: the .kicad_sym holds ONLY this "+ "symbol (open with kicad_show_symbol after installing, or any KiCad), the .pretty "+ "holds ONLY this footprint, the model file is the bare 3D body. The reverse of "+ "install — proves per-asset round-trip. Pull them with ab pull_file."),+ }
handlers/make_part_project.py+13−11@@ -191,22 +191,24 @@ def handle_make_part_project(kicad_info: dict, args: dict) -> dict: ''' # ---------------- board ----------------+ # Part-DOMINANT outline: tight margins so the part is clearly visible in the+ # 3D board view (John, 2026-08-15: on a roomy board a 0402 is an invisible+ # speck and the 3D board "isn't showing the chip"). part_at = (108.0, 68.0)- r_at = (116.0, 68.0)+ coords = [(float(a), float(b)) for a, b in+ re.findall(r'\((?:start|end|at|center)\s+([-\d.]+)\s+([-\d.]+)', fp_text)]+ half_w = max([abs(x) for x, _ in coords] + [1.0])+ half_h = max([abs(y) for _, y in coords] + [1.0])+ margin = max(2.0, half_h * 0.8)+ r_at = (round(part_at[0] + half_w + 4.6, 2), part_at[1]) # 0805 R just beside the part+ x1 = round(part_at[0] - half_w - margin, 2)+ y1 = round(part_at[1] - max(half_h, 1.0) - margin, 2)+ x2 = round(r_at[0] + 1.7 + margin, 2)+ y2 = round(part_at[1] + max(half_h, 1.0) + margin, 2) fp_placed = fp_text.replace(f'(footprint "{fp_name}"', f'(footprint "Adom:{fp_name}"', 1) fp_placed = fp_placed.replace('(layer "F.Cu")', f'(layer "F.Cu")\n (at {part_at[0]} {part_at[1]})', 1) fp_placed = fp_placed.replace('(fp_text reference "REF**"', '(fp_text reference "D1"', 1) fp_placed = fp_placed.replace('(property "Reference" "REF**"', '(property "Reference" "D1"', 1)-- # board outline sized from the part's coordinates (pads/graphics), with margin- coords = [(float(a), float(b)) for a, b in- re.findall(r'\((?:start|end|at|center)\s+([-\d.]+)\s+([-\d.]+)', fp_text)]- half_w = max([abs(x) for x, _ in coords] + [2.0])- half_h = max([abs(y) for _, y in coords] + [2.0])- x1 = round(part_at[0] - half_w - 6, 2)- y1 = round(part_at[1] - half_h - 6, 2)- x2 = round(r_at[0] + 8, 2)- y2 = round(part_at[1] + half_h + 6, 2) edge = "" pts = [(x1, y1), (x2, y1), (x2, y2), (x1, y2), (x1, y1)] for i in range(4):
handlers/uninstall.py+3−1@@ -194,11 +194,13 @@ def handle_uninstall(kicad_info: dict, args: dict) -> dict: for f in tmp.glob("adom-kicad-bridge-*.json"): rm_file(f, "plugin discovery file") - # 6) screenshot cache, 7) demo project+ # 6) screenshot cache, 7) demo project, 8) part exports/staging (0.9.109) rm_tree(Path("C:/tmp/adom-desktop-screenshots"), "screenshot cache") up = os.environ.get("USERPROFILE") if up: rm_tree(Path(up) / "Documents" / "adom-kicad-demo", "demo project")+ rm_tree(Path(up) / "Documents" / "adom-part-exports", "one-off part exports")+ rm_tree(Path(up) / "Documents" / "adom-part-staging", "part install staging") ok = len(failed) == 0 return {
server.py+6@@ -67,6 +67,7 @@ from handlers.export_molecule import handle_export_molecule # v0.9.63+: first-class showcase tour (Bridge demos this bridge during Hydrogen install). from handlers.demo import handle_demo from handlers.make_part_project import handle_make_part_project+from handlers.export_part import handle_export_part # v0.9.10+: check/upgrade KiCad via the OFFICIAL downloads.kicad.org installer # (NOT winget, which lags the real release). check_for_updates is read-only; # upgrade silent-installs the official signed NSIS build.@@ -366,6 +367,10 @@ _VERB_CATALOG = { "export_step": {"summary": "Export a STEP 3D model from a board.", "long": False, "hint": "Absolute board path + output; exports a STEP assembly via kicad-cli.", "related": ["kicad_open_3d_viewer", "kicad_export_gerber"], "pitfalls": ["only components with 3D models attached appear in the STEP"]},+ "export_part": {"summary": "One-off single-part library exports: a .kicad_sym with ONLY that symbol, a .pretty with ONLY that footprint, the bare 3D model file — the reverse of install.", "long": False,+ "hint": "kicad_export_part {\"partName\":\"IN-S42ATA\"} (optional what:[\"symbol\",\"footprint\",\"model\"]). Writes to Documents/adom-part-exports/<part>/; pull with ab pull_file. Proves per-asset round-trip.",+ "related": ["kicad_install_symbol", "kicad_install_footprint", "kicad_make_part_project"],+ "pitfalls": ["the part must be installed in the Adom library first"]}, "make_part_project": {"summary": "Generate a small real project (pro/sch/pcb) AROUND an installed Adom-library part — gives the project/schematic/2D-board/3D-board surfaces something true to show for one component.", "long": False, "hint": "kicad_make_part_project {\"partName\":\"IN-S42ATR\"} after install_symbol/install_footprint. Returns projectFile/schematicFile/boardFile for the show_* verbs. Board keeps the footprint's (model ...) so show_3d_board renders the real part.", "related": ["kicad_show_project", "kicad_show_schematic", "kicad_show_2d_board", "kicad_show_3d_board"],@@ -543,6 +548,7 @@ COMMAND_HANDLERS = { "export_molecule": handle_export_molecule, "demo": handle_demo, "make_part_project": handle_make_part_project,+ "export_part": handle_export_part, "export_bom_csv": handle_export_bom_csv, # v0.9.10+: KiCad version check + upgrade via the official downloads.kicad.org # installer (read-only check; silent /S install). Exposed as kicad_check_for_updates