204 lines
6.7 KiB
Python
Executable file
204 lines
6.7 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""Configure KiCad user paths and library table entries for this library."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import re
|
|
import shutil
|
|
import sys
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
|
|
ENV_VARS = ("KICAD_LIB_DIR_USER", "KICAD_PDF_DIR_USER", "KICAD_3D_PARTY_USER")
|
|
|
|
|
|
def repo_root() -> Path:
|
|
return Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def detect_kicad_config(version: str | None) -> Path:
|
|
base = Path.home() / ".config" / "kicad"
|
|
|
|
if version:
|
|
path = base / version / "kicad_common.json"
|
|
if not path.exists():
|
|
raise SystemExit(f"KiCad config not found: {path}")
|
|
return path
|
|
|
|
candidates = sorted(base.glob("*/kicad_common.json"))
|
|
if not candidates:
|
|
raise SystemExit(f"KiCad config not found under {base}")
|
|
|
|
def version_key(path: Path) -> tuple[int, ...]:
|
|
parts = []
|
|
for item in path.parent.name.split("."):
|
|
try:
|
|
parts.append(int(item))
|
|
except ValueError:
|
|
parts.append(0)
|
|
return tuple(parts)
|
|
|
|
return max(candidates, key=version_key)
|
|
|
|
|
|
def read_existing_vars(config_path: Path) -> dict[str, str]:
|
|
data = json.loads(config_path.read_text(encoding="utf-8"))
|
|
vars_obj = data.get("environment", {}).get("vars")
|
|
return vars_obj if isinstance(vars_obj, dict) else {}
|
|
|
|
|
|
def ask_path(name: str, prompt: str, default: str | None, required: bool = True) -> str:
|
|
env_value = os.environ.get(name)
|
|
if env_value:
|
|
return str(Path(env_value).expanduser())
|
|
|
|
suffix = f" [{default}]" if default else ""
|
|
while True:
|
|
value = input(f"{prompt}{suffix}: ").strip()
|
|
if not value and default:
|
|
value = default
|
|
if value or not required:
|
|
return str(Path(value).expanduser()) if value else ""
|
|
print("Path is required.")
|
|
|
|
|
|
def backup(path: Path) -> Path:
|
|
stamp = datetime.now().strftime("%Y%m%d-%H%M%S")
|
|
target = path.with_name(f"{path.name}.bak-{stamp}")
|
|
shutil.copy2(path, target)
|
|
return target
|
|
|
|
|
|
def update_common_config(config_path: Path, values: dict[str, str]) -> None:
|
|
data = json.loads(config_path.read_text(encoding="utf-8"))
|
|
environment = data.setdefault("environment", {})
|
|
vars_obj = environment.get("vars")
|
|
|
|
if not isinstance(vars_obj, dict):
|
|
vars_obj = {}
|
|
environment["vars"] = vars_obj
|
|
|
|
vars_obj.update(values)
|
|
config_path.write_text(
|
|
json.dumps(data, ensure_ascii=False, indent=2) + "\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
|
|
def ensure_table(path: Path, header: str) -> None:
|
|
if not path.exists():
|
|
path.write_text(f"({header}\n\t(version 7)\n)\n", encoding="utf-8")
|
|
|
|
|
|
def upsert_library_entry(table_path: Path, header: str, entry: str) -> None:
|
|
ensure_table(table_path, header)
|
|
text = table_path.read_text(encoding="utf-8")
|
|
name_match = re.search(r'\(name "([^"]+)"\)', entry)
|
|
if not name_match:
|
|
raise ValueError(f"Library entry has no name: {entry}")
|
|
|
|
name = re.escape(name_match.group(1))
|
|
text = re.sub(
|
|
rf'\n[^\n]*\(lib\s+\(name "{name}"\)[^\n]*',
|
|
"",
|
|
text,
|
|
)
|
|
|
|
insert_at = text.rfind("\n)")
|
|
if insert_at == -1:
|
|
raise SystemExit(f"Unexpected KiCad table format: {table_path}")
|
|
|
|
text = text[:insert_at] + "\n\t" + entry + text[insert_at:]
|
|
table_path.write_text(text, encoding="utf-8")
|
|
|
|
|
|
def configure_library_tables(config_dir: Path) -> None:
|
|
sym_table = config_dir / "sym-lib-table"
|
|
fp_table = config_dir / "fp-lib-table"
|
|
|
|
upsert_library_entry(
|
|
sym_table,
|
|
"sym_lib_table",
|
|
'(lib (name "eda-core") (type "Database") (uri "${KICAD_LIB_DIR_USER}/eda-core.kicad_dbl") (options "") (descr ""))',
|
|
)
|
|
upsert_library_entry(
|
|
sym_table,
|
|
"sym_lib_table",
|
|
'(lib (name "sym-lib-table") (type "Table") (uri "${KICAD_LIB_DIR_USER}/sym-lib-table") (options "") (descr ""))',
|
|
)
|
|
upsert_library_entry(
|
|
fp_table,
|
|
"fp_lib_table",
|
|
'(lib (name "fp-lib-table") (type "Table") (uri "${KICAD_LIB_DIR_USER}/fp-lib-table") (options "") (descr ""))',
|
|
)
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(
|
|
description="Configure KiCad paths and library tables for KiCad User Library."
|
|
)
|
|
parser.add_argument("--kicad-version", help="KiCad config version, for example 10.0")
|
|
parser.add_argument("--lib-dir", default=str(repo_root()), help="Path to this library repo")
|
|
parser.add_argument("--pdf-dir", help="Path to PDF datasheet directory")
|
|
parser.add_argument("--3d-dir", dest="models_dir", help="Path to user 3D models directory")
|
|
parser.add_argument("--non-interactive", action="store_true", help="Do not ask questions")
|
|
args = parser.parse_args()
|
|
|
|
config_path = detect_kicad_config(args.kicad_version)
|
|
existing = read_existing_vars(config_path)
|
|
|
|
lib_dir = str(Path(args.lib_dir).expanduser().resolve())
|
|
pdf_dir = args.pdf_dir or existing.get("KICAD_PDF_DIR_USER")
|
|
models_dir = args.models_dir or existing.get("KICAD_3D_PARTY_USER")
|
|
|
|
if not args.non_interactive:
|
|
lib_dir = ask_path("KICAD_LIB_DIR_USER", "KiCad library path", lib_dir)
|
|
pdf_dir = ask_path("KICAD_PDF_DIR_USER", "PDF directory path", pdf_dir)
|
|
if args.models_dir or existing.get("KICAD_3D_PARTY_USER"):
|
|
models_dir = ask_path("KICAD_3D_PARTY_USER", "3D models directory path", models_dir)
|
|
|
|
values = {
|
|
"KICAD_LIB_DIR_USER": lib_dir,
|
|
"KICAD_PDF_DIR_USER": pdf_dir or "",
|
|
}
|
|
|
|
if models_dir:
|
|
values["KICAD_3D_PARTY_USER"] = models_dir
|
|
|
|
if not values["KICAD_PDF_DIR_USER"]:
|
|
raise SystemExit("Missing required value: KICAD_PDF_DIR_USER")
|
|
|
|
for key, value in values.items():
|
|
if key != "KICAD_LIB_DIR_USER" and not Path(value).expanduser().exists():
|
|
print(f"warning: {key} path does not exist yet: {value}", file=sys.stderr)
|
|
|
|
backups = [backup(config_path)]
|
|
config_dir = config_path.parent
|
|
for table_name in ("sym-lib-table", "fp-lib-table"):
|
|
table_path = config_dir / table_name
|
|
if table_path.exists():
|
|
backups.append(backup(table_path))
|
|
|
|
update_common_config(config_path, values)
|
|
configure_library_tables(config_dir)
|
|
|
|
print(f"Configured KiCad {config_dir.name}:")
|
|
for key in ENV_VARS:
|
|
if key in values:
|
|
print(f" {key} = {values[key]}")
|
|
print("Library table entries updated:")
|
|
print(" sym-lib-table: eda-core, sym-lib-table")
|
|
print(" fp-lib-table: fp-lib-table")
|
|
print("Backups:")
|
|
for item in backups:
|
|
print(f" {item}")
|
|
print("Restart KiCad if it was running.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|