app
KiCad - the KiCad Bridge
Public Made by Adomby adom
Drive your own KiCad from an AI, in the background: open every editor, read the whole board as data (the EDA Bridge Standard board model), stack-up, part heights, netlists, place, route, pour, DRC and exports.
master
John Lauer
v0.9.109: kicad_export_part (one-off single-part library exports: symbol-only .kicad_sym, footprint-only .pretty, bare…
9d49110
2mo ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
"""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."),
}