added windows interaction test
adding more variant stuff
This commit is contained in:
@@ -6,7 +6,16 @@
|
||||
"Bash(for uuid in \"12ef4843-0c6b-44b3-b52b-21b354565dc0\" \"17a476c2-1017-41e7-9d81-f4153fe179f7\" \"25a5bbfc-04ad-4755-9a82-80d42d2cd8ce\")",
|
||||
"Bash(do echo \"=== UUID: $uuid ===\")",
|
||||
"Bash(grep -A 15 \"$uuid\" 25w.kicad_sch frequency.kicad_sch)",
|
||||
"Bash(done)"
|
||||
"Bash(done)",
|
||||
"Bash(cat 25w.variants.json)",
|
||||
"Bash(python sync_variant.py d:/tx/25w-kicad/25w/25w.kicad_sch default)",
|
||||
"Bash(python -c \"\nimport json\nwith open(''25w.variants.json'', ''r'') as f:\n data = json.load(f)\n\n# Create V2 variant with different value for R1\ndata[''variants''][''V2''] = {\n ''name'': ''V2'',\n ''description'': ''Test variant with different R1 value'',\n ''dnp_parts'': [''4245045e-d174-4c56-920d-fabc03d1e234''],\n ''part_overrides'': {\n ''a2101067-196b-479f-99a2-dd5b5d884d82'': { # R1\n ''Value'': ''10M'' # Different from base 7.32M\n }\n }\n}\n\nwith open(''25w.variants.json'', ''w'') as f:\n json.dump(data, f, indent=2)\n\nprint(''Created V2 variant with R1 Value override'')\n\")",
|
||||
"Bash(python apply_variant.py d:/tx/25w-kicad/25w/25w.kicad_sch V2)",
|
||||
"Bash(python apply_variant.py d:/tx/25w-kicad/25w/25w.kicad_sch default)",
|
||||
"Bash(python -c \"import pygetwindow; import pyautogui; print(''Libraries installed'')\")",
|
||||
"Bash(pip install pygetwindow pyautogui)",
|
||||
"Bash(python -c \"import pygetwindow; import pyautogui; print(''Libraries ready'')\")",
|
||||
"Bash(python -c \"import win32gui; import win32con; print(''pywin32 installed'')\")"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
||||
139
app.py
139
app.py
@@ -721,6 +721,145 @@ def handle_sync_from_schematic():
|
||||
except Exception as e:
|
||||
emit('variant_error', {'error': str(e)})
|
||||
|
||||
@socketio.on('test_window_interaction')
|
||||
def handle_test_window_interaction():
|
||||
try:
|
||||
import pygetwindow as gw
|
||||
import pyautogui
|
||||
import time
|
||||
import win32gui
|
||||
import win32con
|
||||
|
||||
emit('window_test_status', {'status': 'Looking for KiCad schematic window...'})
|
||||
|
||||
# List all windows for debugging
|
||||
all_windows = gw.getAllTitles()
|
||||
emit('window_test_status', {'status': f'DEBUG: Found {len(all_windows)} windows total'})
|
||||
|
||||
# Find KiCad schematic editor window
|
||||
windows = gw.getWindowsWithTitle('Schematic Editor')
|
||||
emit('window_test_status', {'status': f'DEBUG: Found {len(windows)} windows with "Schematic Editor"'})
|
||||
|
||||
window_found = False
|
||||
if not windows:
|
||||
# Try alternative window title
|
||||
schematic_windows = [w for w in all_windows if 'kicad' in w.lower() and 'schematic' in w.lower()]
|
||||
emit('window_test_status', {'status': f'DEBUG: Found {len(schematic_windows)} windows with "kicad" and "schematic"'})
|
||||
|
||||
if schematic_windows:
|
||||
emit('window_test_status', {'status': f'DEBUG: Using window: {schematic_windows[0]}'})
|
||||
windows = gw.getWindowsWithTitle(schematic_windows[0])
|
||||
window_found = len(windows) > 0
|
||||
else:
|
||||
window_found = True
|
||||
|
||||
# If window is found, close it
|
||||
if window_found:
|
||||
window = windows[0]
|
||||
emit('window_test_status', {'status': f'Found window: "{window.title}"'})
|
||||
|
||||
# Get window position and size
|
||||
hwnd = window._hWnd
|
||||
rect = win32gui.GetWindowRect(hwnd)
|
||||
x, y, x2, y2 = rect
|
||||
width = x2 - x
|
||||
height = y2 - y
|
||||
emit('window_test_status', {'status': f'DEBUG: Window position=({x},{y}), size=({width}x{height})'})
|
||||
|
||||
# Click on the window's title bar to activate it (more reliable than SetForegroundWindow)
|
||||
click_x = x + width // 2
|
||||
click_y = y + 10 # Title bar is usually at the top
|
||||
emit('window_test_status', {'status': f'Clicking window at ({click_x}, {click_y}) to activate...'})
|
||||
pyautogui.click(click_x, click_y)
|
||||
time.sleep(0.5)
|
||||
|
||||
emit('window_test_status', {'status': 'Sending Ctrl+S (save)...'})
|
||||
pyautogui.hotkey('ctrl', 's')
|
||||
time.sleep(1.0)
|
||||
|
||||
emit('window_test_status', {'status': 'Attempting to close window...'})
|
||||
|
||||
# Method 1: Try WM_CLOSE message
|
||||
emit('window_test_status', {'status': 'DEBUG: Sending WM_CLOSE message...'})
|
||||
win32gui.PostMessage(hwnd, win32con.WM_CLOSE, 0, 0)
|
||||
time.sleep(1.0)
|
||||
|
||||
# Check if window still exists
|
||||
if win32gui.IsWindow(hwnd):
|
||||
emit('window_test_status', {'status': 'DEBUG: Window still exists after WM_CLOSE, trying to click and send Alt+F4...'})
|
||||
|
||||
# Click on window again to make sure it has focus
|
||||
try:
|
||||
rect = win32gui.GetWindowRect(hwnd)
|
||||
x, y, x2, y2 = rect
|
||||
click_x = x + (x2 - x) // 2
|
||||
click_y = y + 10
|
||||
pyautogui.click(click_x, click_y)
|
||||
time.sleep(0.3)
|
||||
except:
|
||||
emit('window_test_status', {'status': 'DEBUG: Could not click window (may already be closed)'})
|
||||
|
||||
pyautogui.hotkey('alt', 'F4')
|
||||
time.sleep(1.0)
|
||||
|
||||
# Final check
|
||||
if win32gui.IsWindow(hwnd):
|
||||
emit('window_test_status', {'status': 'DEBUG: Window still exists after Alt+F4 - may need manual intervention'})
|
||||
else:
|
||||
emit('window_test_status', {'status': 'DEBUG: Window closed successfully via Alt+F4'})
|
||||
else:
|
||||
emit('window_test_status', {'status': 'DEBUG: Window closed successfully via WM_CLOSE'})
|
||||
else:
|
||||
emit('window_test_status', {'status': 'No KiCad schematic window found - will open it'})
|
||||
|
||||
# Wait a couple seconds before reopening
|
||||
emit('window_test_status', {'status': 'Waiting 2 seconds before reopening...'})
|
||||
time.sleep(2.0)
|
||||
|
||||
# Reopen the schematic editor
|
||||
schematic_file = app_args.get('Schematic File', '')
|
||||
if not schematic_file:
|
||||
emit('window_test_error', {'error': 'No schematic file specified in app arguments'})
|
||||
return
|
||||
|
||||
emit('window_test_status', {'status': f'Relaunching schematic editor with: {schematic_file}'})
|
||||
|
||||
# Launch KiCad schematic editor
|
||||
# The schematic editor executable is typically in the same directory as kicad.exe
|
||||
import os
|
||||
kicad_bin_dir = r"C:\Program Files\KiCad\9.0\bin" # Default KiCad 9 installation path
|
||||
if not os.path.exists(kicad_bin_dir):
|
||||
kicad_bin_dir = r"C:\Program Files\KiCad\8.0\bin" # Try KiCad 8
|
||||
|
||||
eeschema_exe = os.path.join(kicad_bin_dir, "eeschema.exe")
|
||||
|
||||
if not os.path.exists(eeschema_exe):
|
||||
emit('window_test_error', {'error': f'KiCad executable not found at: {eeschema_exe}'})
|
||||
return
|
||||
|
||||
emit('window_test_status', {'status': f'DEBUG: Launching {eeschema_exe} {schematic_file}'})
|
||||
|
||||
# Launch KiCad with the schematic file
|
||||
result = subprocess.Popen([eeschema_exe, schematic_file], shell=False)
|
||||
emit('window_test_status', {'status': f'DEBUG: Process started with PID {result.pid}'})
|
||||
|
||||
time.sleep(2.0)
|
||||
|
||||
# Verify the window opened
|
||||
all_windows = gw.getAllTitles()
|
||||
schematic_windows = [w for w in all_windows if 'kicad' in w.lower() and 'schematic' in w.lower()]
|
||||
if schematic_windows:
|
||||
emit('window_test_status', {'status': f'Successfully reopened schematic: {schematic_windows[0]}'})
|
||||
else:
|
||||
emit('window_test_status', {'status': 'Schematic editor launched but window not detected yet'})
|
||||
|
||||
emit('window_test_complete', {'message': 'Window interaction test completed!'})
|
||||
except ImportError as e:
|
||||
emit('window_test_error', {'error': f'Missing required library: {str(e)}. Please install: pip install pygetwindow pyautogui pywin32'})
|
||||
except Exception as e:
|
||||
import traceback
|
||||
emit('window_test_error', {'error': f'{str(e)}\n\nTraceback:\n{traceback.format_exc()}'})
|
||||
|
||||
def shutdown_server():
|
||||
print("Server stopped")
|
||||
os._exit(0)
|
||||
|
||||
@@ -76,8 +76,22 @@ def apply_variant_to_schematic(schematic_file: str, variant_name: str, kicad_cli
|
||||
|
||||
dnp_parts = manager.get_dnp_parts(variant_name)
|
||||
|
||||
# Build property overrides dict: uuid -> {base + variant_overrides}
|
||||
property_overrides = {}
|
||||
base_values = manager.variants.get("base_values", {})
|
||||
variant_overrides = manager.variants["variants"][variant_name].get("part_overrides", {})
|
||||
|
||||
# Merge base + variant overrides for each part
|
||||
all_uuids = set(list(base_values.keys()) + list(variant_overrides.keys()))
|
||||
for uuid in all_uuids:
|
||||
props = base_values.get(uuid, {}).copy()
|
||||
props.update(variant_overrides.get(uuid, {}))
|
||||
if props:
|
||||
property_overrides[uuid] = props
|
||||
|
||||
print(f"Applying variant '{variant_name}' to {Path(schematic_file).name}")
|
||||
print(f"DNP parts ({len(dnp_parts)}): {dnp_parts}")
|
||||
print(f"Property overrides for {len(property_overrides)} parts")
|
||||
|
||||
# Get all schematic files (root + hierarchical sheets)
|
||||
all_schematics = get_all_schematic_files(schematic_file)
|
||||
@@ -88,7 +102,7 @@ def apply_variant_to_schematic(schematic_file: str, variant_name: str, kicad_cli
|
||||
# Process each schematic file
|
||||
for idx, sch_file in enumerate(all_schematics):
|
||||
is_root = (idx == 0) # First file is the root schematic
|
||||
if not process_single_schematic(sch_file, dnp_parts, variant_name, is_root):
|
||||
if not process_single_schematic(sch_file, dnp_parts, property_overrides, variant_name, is_root):
|
||||
overall_success = False
|
||||
|
||||
if overall_success:
|
||||
@@ -98,19 +112,22 @@ def apply_variant_to_schematic(schematic_file: str, variant_name: str, kicad_cli
|
||||
return overall_success
|
||||
|
||||
|
||||
def process_single_schematic(schematic_file: str, dnp_uuids: list, variant_name: str = None, is_root: bool = False) -> bool:
|
||||
def process_single_schematic(schematic_file: str, dnp_uuids: list, property_overrides: dict = None, variant_name: str = None, is_root: bool = False) -> bool:
|
||||
"""
|
||||
Process a single schematic file to apply DNP flags.
|
||||
Process a single schematic file to apply DNP flags and property overrides.
|
||||
|
||||
Args:
|
||||
schematic_file: Path to .kicad_sch file
|
||||
dnp_uuids: List of UUIDs that should be DNP
|
||||
property_overrides: Dict of UUID -> {property_name: value} for property overrides
|
||||
variant_name: Name of variant being applied (for title block)
|
||||
is_root: True if this is the root schematic (not a sub-sheet)
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
if property_overrides is None:
|
||||
property_overrides = {}
|
||||
sch_path = Path(schematic_file)
|
||||
if not sch_path.exists():
|
||||
print(f"Error: Schematic file not found: {schematic_file}")
|
||||
@@ -228,6 +245,70 @@ def process_single_schematic(schematic_file: str, dnp_uuids: list, variant_name:
|
||||
else:
|
||||
print(f" Cleared DNP: {current_ref if current_ref else current_uuid}")
|
||||
|
||||
# Apply property overrides
|
||||
# Parse through symbols and update properties for parts that have overrides
|
||||
if property_overrides:
|
||||
in_symbol = False
|
||||
current_uuid = None
|
||||
current_ref = None
|
||||
symbol_start_indent = None
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
stripped = line.strip()
|
||||
indent_level = len(line) - len(line.lstrip())
|
||||
|
||||
# Detect start of symbol
|
||||
if stripped.startswith('(symbol'):
|
||||
in_symbol = True
|
||||
current_uuid = None
|
||||
current_ref = None
|
||||
symbol_start_indent = indent_level
|
||||
|
||||
# Detect end of symbol (closing paren at same indent level)
|
||||
elif in_symbol and stripped == ')' and indent_level == symbol_start_indent:
|
||||
in_symbol = False
|
||||
current_uuid = None
|
||||
current_ref = None
|
||||
|
||||
# Extract UUID when in symbol (at symbol level, not nested)
|
||||
elif in_symbol and '(dnp' in stripped and not current_uuid:
|
||||
# Look forward for UUID after DNP line
|
||||
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
|
||||
|
||||
# Extract reference (for logging)
|
||||
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]
|
||||
|
||||
# Check if this symbol has property overrides
|
||||
elif in_symbol and current_uuid and current_uuid in property_overrides:
|
||||
overrides = property_overrides[current_uuid]
|
||||
|
||||
# Check each tracked property
|
||||
for prop_name in ['Value', 'MPN', 'Manufacturer', 'IPN']:
|
||||
if prop_name in overrides and f'(property "{prop_name}"' in stripped:
|
||||
# Extract current value
|
||||
parts = line.split('"')
|
||||
if len(parts) >= 4:
|
||||
current_value = parts[3]
|
||||
new_value = overrides[prop_name]
|
||||
|
||||
# Update if different
|
||||
if current_value != new_value:
|
||||
# Reconstruct the line with new value
|
||||
indent = line[:len(line) - len(line.lstrip())]
|
||||
parts[3] = new_value
|
||||
lines[i] = indent + '"'.join(parts)
|
||||
modified = True
|
||||
print(f" Set {prop_name}: {current_ref if current_ref else current_uuid} = {new_value} (was {current_value})")
|
||||
|
||||
if modified:
|
||||
# Backup original file
|
||||
backup_path = sch_path.with_suffix('.kicad_sch.bak')
|
||||
|
||||
@@ -116,6 +116,7 @@ def sync_variant_from_schematic(schematic_file: str, target_variant: str = None)
|
||||
|
||||
all_dnp_uuids = []
|
||||
all_uuids = []
|
||||
all_part_properties = {} # uuid -> {property_name: value}
|
||||
|
||||
# Process each schematic file
|
||||
for sch_file in all_schematics:
|
||||
@@ -130,17 +131,21 @@ def sync_variant_from_schematic(schematic_file: str, target_variant: str = None)
|
||||
with open(sch_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Parse schematic to find DNP components
|
||||
# Parse schematic to find DNP components and read properties
|
||||
# Track properties: Value, MPN, Manufacturer, IPN
|
||||
lines = content.split('\n')
|
||||
in_symbol = False
|
||||
current_uuid = None
|
||||
current_ref = None
|
||||
current_lib_id = None
|
||||
current_properties = {} # Collect properties for this symbol
|
||||
has_dnp = False
|
||||
|
||||
# Track line depth to know when we're at symbol level
|
||||
symbol_start_indent = None
|
||||
for i, line in enumerate(lines):
|
||||
stripped = line.strip()
|
||||
indent_level = len(line) - len(line.lstrip())
|
||||
|
||||
# Detect start of symbol
|
||||
if stripped.startswith('(symbol'):
|
||||
@@ -148,17 +153,27 @@ def sync_variant_from_schematic(schematic_file: str, target_variant: str = None)
|
||||
current_uuid = None
|
||||
current_ref = None
|
||||
current_lib_id = None
|
||||
current_properties = {}
|
||||
has_dnp = False
|
||||
symbol_uuid_found = False # Track if we found the main symbol UUID
|
||||
symbol_start_indent = indent_level
|
||||
|
||||
# Detect end of symbol
|
||||
elif in_symbol and stripped == ')':
|
||||
# Check if this symbol block is closing (simple heuristic)
|
||||
# Detect end of symbol - closing paren at same indent as symbol start
|
||||
elif in_symbol and stripped == ')' and indent_level == symbol_start_indent:
|
||||
# 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 and not is_power:
|
||||
# Store properties for this part
|
||||
if current_properties:
|
||||
if current_uuid not in all_part_properties:
|
||||
all_part_properties[current_uuid] = {}
|
||||
all_part_properties[current_uuid].update(current_properties)
|
||||
all_part_properties[current_uuid]['reference'] = current_ref # For display
|
||||
|
||||
# Track DNP
|
||||
if has_dnp:
|
||||
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}")
|
||||
@@ -170,13 +185,14 @@ def sync_variant_from_schematic(schematic_file: str, target_variant: str = None)
|
||||
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)
|
||||
# Check for DNP flag and extract UUID
|
||||
# DNP line comes before UUID, so we look forward for the UUID
|
||||
elif in_symbol and '(dnp' in stripped and not symbol_uuid_found:
|
||||
# Check if DNP is set
|
||||
if '(dnp yes)' in stripped or (stripped == '(dnp)'):
|
||||
has_dnp = True
|
||||
# Now look forward for the UUID (it comes right after DNP)
|
||||
|
||||
# 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
|
||||
@@ -194,6 +210,15 @@ def sync_variant_from_schematic(schematic_file: str, target_variant: str = None)
|
||||
if len(parts) >= 4:
|
||||
current_ref = parts[3]
|
||||
|
||||
# Extract tracked properties
|
||||
elif in_symbol:
|
||||
for prop_name in ['Value', 'MPN', 'Manufacturer', 'IPN']:
|
||||
if f'(property "{prop_name}"' in line:
|
||||
parts = line.split('"')
|
||||
if len(parts) >= 4:
|
||||
current_properties[prop_name] = parts[3]
|
||||
break
|
||||
|
||||
# Get all component UUIDs (excluding power symbols)
|
||||
# Use same approach - look for UUID after DNP line
|
||||
in_symbol = False
|
||||
@@ -238,31 +263,57 @@ def sync_variant_from_schematic(schematic_file: str, target_variant: str = None)
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
# Update variant with DNP list
|
||||
# Update variant with DNP list and property changes
|
||||
print(f"\nUpdating variant '{active_variant}'...")
|
||||
print(f" Found {len(all_uuids)} total UUIDs")
|
||||
print(f" Found {len(all_dnp_uuids)} DNP UUIDs")
|
||||
print(f" Found {len(all_part_properties)} parts with tracked properties")
|
||||
|
||||
# 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
|
||||
if active_variant not in manager.variants["variants"]:
|
||||
print(f" Error: Variant '{active_variant}' not found in variants")
|
||||
return False
|
||||
|
||||
# Update DNP list
|
||||
manager.variants["variants"][active_variant]["dnp_parts"] = sorted(all_dnp_uuids)
|
||||
|
||||
# Update property overrides based on schematic values
|
||||
property_changes = 0
|
||||
for uuid, sch_properties in all_part_properties.items():
|
||||
# Get expected properties (base + variant overrides)
|
||||
expected_props = manager.get_part_properties(active_variant, uuid)
|
||||
|
||||
# Check each tracked property
|
||||
for prop_name in ['Value', 'MPN', 'Manufacturer', 'IPN']:
|
||||
sch_value = sch_properties.get(prop_name, '')
|
||||
expected_value = expected_props.get(prop_name, '')
|
||||
|
||||
# If schematic value differs from expected, update the variant
|
||||
if sch_value and sch_value != expected_value:
|
||||
# Check if we have a base value for this property
|
||||
base_value = manager.variants.get("base_values", {}).get(uuid, {}).get(prop_name, '')
|
||||
|
||||
if not base_value:
|
||||
# No base value exists, so set it
|
||||
manager.set_base_value(uuid, prop_name, sch_value)
|
||||
print(f" Set base {prop_name} for {sch_properties.get('reference', uuid)}: {sch_value}")
|
||||
elif sch_value != base_value:
|
||||
# Base value exists but differs - store as override
|
||||
if "part_overrides" not in manager.variants["variants"][active_variant]:
|
||||
manager.variants["variants"][active_variant]["part_overrides"] = {}
|
||||
if uuid not in manager.variants["variants"][active_variant]["part_overrides"]:
|
||||
manager.variants["variants"][active_variant]["part_overrides"][uuid] = {}
|
||||
manager.variants["variants"][active_variant]["part_overrides"][uuid][prop_name] = sch_value
|
||||
property_changes += 1
|
||||
print(f" Override {prop_name} for {sch_properties.get('reference', uuid)}: {sch_value} (base: {base_value})")
|
||||
|
||||
# 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)}")
|
||||
print(f" Property overrides: {property_changes}")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -306,6 +306,12 @@
|
||||
<div id="dbSyncMessage" class="message"></div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<h2>Window Testing</h2>
|
||||
<button id="testWindowBtn" class="btn">Test: Save & Close Schematic Window</button>
|
||||
<div id="windowTestMessage" class="message"></div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<h2>System Initialization</h2>
|
||||
<button id="initUserBtn" class="btn">Initialize User Environment</button>
|
||||
@@ -835,6 +841,36 @@
|
||||
loadVariants();
|
||||
});
|
||||
|
||||
// Window Testing
|
||||
const testWindowBtn = document.getElementById('testWindowBtn');
|
||||
const windowTestMessageEl = document.getElementById('windowTestMessage');
|
||||
|
||||
function showWindowTestMessage(text, type) {
|
||||
windowTestMessageEl.textContent = text;
|
||||
windowTestMessageEl.className = 'message ' + type;
|
||||
windowTestMessageEl.style.display = 'block';
|
||||
}
|
||||
|
||||
testWindowBtn.addEventListener('click', () => {
|
||||
testWindowBtn.disabled = true;
|
||||
showWindowTestMessage('Testing window interaction...', 'info');
|
||||
socket.emit('test_window_interaction');
|
||||
});
|
||||
|
||||
socket.on('window_test_status', (data) => {
|
||||
showWindowTestMessage(data.status, 'info');
|
||||
});
|
||||
|
||||
socket.on('window_test_complete', (data) => {
|
||||
showWindowTestMessage(data.message, 'success');
|
||||
testWindowBtn.disabled = false;
|
||||
});
|
||||
|
||||
socket.on('window_test_error', (data) => {
|
||||
showWindowTestMessage('Error: ' + data.error, 'error');
|
||||
testWindowBtn.disabled = false;
|
||||
});
|
||||
|
||||
// Close modals when clicking outside
|
||||
window.onclick = function(event) {
|
||||
if (event.target.className === 'modal') {
|
||||
|
||||
@@ -35,19 +35,39 @@ class VariantManager:
|
||||
"""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)
|
||||
data = json.load(f)
|
||||
|
||||
# Migrate from version 2 to version 3
|
||||
if data.get("meta", {}).get("version", 1) < 3:
|
||||
print(f"Migrating variant file from version {data.get('meta', {}).get('version', 1)} to 3")
|
||||
if "base_values" not in data:
|
||||
data["base_values"] = {}
|
||||
for variant in data.get("variants", {}).values():
|
||||
if "part_overrides" not in variant:
|
||||
variant["part_overrides"] = {}
|
||||
data["meta"]["version"] = 3
|
||||
# Save migrated version
|
||||
with open(self.variants_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=2)
|
||||
|
||||
return data
|
||||
else:
|
||||
# Default structure
|
||||
return {
|
||||
"meta": {
|
||||
"version": 2, # Version 2 uses UUIDs instead of references
|
||||
"version": 3, # Version 3 adds base_values and part_overrides
|
||||
"active_variant": "default"
|
||||
},
|
||||
"base_values": {
|
||||
# UUID -> {property_name: value}
|
||||
# Stores the base/default values for parts that vary between variants
|
||||
},
|
||||
"variants": {
|
||||
"default": {
|
||||
"name": "default",
|
||||
"description": "Default variant - all parts fitted",
|
||||
"dnp_parts": [] # List of UUIDs that are DNP (Do Not Place)
|
||||
"dnp_parts": [], # List of UUIDs that are DNP (Do Not Place)
|
||||
"part_overrides": {} # UUID -> {property_name: override_value}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,6 +214,88 @@ class VariantManager:
|
||||
|
||||
return uuid in self.variants["variants"][variant_name]["dnp_parts"]
|
||||
|
||||
def get_part_properties(self, variant_name: str, uuid: str) -> Dict[str, str]:
|
||||
"""
|
||||
Get effective property values for a part in a variant.
|
||||
Merges base_values with variant-specific overrides.
|
||||
|
||||
Args:
|
||||
variant_name: Variant name
|
||||
uuid: Component UUID
|
||||
|
||||
Returns:
|
||||
Dict of property_name -> value (merged base + overrides)
|
||||
"""
|
||||
if variant_name not in self.variants["variants"]:
|
||||
return {}
|
||||
|
||||
# Start with base values
|
||||
properties = self.variants.get("base_values", {}).get(uuid, {}).copy()
|
||||
|
||||
# Apply variant overrides
|
||||
overrides = self.variants["variants"][variant_name].get("part_overrides", {}).get(uuid, {})
|
||||
properties.update(overrides)
|
||||
|
||||
return properties
|
||||
|
||||
def set_part_property(self, variant_name: str, uuid: str, property_name: str, value: str) -> bool:
|
||||
"""
|
||||
Set a property override for a part in a variant.
|
||||
|
||||
Args:
|
||||
variant_name: Variant name
|
||||
uuid: Component UUID
|
||||
property_name: Property to set (e.g., "Value", "MPN")
|
||||
value: New value
|
||||
|
||||
Returns:
|
||||
True if successful, False if variant doesn't exist
|
||||
"""
|
||||
if variant_name not in self.variants["variants"]:
|
||||
return False
|
||||
|
||||
# Ensure part_overrides exists for this variant
|
||||
if "part_overrides" not in self.variants["variants"][variant_name]:
|
||||
self.variants["variants"][variant_name]["part_overrides"] = {}
|
||||
|
||||
# Get or create override dict for this UUID
|
||||
if uuid not in self.variants["variants"][variant_name]["part_overrides"]:
|
||||
self.variants["variants"][variant_name]["part_overrides"][uuid] = {}
|
||||
|
||||
# Check if this matches base value - if so, remove override (keep it sparse)
|
||||
base_value = self.variants.get("base_values", {}).get(uuid, {}).get(property_name)
|
||||
if value == base_value:
|
||||
# Remove from overrides since it matches base
|
||||
if property_name in self.variants["variants"][variant_name]["part_overrides"][uuid]:
|
||||
del self.variants["variants"][variant_name]["part_overrides"][uuid][property_name]
|
||||
# Clean up empty override dicts
|
||||
if not self.variants["variants"][variant_name]["part_overrides"][uuid]:
|
||||
del self.variants["variants"][variant_name]["part_overrides"][uuid]
|
||||
else:
|
||||
# Store override
|
||||
self.variants["variants"][variant_name]["part_overrides"][uuid][property_name] = value
|
||||
|
||||
self._save_variants()
|
||||
return True
|
||||
|
||||
def set_base_value(self, uuid: str, property_name: str, value: str):
|
||||
"""
|
||||
Set a base property value for a part.
|
||||
|
||||
Args:
|
||||
uuid: Component UUID
|
||||
property_name: Property name
|
||||
value: Value to set
|
||||
"""
|
||||
if "base_values" not in self.variants:
|
||||
self.variants["base_values"] = {}
|
||||
|
||||
if uuid not in self.variants["base_values"]:
|
||||
self.variants["base_values"][uuid] = {}
|
||||
|
||||
self.variants["base_values"][uuid][property_name] = value
|
||||
self._save_variants()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
|
||||
Reference in New Issue
Block a user