initial commit
This commit is contained in:
335
add_libraries.py
Normal file
335
add_libraries.py
Normal file
@@ -0,0 +1,335 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Adds all *.kicad_sym libraries under UM_KICAD/lib/symbols
|
||||
to the KiCad v9 global sym-lib-table.
|
||||
|
||||
Also adds all *.pretty footprint libraries under UM_KICAD/lib/footprints
|
||||
to the KiCad v9 global fp-lib-table.
|
||||
|
||||
Safe behavior:
|
||||
- Does NOT remove existing entries
|
||||
- Skips libraries already present (by URI)
|
||||
- Creates sym-lib-table.bak and fp-lib-table.bak backups
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
|
||||
|
||||
# ----------------------------
|
||||
# KiCad v9 config location
|
||||
# ----------------------------
|
||||
def kicad9_config_dir() -> Path:
|
||||
home = Path.home()
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
appdata = os.environ.get("APPDATA")
|
||||
if not appdata:
|
||||
raise RuntimeError("APPDATA not set")
|
||||
return Path(appdata) / "kicad" / "9.0"
|
||||
|
||||
if sys.platform == "darwin":
|
||||
return home / "Library" / "Preferences" / "kicad" / "9.0"
|
||||
|
||||
xdg = os.environ.get("XDG_CONFIG_HOME")
|
||||
if xdg:
|
||||
return Path(xdg) / "kicad" / "9.0"
|
||||
|
||||
return home / ".config" / "kicad" / "9.0"
|
||||
|
||||
|
||||
def global_sym_lib_table_path() -> Path:
|
||||
return kicad9_config_dir() / "sym-lib-table"
|
||||
|
||||
|
||||
def global_fp_lib_table_path() -> Path:
|
||||
return kicad9_config_dir() / "fp-lib-table"
|
||||
|
||||
|
||||
# ----------------------------
|
||||
# Basic sym-lib-table parsing
|
||||
# ----------------------------
|
||||
LIB_BLOCK_RE = re.compile(r"\(lib\s+(?:.|\n)*?\)\s*\)", re.MULTILINE)
|
||||
NAME_RE = re.compile(r"\(name\s+([^)]+)\)")
|
||||
URI_RE = re.compile(r"\(uri\s+([^)]+)\)")
|
||||
|
||||
|
||||
def strip_atom(atom: str) -> str:
|
||||
atom = atom.strip()
|
||||
if atom.startswith('"') and atom.endswith('"'):
|
||||
return atom[1:-1]
|
||||
return atom
|
||||
|
||||
|
||||
def parse_existing_libs(text: str) -> Dict[str, str]:
|
||||
libs = {}
|
||||
for m in LIB_BLOCK_RE.finditer(text):
|
||||
block = m.group(0)
|
||||
name_m = NAME_RE.search(block)
|
||||
uri_m = URI_RE.search(block)
|
||||
if not name_m or not uri_m:
|
||||
continue
|
||||
name = strip_atom(name_m.group(1))
|
||||
uri = strip_atom(uri_m.group(1))
|
||||
libs[name] = uri
|
||||
return libs
|
||||
|
||||
|
||||
# ----------------------------
|
||||
# Helpers
|
||||
# ----------------------------
|
||||
def make_unique_name(name: str, used: set[str]) -> str:
|
||||
if name not in used:
|
||||
return name
|
||||
i = 2
|
||||
while f"{name}_{i}" in used:
|
||||
i += 1
|
||||
return f"{name}_{i}"
|
||||
|
||||
|
||||
def create_lib_block(name: str, uri: str, descr: str) -> str:
|
||||
return (
|
||||
"(lib\n"
|
||||
f" (name \"{name}\")\n"
|
||||
" (type \"KiCad\")\n"
|
||||
f" (uri \"{uri}\")\n"
|
||||
" (options \"\")\n"
|
||||
f" (descr \"{descr}\")\n"
|
||||
")"
|
||||
)
|
||||
|
||||
|
||||
def create_fp_lib_block(name: str, uri: str, descr: str) -> str:
|
||||
return (
|
||||
"(lib\n"
|
||||
f" (name \"{name}\")\n"
|
||||
" (type \"KiCad\")\n"
|
||||
f" (uri \"{uri}\")\n"
|
||||
" (options \"\")\n"
|
||||
f" (descr \"{descr}\")\n"
|
||||
")"
|
||||
)
|
||||
|
||||
|
||||
# ----------------------------
|
||||
# Main logic
|
||||
# ----------------------------
|
||||
def sync_symbol_libraries(um_root: Path) -> int:
|
||||
"""Synchronize symbol libraries"""
|
||||
symbols_dir = um_root / "lib" / "symbols"
|
||||
|
||||
if not symbols_dir.exists():
|
||||
print(f"ERROR: {symbols_dir} does not exist")
|
||||
return 1
|
||||
|
||||
sym_table_path = global_sym_lib_table_path()
|
||||
sym_table_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
existing_text = ""
|
||||
if sym_table_path.exists():
|
||||
existing_text = sym_table_path.read_text(encoding="utf-8")
|
||||
|
||||
existing_libs = parse_existing_libs(existing_text)
|
||||
|
||||
# Backup
|
||||
if sym_table_path.exists():
|
||||
backup_path = sym_table_path.with_suffix(".bak")
|
||||
backup_path.write_text(existing_text, encoding="utf-8")
|
||||
print(f"Backup written: {backup_path}")
|
||||
|
||||
# Filter out all existing UM_KICAD entries
|
||||
existing_blocks = []
|
||||
removed = 0
|
||||
for m in LIB_BLOCK_RE.finditer(existing_text):
|
||||
block = m.group(0).strip()
|
||||
uri_m = URI_RE.search(block)
|
||||
if uri_m:
|
||||
uri = strip_atom(uri_m.group(1))
|
||||
if "${UM_KICAD}" in uri:
|
||||
removed += 1
|
||||
continue
|
||||
existing_blocks.append(block)
|
||||
|
||||
# Scan UM_KICAD/lib/symbols
|
||||
sym_files = sorted(symbols_dir.glob("*.kicad_sym"))
|
||||
|
||||
if not sym_files:
|
||||
print("No .kicad_sym files found.")
|
||||
# Still rebuild table without UM_KICAD entries
|
||||
output = ["(sym_lib_table"]
|
||||
for block in existing_blocks:
|
||||
output.append(" " + block.replace("\n", "\n "))
|
||||
output.append(")\n")
|
||||
sym_table_path.write_text("\n".join(output), encoding="utf-8")
|
||||
print(f"Removed {removed} UM_KICAD entries")
|
||||
return 0
|
||||
|
||||
# Build new UM_KICAD blocks
|
||||
new_blocks: List[str] = []
|
||||
used_names = set()
|
||||
|
||||
# Collect names from non-UM_KICAD entries to avoid conflicts
|
||||
for name, uri in existing_libs.items():
|
||||
if "${UM_KICAD}" not in uri:
|
||||
used_names.add(name)
|
||||
|
||||
for sym_file in sym_files:
|
||||
rel_path = sym_file.relative_to(um_root).as_posix()
|
||||
uri = "${UM_KICAD}/" + rel_path
|
||||
|
||||
base_name = sym_file.stem
|
||||
name = make_unique_name(base_name, used_names)
|
||||
|
||||
block = create_lib_block(
|
||||
name=name,
|
||||
uri=uri,
|
||||
descr=f"Auto-added from {rel_path}"
|
||||
)
|
||||
|
||||
new_blocks.append(block)
|
||||
used_names.add(name)
|
||||
|
||||
# Rebuild table
|
||||
all_blocks = existing_blocks + new_blocks
|
||||
|
||||
output = ["(sym_lib_table"]
|
||||
for block in all_blocks:
|
||||
output.append(" " + block.replace("\n", "\n "))
|
||||
output.append(")\n")
|
||||
|
||||
sym_table_path.write_text("\n".join(output), encoding="utf-8")
|
||||
|
||||
print(f"Updated {sym_table_path}")
|
||||
print(f"Found: {len(sym_files)} libraries")
|
||||
print(f"Removed: {removed} old UM_KICAD entries")
|
||||
print(f"Added: {len(new_blocks)} new UM_KICAD entries")
|
||||
return 0
|
||||
|
||||
|
||||
def sync_footprint_libraries(um_root: Path) -> int:
|
||||
"""Synchronize footprint libraries"""
|
||||
footprints_dir = um_root / "lib" / "footprints"
|
||||
|
||||
if not footprints_dir.exists():
|
||||
print(f"Warning: {footprints_dir} does not exist, skipping footprint sync")
|
||||
return 0
|
||||
|
||||
fp_table_path = global_fp_lib_table_path()
|
||||
fp_table_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
existing_text = ""
|
||||
if fp_table_path.exists():
|
||||
existing_text = fp_table_path.read_text(encoding="utf-8")
|
||||
|
||||
existing_libs = parse_existing_libs(existing_text)
|
||||
|
||||
# Backup
|
||||
if fp_table_path.exists():
|
||||
backup_path = fp_table_path.with_suffix(".bak")
|
||||
backup_path.write_text(existing_text, encoding="utf-8")
|
||||
print(f"Backup written: {backup_path}")
|
||||
|
||||
# Filter out all existing UM_KICAD entries
|
||||
existing_blocks = []
|
||||
removed = 0
|
||||
for m in LIB_BLOCK_RE.finditer(existing_text):
|
||||
block = m.group(0).strip()
|
||||
uri_m = URI_RE.search(block)
|
||||
if uri_m:
|
||||
uri = strip_atom(uri_m.group(1))
|
||||
if "${UM_KICAD}" in uri:
|
||||
removed += 1
|
||||
continue
|
||||
existing_blocks.append(block)
|
||||
|
||||
# Scan UM_KICAD/lib/footprints for *.pretty directories
|
||||
pretty_dirs = sorted([d for d in footprints_dir.iterdir() if d.is_dir() and d.name.endswith('.pretty')])
|
||||
|
||||
if not pretty_dirs:
|
||||
print("No .pretty footprint libraries found.")
|
||||
# Still rebuild table without UM_KICAD entries
|
||||
output = ["(fp_lib_table"]
|
||||
for block in existing_blocks:
|
||||
output.append(" " + block.replace("\n", "\n "))
|
||||
output.append(")\n")
|
||||
fp_table_path.write_text("\n".join(output), encoding="utf-8")
|
||||
print(f"Removed {removed} UM_KICAD footprint entries")
|
||||
return 0
|
||||
|
||||
# Build new UM_KICAD blocks
|
||||
new_blocks: List[str] = []
|
||||
used_names = set()
|
||||
|
||||
# Collect names from non-UM_KICAD entries to avoid conflicts
|
||||
for name, uri in existing_libs.items():
|
||||
if "${UM_KICAD}" not in uri:
|
||||
used_names.add(name)
|
||||
|
||||
for pretty_dir in pretty_dirs:
|
||||
rel_path = pretty_dir.relative_to(um_root).as_posix()
|
||||
uri = "${UM_KICAD}/" + rel_path
|
||||
|
||||
base_name = pretty_dir.stem # Removes .pretty extension
|
||||
name = make_unique_name(base_name, used_names)
|
||||
|
||||
block = create_fp_lib_block(
|
||||
name=name,
|
||||
uri=uri,
|
||||
descr=f"Auto-added from {rel_path}"
|
||||
)
|
||||
|
||||
new_blocks.append(block)
|
||||
used_names.add(name)
|
||||
|
||||
# Rebuild table
|
||||
all_blocks = existing_blocks + new_blocks
|
||||
|
||||
output = ["(fp_lib_table"]
|
||||
for block in all_blocks:
|
||||
output.append(" " + block.replace("\n", "\n "))
|
||||
output.append(")\n")
|
||||
|
||||
fp_table_path.write_text("\n".join(output), encoding="utf-8")
|
||||
|
||||
print(f"Updated {fp_table_path}")
|
||||
print(f"Found: {len(pretty_dirs)} footprint libraries")
|
||||
print(f"Removed: {removed} old UM_KICAD entries")
|
||||
print(f"Added: {len(new_blocks)} new UM_KICAD entries")
|
||||
return 0
|
||||
|
||||
|
||||
def main() -> int:
|
||||
um_root_env = os.environ.get("UM_KICAD")
|
||||
if not um_root_env:
|
||||
print("ERROR: UM_KICAD not set")
|
||||
return 1
|
||||
|
||||
um_root = Path(um_root_env).resolve()
|
||||
|
||||
print("=" * 60)
|
||||
print("Syncing Symbol Libraries...")
|
||||
print("=" * 60)
|
||||
result = sync_symbol_libraries(um_root)
|
||||
if result != 0:
|
||||
return result
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("Syncing Footprint Libraries...")
|
||||
print("=" * 60)
|
||||
result = sync_footprint_libraries(um_root)
|
||||
if result != 0:
|
||||
return result
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("Restart KiCad to refresh libraries.")
|
||||
print("=" * 60)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user