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

278
sync_variant.py Normal file
View File

@@ -0,0 +1,278 @@
#!/usr/bin/env python3
"""
sync_variant.py
===============
Sync variant from KiCad schematic - read DNP flags and update variant data.
This script reads the current state of DNP flags from the schematic and updates
the active variant to match.
"""
import sys
from pathlib import Path
from variant_manager import VariantManager
def get_all_schematic_files(root_schematic: str) -> list:
"""
Get all schematic files in a hierarchical design.
Args:
root_schematic: Path to root .kicad_sch file
Returns:
List of all schematic file paths (including root)
"""
root_path = Path(root_schematic)
if not root_path.exists():
return [root_schematic]
schematic_files = [str(root_path)]
schematic_dir = root_path.parent
# Read root schematic to find sheet files
try:
with open(root_path, 'r', encoding='utf-8') as f:
content = f.read()
# Find all sheet file references
for line in content.split('\n'):
if '(property "Sheetfile"' in line:
parts = line.split('"')
if len(parts) >= 4:
sheet_file = parts[3]
sheet_path = schematic_dir / sheet_file
if sheet_path.exists():
# Recursively get sheets from this sheet
sub_sheets = get_all_schematic_files(str(sheet_path))
for sub in sub_sheets:
if sub not in schematic_files:
schematic_files.append(sub)
except Exception as e:
print(f"Warning: Error reading sheet files: {e}")
return schematic_files
def sync_variant_from_schematic(schematic_file: str, target_variant: str = None) -> bool:
"""
Sync variant from schematic DNP flags and title block.
Args:
schematic_file: Path to .kicad_sch file
target_variant: Specific variant to sync to (optional). If not provided, uses title block or active variant.
Returns:
True if successful, False otherwise
"""
manager = VariantManager(schematic_file)
# If target_variant specified, use that
if target_variant:
if target_variant not in manager.get_variants():
print(f"Error: Variant '{target_variant}' not found")
return False
active_variant = target_variant
print(f"Syncing to specified variant: {active_variant}")
else:
# Read variant name from title block in root schematic
variant_from_title = None
sch_path = Path(schematic_file)
if sch_path.exists():
try:
with open(sch_path, 'r', encoding='utf-8') as f:
content = f.read()
lines = content.split('\n')
in_title_block = False
for line in lines:
stripped = line.strip()
if stripped.startswith('(title_block'):
in_title_block = True
elif in_title_block and stripped == ')':
break
elif in_title_block and '(comment 1' in stripped:
# Extract variant name from comment 1
parts = line.split('"')
if len(parts) >= 2:
variant_from_title = parts[1]
print(f"Found variant in title block: {variant_from_title}")
break
except:
pass
# Use variant from title block if found, otherwise use active variant
if variant_from_title and variant_from_title in manager.get_variants():
active_variant = variant_from_title
manager.set_active_variant(variant_from_title)
print(f"Set active variant to: {active_variant}")
else:
active_variant = manager.get_active_variant()
print(f"Using active variant: {active_variant}")
# Get all schematic files (root + hierarchical sheets)
all_schematics = get_all_schematic_files(schematic_file)
print(f"Processing {len(all_schematics)} schematic file(s)")
all_dnp_uuids = []
all_uuids = []
# Process each schematic file
for sch_file in all_schematics:
sch_path = Path(sch_file)
if not sch_path.exists():
print(f"Warning: Schematic file not found: {sch_file}")
continue
print(f"\n Processing: {sch_path.name}")
try:
with open(sch_path, 'r', encoding='utf-8') as f:
content = f.read()
# Parse schematic to find DNP components
lines = content.split('\n')
in_symbol = False
current_uuid = None
current_ref = None
current_lib_id = None
has_dnp = False
# Track line depth to know when we're at symbol level
for i, line in enumerate(lines):
stripped = line.strip()
# Detect start of symbol
if stripped.startswith('(symbol'):
in_symbol = True
current_uuid = None
current_ref = None
current_lib_id = None
has_dnp = False
symbol_uuid_found = False # Track if we found the main symbol UUID
# Detect end of symbol
elif in_symbol and stripped == ')':
# Check if this symbol block is closing (simple heuristic)
# Skip power symbols
is_power = current_lib_id and 'power:' in current_lib_id
is_power = is_power or (current_ref and current_ref.startswith('#'))
if current_uuid and has_dnp and not is_power:
if current_uuid not in all_dnp_uuids:
all_dnp_uuids.append(current_uuid)
print(f" Found DNP: {current_ref if current_ref else current_uuid}")
in_symbol = False
# Extract lib_id to check for power symbols
elif in_symbol and '(lib_id' in stripped:
lib_parts = line.split('"')
if len(lib_parts) >= 2:
current_lib_id = lib_parts[1]
# Check for DNP flag - can be (dnp), (dnp yes), or (dnp no)
# Do this before UUID extraction so we know if we need the UUID
elif in_symbol and '(dnp' in stripped and not has_dnp:
# Only set has_dnp if it's (dnp) or (dnp yes), not (dnp no)
if '(dnp yes)' in stripped or (stripped == '(dnp)'):
has_dnp = True
# Now look forward for the UUID (it comes right after DNP)
for j in range(i + 1, min(len(lines), i + 5)):
if '(uuid' in lines[j]:
# Check it's at symbol level
if '\t(uuid' in lines[j] or ' (uuid' in lines[j]:
uuid_parts = lines[j].split('"')
if len(uuid_parts) >= 2:
current_uuid = uuid_parts[1]
symbol_uuid_found = True
break
# Extract reference designator (for logging)
elif in_symbol and '(property "Reference"' in line and not current_ref:
# Extract reference from line like: (property "Reference" "R1"
parts = line.split('"')
if len(parts) >= 4:
current_ref = parts[3]
# Get all component UUIDs (excluding power symbols)
# Use same approach - look for UUID after DNP line
in_symbol = False
current_uuid = None
current_lib_id = None
current_ref = None
for i, line in enumerate(lines):
stripped = line.strip()
if stripped.startswith('(symbol'):
in_symbol = True
current_uuid = None
current_lib_id = None
current_ref = None
elif in_symbol and stripped == ')':
# Skip power symbols
is_power = current_lib_id and 'power:' in current_lib_id
is_power = is_power or (current_ref and current_ref.startswith('#'))
if current_uuid and not is_power and current_uuid not in all_uuids:
all_uuids.append(current_uuid)
in_symbol = False
elif in_symbol and '(lib_id' in stripped:
lib_parts = line.split('"')
if len(lib_parts) >= 2:
current_lib_id = lib_parts[1]
elif in_symbol and '(property "Reference"' in stripped and not current_ref:
ref_parts = line.split('"')
if len(ref_parts) >= 4:
current_ref = ref_parts[3]
elif in_symbol and '(dnp' in stripped and not current_uuid:
# Found DNP line - look forward for UUID
for j in range(i + 1, min(len(lines), i + 5)):
if '(uuid' in lines[j]:
if '\t(uuid' in lines[j] or ' (uuid' in lines[j]:
uuid_parts = lines[j].split('"')
if len(uuid_parts) >= 2:
current_uuid = uuid_parts[1]
break
except Exception as e:
print(f" Error processing {sch_path.name}: {e}")
import traceback
traceback.print_exc()
# Update variant with DNP list
print(f"\nUpdating variant '{active_variant}'...")
print(f" Found {len(all_uuids)} total UUIDs")
print(f" Found {len(all_dnp_uuids)} DNP UUIDs")
# Build the new DNP list directly instead of calling set_part_dnp multiple times
# This avoids multiple file saves
if active_variant in manager.variants["variants"]:
# Set the DNP list directly
manager.variants["variants"][active_variant]["dnp_parts"] = sorted(all_dnp_uuids)
# Save once at the end
manager._save_variants()
print(f" Updated DNP list with {len(all_dnp_uuids)} parts")
for uuid in all_dnp_uuids:
print(f" DNP UUID: {uuid}")
else:
print(f" Error: Variant '{active_variant}' not found in variants")
return False
print(f"\nVariant '{active_variant}' updated:")
print(f" Total components: {len(all_uuids)}")
print(f" DNP components: {len(all_dnp_uuids)}")
print(f" Fitted components: {len(all_uuids) - len(all_dnp_uuids)}")
return True
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python sync_variant.py <schematic.kicad_sch> [variant_name]")
sys.exit(1)
schematic = sys.argv[1]
variant = sys.argv[2] if len(sys.argv) > 2 else None
success = sync_variant_from_schematic(schematic, variant)
sys.exit(0 if success else 1)