300 lines
9.4 KiB
Python
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())
|