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(for uuid in \"12ef4843-0c6b-44b3-b52b-21b354565dc0\" \"17a476c2-1017-41e7-9d81-f4153fe179f7\" \"25a5bbfc-04ad-4755-9a82-80d42d2cd8ce\")",
|
||||||
"Bash(do echo \"=== UUID: $uuid ===\")",
|
"Bash(do echo \"=== UUID: $uuid ===\")",
|
||||||
"Bash(grep -A 15 \"$uuid\" 25w.kicad_sch frequency.kicad_sch)",
|
"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": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|||||||
139
app.py
139
app.py
@@ -721,6 +721,145 @@ def handle_sync_from_schematic():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
emit('variant_error', {'error': str(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():
|
def shutdown_server():
|
||||||
print("Server stopped")
|
print("Server stopped")
|
||||||
os._exit(0)
|
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)
|
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"Applying variant '{variant_name}' to {Path(schematic_file).name}")
|
||||||
print(f"DNP parts ({len(dnp_parts)}): {dnp_parts}")
|
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)
|
# Get all schematic files (root + hierarchical sheets)
|
||||||
all_schematics = get_all_schematic_files(schematic_file)
|
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
|
# Process each schematic file
|
||||||
for idx, sch_file in enumerate(all_schematics):
|
for idx, sch_file in enumerate(all_schematics):
|
||||||
is_root = (idx == 0) # First file is the root schematic
|
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
|
overall_success = False
|
||||||
|
|
||||||
if overall_success:
|
if overall_success:
|
||||||
@@ -98,19 +112,22 @@ def apply_variant_to_schematic(schematic_file: str, variant_name: str, kicad_cli
|
|||||||
return overall_success
|
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:
|
Args:
|
||||||
schematic_file: Path to .kicad_sch file
|
schematic_file: Path to .kicad_sch file
|
||||||
dnp_uuids: List of UUIDs that should be DNP
|
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)
|
variant_name: Name of variant being applied (for title block)
|
||||||
is_root: True if this is the root schematic (not a sub-sheet)
|
is_root: True if this is the root schematic (not a sub-sheet)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True if successful, False otherwise
|
True if successful, False otherwise
|
||||||
"""
|
"""
|
||||||
|
if property_overrides is None:
|
||||||
|
property_overrides = {}
|
||||||
sch_path = Path(schematic_file)
|
sch_path = Path(schematic_file)
|
||||||
if not sch_path.exists():
|
if not sch_path.exists():
|
||||||
print(f"Error: Schematic file not found: {schematic_file}")
|
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:
|
else:
|
||||||
print(f" Cleared DNP: {current_ref if current_ref else current_uuid}")
|
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:
|
if modified:
|
||||||
# Backup original file
|
# Backup original file
|
||||||
backup_path = sch_path.with_suffix('.kicad_sch.bak')
|
backup_path = sch_path.with_suffix('.kicad_sch.bak')
|
||||||
|
|||||||
123
sync_variant.py
123
sync_variant.py
@@ -116,6 +116,7 @@ def sync_variant_from_schematic(schematic_file: str, target_variant: str = None)
|
|||||||
|
|
||||||
all_dnp_uuids = []
|
all_dnp_uuids = []
|
||||||
all_uuids = []
|
all_uuids = []
|
||||||
|
all_part_properties = {} # uuid -> {property_name: value}
|
||||||
|
|
||||||
# Process each schematic file
|
# Process each schematic file
|
||||||
for sch_file in all_schematics:
|
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:
|
with open(sch_path, 'r', encoding='utf-8') as f:
|
||||||
content = f.read()
|
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')
|
lines = content.split('\n')
|
||||||
in_symbol = False
|
in_symbol = False
|
||||||
current_uuid = None
|
current_uuid = None
|
||||||
current_ref = None
|
current_ref = None
|
||||||
current_lib_id = None
|
current_lib_id = None
|
||||||
|
current_properties = {} # Collect properties for this symbol
|
||||||
has_dnp = False
|
has_dnp = False
|
||||||
|
|
||||||
# Track line depth to know when we're at symbol level
|
# Track line depth to know when we're at symbol level
|
||||||
|
symbol_start_indent = None
|
||||||
for i, line in enumerate(lines):
|
for i, line in enumerate(lines):
|
||||||
stripped = line.strip()
|
stripped = line.strip()
|
||||||
|
indent_level = len(line) - len(line.lstrip())
|
||||||
|
|
||||||
# Detect start of symbol
|
# Detect start of symbol
|
||||||
if stripped.startswith('(symbol'):
|
if stripped.startswith('(symbol'):
|
||||||
@@ -148,20 +153,30 @@ def sync_variant_from_schematic(schematic_file: str, target_variant: str = None)
|
|||||||
current_uuid = None
|
current_uuid = None
|
||||||
current_ref = None
|
current_ref = None
|
||||||
current_lib_id = None
|
current_lib_id = None
|
||||||
|
current_properties = {}
|
||||||
has_dnp = False
|
has_dnp = False
|
||||||
symbol_uuid_found = False # Track if we found the main symbol UUID
|
symbol_uuid_found = False # Track if we found the main symbol UUID
|
||||||
|
symbol_start_indent = indent_level
|
||||||
|
|
||||||
# Detect end of symbol
|
# Detect end of symbol - closing paren at same indent as symbol start
|
||||||
elif in_symbol and stripped == ')':
|
elif in_symbol and stripped == ')' and indent_level == symbol_start_indent:
|
||||||
# Check if this symbol block is closing (simple heuristic)
|
|
||||||
# Skip power symbols
|
# Skip power symbols
|
||||||
is_power = current_lib_id and 'power:' in current_lib_id
|
is_power = current_lib_id and 'power:' in current_lib_id
|
||||||
is_power = is_power or (current_ref and current_ref.startswith('#'))
|
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:
|
||||||
if current_uuid not in all_dnp_uuids:
|
# Store properties for this part
|
||||||
all_dnp_uuids.append(current_uuid)
|
if current_properties:
|
||||||
print(f" Found DNP: {current_ref if current_ref else current_uuid}")
|
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}")
|
||||||
in_symbol = False
|
in_symbol = False
|
||||||
|
|
||||||
# Extract lib_id to check for power symbols
|
# Extract lib_id to check for power symbols
|
||||||
@@ -170,22 +185,23 @@ def sync_variant_from_schematic(schematic_file: str, target_variant: str = None)
|
|||||||
if len(lib_parts) >= 2:
|
if len(lib_parts) >= 2:
|
||||||
current_lib_id = lib_parts[1]
|
current_lib_id = lib_parts[1]
|
||||||
|
|
||||||
# Check for DNP flag - can be (dnp), (dnp yes), or (dnp no)
|
# Check for DNP flag and extract UUID
|
||||||
# Do this before UUID extraction so we know if we need the UUID
|
# DNP line comes before UUID, so we look forward for the UUID
|
||||||
elif in_symbol and '(dnp' in stripped and not has_dnp:
|
elif in_symbol and '(dnp' in stripped and not symbol_uuid_found:
|
||||||
# Only set has_dnp if it's (dnp) or (dnp yes), not (dnp no)
|
# Check if DNP is set
|
||||||
if '(dnp yes)' in stripped or (stripped == '(dnp)'):
|
if '(dnp yes)' in stripped or (stripped == '(dnp)'):
|
||||||
has_dnp = True
|
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)):
|
# Look forward for the UUID (it comes right after DNP)
|
||||||
if '(uuid' in lines[j]:
|
for j in range(i + 1, min(len(lines), i + 5)):
|
||||||
# Check it's at symbol level
|
if '(uuid' in lines[j]:
|
||||||
if '\t(uuid' in lines[j] or ' (uuid' in lines[j]:
|
# Check it's at symbol level
|
||||||
uuid_parts = lines[j].split('"')
|
if '\t(uuid' in lines[j] or ' (uuid' in lines[j]:
|
||||||
if len(uuid_parts) >= 2:
|
uuid_parts = lines[j].split('"')
|
||||||
current_uuid = uuid_parts[1]
|
if len(uuid_parts) >= 2:
|
||||||
symbol_uuid_found = True
|
current_uuid = uuid_parts[1]
|
||||||
break
|
symbol_uuid_found = True
|
||||||
|
break
|
||||||
|
|
||||||
# Extract reference designator (for logging)
|
# Extract reference designator (for logging)
|
||||||
elif in_symbol and '(property "Reference"' in line and not current_ref:
|
elif in_symbol and '(property "Reference"' in line and not current_ref:
|
||||||
@@ -194,6 +210,15 @@ def sync_variant_from_schematic(schematic_file: str, target_variant: str = None)
|
|||||||
if len(parts) >= 4:
|
if len(parts) >= 4:
|
||||||
current_ref = parts[3]
|
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)
|
# Get all component UUIDs (excluding power symbols)
|
||||||
# Use same approach - look for UUID after DNP line
|
# Use same approach - look for UUID after DNP line
|
||||||
in_symbol = False
|
in_symbol = False
|
||||||
@@ -238,31 +263,57 @@ def sync_variant_from_schematic(schematic_file: str, target_variant: str = None)
|
|||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
# Update variant with DNP list
|
# Update variant with DNP list and property changes
|
||||||
print(f"\nUpdating variant '{active_variant}'...")
|
print(f"\nUpdating variant '{active_variant}'...")
|
||||||
print(f" Found {len(all_uuids)} total UUIDs")
|
print(f" Found {len(all_uuids)} total UUIDs")
|
||||||
print(f" Found {len(all_dnp_uuids)} DNP 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
|
if active_variant not in manager.variants["variants"]:
|
||||||
# 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")
|
print(f" Error: Variant '{active_variant}' not found in variants")
|
||||||
return False
|
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"\nVariant '{active_variant}' updated:")
|
print(f"\nVariant '{active_variant}' updated:")
|
||||||
print(f" Total components: {len(all_uuids)}")
|
print(f" Total components: {len(all_uuids)}")
|
||||||
print(f" DNP components: {len(all_dnp_uuids)}")
|
print(f" DNP components: {len(all_dnp_uuids)}")
|
||||||
print(f" Fitted components: {len(all_uuids) - len(all_dnp_uuids)}")
|
print(f" Fitted components: {len(all_uuids) - len(all_dnp_uuids)}")
|
||||||
|
print(f" Property overrides: {property_changes}")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
@@ -306,6 +306,12 @@
|
|||||||
<div id="dbSyncMessage" class="message"></div>
|
<div id="dbSyncMessage" class="message"></div>
|
||||||
</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">
|
<div class="actions">
|
||||||
<h2>System Initialization</h2>
|
<h2>System Initialization</h2>
|
||||||
<button id="initUserBtn" class="btn">Initialize User Environment</button>
|
<button id="initUserBtn" class="btn">Initialize User Environment</button>
|
||||||
@@ -835,6 +841,36 @@
|
|||||||
loadVariants();
|
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
|
// Close modals when clicking outside
|
||||||
window.onclick = function(event) {
|
window.onclick = function(event) {
|
||||||
if (event.target.className === 'modal') {
|
if (event.target.className === 'modal') {
|
||||||
|
|||||||
@@ -35,19 +35,39 @@ class VariantManager:
|
|||||||
"""Load variants from JSON file"""
|
"""Load variants from JSON file"""
|
||||||
if self.variants_file.exists():
|
if self.variants_file.exists():
|
||||||
with open(self.variants_file, 'r', encoding='utf-8') as f:
|
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:
|
else:
|
||||||
# Default structure
|
# Default structure
|
||||||
return {
|
return {
|
||||||
"meta": {
|
"meta": {
|
||||||
"version": 2, # Version 2 uses UUIDs instead of references
|
"version": 3, # Version 3 adds base_values and part_overrides
|
||||||
"active_variant": "default"
|
"active_variant": "default"
|
||||||
},
|
},
|
||||||
|
"base_values": {
|
||||||
|
# UUID -> {property_name: value}
|
||||||
|
# Stores the base/default values for parts that vary between variants
|
||||||
|
},
|
||||||
"variants": {
|
"variants": {
|
||||||
"default": {
|
"default": {
|
||||||
"name": "default",
|
"name": "default",
|
||||||
"description": "Default variant - all parts fitted",
|
"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"]
|
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 __name__ == "__main__":
|
||||||
if len(sys.argv) < 2:
|
if len(sys.argv) < 2:
|
||||||
|
|||||||
Reference in New Issue
Block a user