initial commit

This commit is contained in:
brentperteet
2026-02-22 08:16:48 -06:00
commit 3f0aff923d
15 changed files with 4364 additions and 0 deletions

211
variant_manager.py Normal file
View File

@@ -0,0 +1,211 @@
#!/usr/bin/env python3
"""
variant_manager.py
==================
Manage KiCad design variants - track which parts are fitted/unfitted in different variants.
Variants are stored in a JSON file alongside the project.
"""
import json
import sys
from pathlib import Path
from typing import Dict, List, Set
class VariantManager:
"""Manages design variants for a KiCad project"""
def __init__(self, project_path: str):
"""
Initialize variant manager for a project.
Args:
project_path: Path to the .kicad_pro or .kicad_sch file
"""
self.project_path = Path(project_path)
self.project_dir = self.project_path.parent
self.project_name = self.project_path.stem
# Variants file stored alongside project
self.variants_file = self.project_dir / f"{self.project_name}.variants.json"
self.variants = self._load_variants()
def _load_variants(self) -> Dict:
"""Load variants from JSON file"""
if self.variants_file.exists():
with open(self.variants_file, 'r', encoding='utf-8') as f:
return json.load(f)
else:
# Default structure
return {
"meta": {
"version": 2, # Version 2 uses UUIDs instead of references
"active_variant": "default"
},
"variants": {
"default": {
"name": "default",
"description": "Default variant - all parts fitted",
"dnp_parts": [] # List of UUIDs that are DNP (Do Not Place)
}
}
}
def _save_variants(self):
"""Save variants to JSON file"""
with open(self.variants_file, 'w', encoding='utf-8') as f:
json.dump(self.variants, f, indent=2)
def get_variants(self) -> Dict:
"""Get all variants"""
return self.variants["variants"]
def get_active_variant(self) -> str:
"""Get the name of the active variant"""
return self.variants["meta"]["active_variant"]
def create_variant(self, name: str, description: str = "", based_on: str = None) -> bool:
"""
Create a new variant.
Args:
name: Variant name
description: Variant description
based_on: Name of variant to copy from (None = start empty)
Returns:
True if created, False if already exists
"""
if name in self.variants["variants"]:
return False
if based_on and based_on in self.variants["variants"]:
# Copy DNP list from base variant
dnp_parts = self.variants["variants"][based_on]["dnp_parts"].copy()
else:
dnp_parts = []
self.variants["variants"][name] = {
"name": name,
"description": description,
"dnp_parts": dnp_parts
}
self._save_variants()
return True
def delete_variant(self, name: str) -> bool:
"""
Delete a variant.
Args:
name: Variant name
Returns:
True if deleted, False if doesn't exist or is active
"""
if name not in self.variants["variants"]:
return False
if name == self.variants["meta"]["active_variant"]:
return False # Can't delete active variant
if name == "default":
return False # Can't delete default variant
del self.variants["variants"][name]
self._save_variants()
return True
def set_active_variant(self, name: str) -> bool:
"""
Set the active variant.
Args:
name: Variant name
Returns:
True if set, False if variant doesn't exist
"""
if name not in self.variants["variants"]:
return False
self.variants["meta"]["active_variant"] = name
self._save_variants()
return True
def set_part_dnp(self, variant_name: str, uuid: str, is_dnp: bool) -> bool:
"""
Set whether a part is DNP (Do Not Place) in a variant.
Args:
variant_name: Variant name
uuid: Component UUID (e.g., "681abb84-6eb2-4c95-9a2f-a9fc19a34beb")
is_dnp: True to mark as DNP, False to mark as fitted
Returns:
True if successful, False if variant doesn't exist
"""
if variant_name not in self.variants["variants"]:
return False
dnp_list = self.variants["variants"][variant_name]["dnp_parts"]
if is_dnp:
if uuid not in dnp_list:
dnp_list.append(uuid)
dnp_list.sort() # Keep sorted
else:
if uuid in dnp_list:
dnp_list.remove(uuid)
self._save_variants()
return True
def get_dnp_parts(self, variant_name: str) -> List[str]:
"""
Get list of DNP parts for a variant.
Args:
variant_name: Variant name
Returns:
List of UUIDs, or empty list if variant doesn't exist
"""
if variant_name not in self.variants["variants"]:
return []
return self.variants["variants"][variant_name]["dnp_parts"].copy()
def is_part_dnp(self, variant_name: str, uuid: str) -> bool:
"""
Check if a part is DNP in a variant.
Args:
variant_name: Variant name
uuid: Component UUID
Returns:
True if DNP, False if fitted or variant doesn't exist
"""
if variant_name not in self.variants["variants"]:
return False
return uuid in self.variants["variants"][variant_name]["dnp_parts"]
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python variant_manager.py <project.kicad_pro>")
sys.exit(1)
manager = VariantManager(sys.argv[1])
# Print current variants
print(f"Project: {manager.project_name}")
print(f"Active variant: {manager.get_active_variant()}")
print("\nVariants:")
for name, variant in manager.get_variants().items():
dnp_count = len(variant["dnp_parts"])
print(f" {name}: {variant['description']} ({dnp_count} DNP parts)")