initial commit
This commit is contained in:
211
variant_manager.py
Normal file
211
variant_manager.py
Normal 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)")
|
||||
Reference in New Issue
Block a user