Files
kicad-manager/init_user.py
brentperteet 3f0aff923d initial commit
2026-02-22 08:16:48 -06:00

300 lines
9.4 KiB
Python

#!/usr/bin/env python3
"""
init_user.py
============
Initialize a new user's KiCad environment with UM customizations.
This script:
1. Creates ODBC System DSN for SQLite database (Windows only)
2. Copies theme files from UM_KICAD/lib/themes to the user's KiCad config directory
3. Updates KiCad preferences to use the UM theme
4. Sets other UM-specific preferences
Usage:
python3 init_user.py
"""
import os
import sys
import json
import shutil
from pathlib import Path
# ---------------------------------------------------------------------------
# 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 get_themes_dir() -> Path:
"""Get the user's KiCad themes directory"""
return kicad9_config_dir() / "colors"
def get_kicad_common_json() -> Path:
"""Get the path to kicad_common.json (main preferences file)"""
return kicad9_config_dir() / "kicad_common.json"
# ---------------------------------------------------------------------------
# ODBC DSN Creation (Windows only)
# ---------------------------------------------------------------------------
def create_odbc_dsn(um_root: Path) -> bool:
"""
Create a System DSN for SQLite ODBC connection on Windows.
Args:
um_root: Path to UM_KICAD root directory
Returns:
True if DSN was created or already exists, False on error
"""
if not sys.platform.startswith("win"):
print(" ODBC DSN creation is only supported on Windows")
return True # Not an error, just not applicable
try:
import winreg
except ImportError:
print(" Warning: winreg module not available, skipping ODBC DSN creation")
return False
# Database path
db_path = um_root / "database" / "parts.sqlite"
if not db_path.exists():
print(f" Warning: Database not found at {db_path}")
return False
dsn_name = "UM_2KiCad_Parts2"
driver_name = "SQLite3 ODBC Driver"
try:
# Check if DSN already exists
try:
key = winreg.OpenKey(
winreg.HKEY_LOCAL_MACHINE,
r"SOFTWARE\ODBC\ODBC.INI\UM_KiCad_Parts",
0,
winreg.KEY_READ
)
winreg.CloseKey(key)
print(f" ODBC DSN '{dsn_name}' already exists")
return True
except FileNotFoundError:
pass # DSN doesn't exist, we'll create it
# Create the DSN
# First, add to ODBC Data Sources list
key = winreg.OpenKey(
winreg.HKEY_LOCAL_MACHINE,
r"SOFTWARE\ODBC\ODBC.INI\ODBC Data Sources",
0,
winreg.KEY_WRITE
)
winreg.SetValueEx(key, dsn_name, 0, winreg.REG_SZ, driver_name)
winreg.CloseKey(key)
# Create the DSN configuration key
key = winreg.CreateKey(
winreg.HKEY_LOCAL_MACHINE,
rf"SOFTWARE\ODBC\ODBC.INI\{dsn_name}"
)
# Set DSN configuration values
winreg.SetValueEx(key, "Driver", 0, winreg.REG_SZ, driver_name)
winreg.SetValueEx(key, "Database", 0, winreg.REG_SZ, str(db_path))
winreg.SetValueEx(key, "Description", 0, winreg.REG_SZ, "UM KiCad Parts Database")
winreg.CloseKey(key)
print(f" Created ODBC System DSN '{dsn_name}'")
print(f" Database: {db_path}")
return True
except PermissionError:
print(" ERROR: Administrator privileges required to create System DSN")
print(" Please run this script as Administrator, or create the DSN manually:")
print(f" DSN Name: {dsn_name}")
print(f" Driver: {driver_name}")
print(f" Database: {db_path}")
return False
except Exception as e:
print(f" Warning: Failed to create ODBC DSN: {e}")
print(f" You may need to create it manually:")
print(f" DSN Name: {dsn_name}")
print(f" Driver: {driver_name}")
print(f" Database: {db_path}")
return False
# ---------------------------------------------------------------------------
# Theme installation
# ---------------------------------------------------------------------------
def install_themes(um_root: Path) -> list[str]:
"""
Copy all theme files from UM_KICAD/lib/themes to user's KiCad config.
Returns:
List of installed theme names
"""
source_themes_dir = um_root / "lib" / "themes"
if not source_themes_dir.exists():
print(f"Warning: Themes directory not found at {source_themes_dir}")
return []
dest_themes_dir = get_themes_dir()
dest_themes_dir.mkdir(parents=True, exist_ok=True)
installed = []
theme_files = list(source_themes_dir.glob("*.json"))
if not theme_files:
print("Warning: No theme files found")
return []
for theme_file in theme_files:
dest_file = dest_themes_dir / theme_file.name
shutil.copy2(theme_file, dest_file)
theme_name = theme_file.stem
installed.append(theme_name)
print(f" Installed theme: {theme_name}")
return installed
# ---------------------------------------------------------------------------
# Preferences configuration
# ---------------------------------------------------------------------------
def set_theme_preference(theme_name: str):
"""
Update kicad_common.json and user.json to use the specified theme.
Args:
theme_name: Name of the theme (without .json extension)
"""
# 1. Copy the theme to user.json (this is the active theme file)
themes_dir = get_themes_dir()
source_theme = themes_dir / f"{theme_name}.json"
user_theme = themes_dir / "user.json"
if source_theme.exists():
shutil.copy2(source_theme, user_theme)
print(f" Activated theme '{theme_name}' by copying to user.json")
else:
print(f" Warning: Theme file not found: {source_theme}")
# 2. Update kicad_common.json to reference the theme
config_file = get_kicad_common_json()
# Load existing config or create new one
if config_file.exists():
with open(config_file, 'r', encoding='utf-8') as f:
config = json.load(f)
print(f" Loaded existing preferences from {config_file}")
else:
config = {}
print(f" Creating new preferences file at {config_file}")
# Ensure the appearance section exists
if "appearance" not in config:
config["appearance"] = {}
# Set the theme for all applications
config["appearance"]["color_theme"] = theme_name
# Also set it for specific editors
for editor in ["pcb_editor", "schematic_editor", "gerbview", "3d_viewer"]:
if editor not in config:
config[editor] = {}
if "appearance" not in config[editor]:
config[editor]["appearance"] = {}
config[editor]["appearance"]["color_theme"] = theme_name
# Backup existing config
if config_file.exists():
backup_file = config_file.with_suffix('.json.bak')
shutil.copy2(config_file, backup_file)
print(f" Backed up existing config to {backup_file.name}")
# Write updated config
config_file.parent.mkdir(parents=True, exist_ok=True)
with open(config_file, 'w', encoding='utf-8') as f:
json.dump(config, f, indent=2)
print(f" Set theme to '{theme_name}' in KiCad preferences")
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main() -> int:
um_root_env = os.environ.get("UM_KICAD")
if not um_root_env:
print("ERROR: UM_KICAD environment variable not set")
print("Please set UM_KICAD to the root of your UM KiCad repository")
return 1
um_root = Path(um_root_env).resolve()
if not um_root.exists():
print(f"ERROR: UM_KICAD path does not exist: {um_root}")
return 1
print("=" * 60)
print("UM KiCad User Initialization")
print("=" * 60)
print(f"\nUM_KICAD: {um_root}")
print(f"KiCad config: {kicad9_config_dir()}")
print()
# Create ODBC DSN (Windows only)
if sys.platform.startswith("win"):
print("Creating ODBC System DSN...")
create_odbc_dsn(um_root)
print()
# Install themes
print("Installing themes...")
installed_themes = install_themes(um_root)
if not installed_themes:
print("\nNo themes were installed")
return 1
print(f"\nInstalled {len(installed_themes)} theme(s)")
# Set the first theme as default (typically "UM")
default_theme = installed_themes[0]
print(f"\nSetting default theme...")
set_theme_preference(default_theme)
print("\n" + "=" * 60)
print("Initialization complete!")
print("=" * 60)
print("\nNext steps:")
print("1. Restart KiCad to see the new theme")
print("2. You can change themes in Preferences > Colors")
print(f"3. Available themes: {', '.join(installed_themes)}")
return 0
if __name__ == "__main__":
raise SystemExit(main())