diff --git a/Archive.zip b/Archive.zip index 913044f..d675ce0 100644 Binary files a/Archive.zip and b/Archive.zip differ diff --git a/__init__.py b/__init__.py index 9e62c6a..9e3a403 100644 --- a/__init__.py +++ b/__init__.py @@ -499,7 +499,7 @@ def delete_blend_file_preset(preset_name): return False def initialize_presets(): - """Initialize presets during addon registration - prevents race conditions""" + """Initialize presets with simplified, reliable synchronization""" try: import bpy if not hasattr(bpy.context, 'scene') or not bpy.context.scene: @@ -509,113 +509,157 @@ def initialize_presets(): if not props: return - # Check if migration is needed first - migration_result = migrate_presets_if_needed() + print(f"[TTG] Starting preset initialization...") - # Show migration notification if there were significant changes - if migration_result.get('migrated_count', 0) > 0 or migration_result.get('errors'): - def show_migration_notification(): - # Use a timer to show notification after UI is ready - try: - import bpy - if hasattr(bpy.context, 'window_manager'): - migrated_count = migration_result.get('migrated_count', 0) - errors = migration_result.get('errors', []) - - if migrated_count > 0: - print(f"[TTG] 🎉 MIGRATION SUCCESS: {migrated_count} presets migrated to persistent storage!") - print(f"[TTG] 🛡️ Your presets are now protected from addon updates.") - - if errors: - print(f"[TTG] ⚠️ WARNING: {len(errors)} presets had migration errors.") - print(f"[TTG] Use 'Migration Report' button in Presets section for details.") - except Exception as e: - print(f"[TTG] Could not show migration notification: {e}") - return None # Stop timer - - # Schedule notification - bpy.app.timers.register(show_migration_notification, first_interval=2.0) - - # Get existing preset names for optimization - existing_presets = {p.name: p for p in props.presets} + # Clear existing presets to avoid duplicates + props.presets.clear() - # Three-tier loading priority: Blend File > Persistent File > Legacy File + # Collect all presets from all sources + all_presets = {} - # 1. Load presets from blend file first (highest priority) - blend_presets = get_blend_file_presets() - updated_presets = set() - - for preset_name in blend_presets.keys(): - updated_presets.add(preset_name) - if preset_name in existing_presets: - # Update existing preset source if needed - if existing_presets[preset_name].source != 'BLEND_FILE': - existing_presets[preset_name].source = 'BLEND_FILE' - else: - # Add new preset - preset = props.presets.add() - preset.name = preset_name - preset.source = 'BLEND_FILE' + # 1. Load from blend file (highest priority - travels with file) + try: + blend_presets = get_blend_file_presets() + for name, data in blend_presets.items(): + all_presets[name] = { + 'data': data, + 'source': 'BLEND_FILE' + } + print(f"[TTG] Found {len(blend_presets)} presets in blend file") + except Exception as e: + print(f"[TTG] Error loading blend file presets: {e}") # 2. Load from persistent storage (survives addon updates) - persistent_dir = get_persistent_preset_path() - if os.path.exists(persistent_dir): - try: + try: + persistent_dir = get_persistent_preset_path() + if os.path.exists(persistent_dir): for filename in os.listdir(persistent_dir): if filename.endswith('.json'): preset_name = os.path.splitext(filename)[0] - # Only add if not already present from blend file - if preset_name not in blend_presets: - updated_presets.add(preset_name) - if preset_name in existing_presets: - # Update existing preset source if needed - if existing_presets[preset_name].source != 'PERSISTENT_FILE': - existing_presets[preset_name].source = 'PERSISTENT_FILE' - else: - # Add new preset - preset = props.presets.add() - preset.name = preset_name - preset.source = 'PERSISTENT_FILE' - print(f"[TTG] Loaded persistent presets from {persistent_dir}") - except OSError as e: - print(f"[TTG] Error loading persistent presets during initialization: {e}") + # Only add if not already in blend file (blend file has priority) + if preset_name not in all_presets: + try: + with open(os.path.join(persistent_dir, filename), 'r') as f: + preset_data = json.load(f) + all_presets[preset_name] = { + 'data': preset_data, + 'source': 'PERSISTENT_FILE' + } + except (json.JSONDecodeError, OSError) as e: + print(f"[TTG] Error reading persistent preset {preset_name}: {e}") + print(f"[TTG] Found {len([p for p in all_presets.values() if p['source'] == 'PERSISTENT_FILE'])} persistent presets") + except Exception as e: + print(f"[TTG] Error loading persistent presets: {e}") - # 3. Load legacy local presets as fallback (lowest priority) - legacy_dir = get_preset_path() - if os.path.exists(legacy_dir) and legacy_dir != persistent_dir: - try: + # 3. Load from legacy storage (backward compatibility) + try: + legacy_dir = get_preset_path() + if os.path.exists(legacy_dir) and legacy_dir != get_persistent_preset_path(): for filename in os.listdir(legacy_dir): if filename.endswith('.json'): preset_name = os.path.splitext(filename)[0] - # Only add if not already present from higher priority sources - if preset_name not in blend_presets and preset_name not in updated_presets: - updated_presets.add(preset_name) - if preset_name in existing_presets: - # Update existing preset source if needed - if existing_presets[preset_name].source != 'LOCAL_FILE': - existing_presets[preset_name].source = 'LOCAL_FILE' - else: - # Add new preset - preset = props.presets.add() - preset.name = preset_name - preset.source = 'LOCAL_FILE' - print(f"[TTG] Loaded legacy presets from {legacy_dir}") - except OSError as e: - print(f"[TTG] Error loading legacy presets during initialization: {e}") + # Only add if not already found in higher priority sources + if preset_name not in all_presets: + try: + with open(os.path.join(legacy_dir, filename), 'r') as f: + preset_data = json.load(f) + all_presets[preset_name] = { + 'data': preset_data, + 'source': 'LOCAL_FILE' + } + except (json.JSONDecodeError, OSError) as e: + print(f"[TTG] Error reading legacy preset {preset_name}: {e}") + print(f"[TTG] Found {len([p for p in all_presets.values() if p['source'] == 'LOCAL_FILE'])} legacy presets") + except Exception as e: + print(f"[TTG] Error loading legacy presets: {e}") - # Remove presets that no longer exist - for i in range(len(props.presets) - 1, -1, -1): - if props.presets[i].name not in updated_presets: - props.presets.remove(i) - - # Count presets by source for reporting + # Add all presets to UI list + for preset_name, preset_info in all_presets.items(): + preset = props.presets.add() + preset.name = preset_name + preset.source = preset_info['source'] + + # Report results blend_count = len([p for p in props.presets if p.source == 'BLEND_FILE']) persistent_count = len([p for p in props.presets if p.source == 'PERSISTENT_FILE']) legacy_count = len([p for p in props.presets if p.source == 'LOCAL_FILE']) - print(f"[TTG] Initialized {len(props.presets)} presets ({blend_count} blend file, {persistent_count} persistent, {legacy_count} legacy)") + print(f"[TTG] ✅ Initialized {len(props.presets)} presets ({blend_count} blend file, {persistent_count} persistent, {legacy_count} legacy)") + except Exception as e: - print(f"[TTG] Error in initialize_presets: {e}") + print(f"[TTG] Critical error in initialize_presets: {e}") + import traceback + traceback.print_exc() +def refresh_presets(): + """Refresh the preset list by rescanning files (legacy function)""" + import bpy + refresh_presets_sync() + + # Force UI update + try: + if bpy.context.area: + bpy.context.area.tag_redraw() + except: + pass + +def refresh_presets_sync(): + """Centralized, synchronous preset refresh - guarantees completion""" + try: + import bpy + + print("[TTG] 🔄 Synchronizing presets...") + + # Ensure we have valid context + if not hasattr(bpy.context, 'scene') or not bpy.context.scene: + print("[TTG] ⚠️ No valid scene context for preset refresh") + return False + + props = bpy.context.scene.text_texture_props + if not props: + print("[TTG] ⚠️ No text_texture_props found") + return False + + # Run the simplified initialization + initialize_presets() + + # Validate that presets are actually loaded + preset_count = len(props.presets) + if preset_count == 0: + print("[TTG] ⚠️ Warning: No presets found after refresh") + else: + print(f"[TTG] ✅ Preset sync complete - {preset_count} presets available") + + return True + + except Exception as e: + print(f"[TTG] ❌ Error in refresh_presets_sync: {e}") + import traceback + traceback.print_exc() + return False + +def ensure_presets_available(): + """Defensive programming - ensure presets are loaded before UI operations""" + try: + import bpy + + if not hasattr(bpy.context, 'scene') or not bpy.context.scene: + return False + + props = bpy.context.scene.text_texture_props + if not props: + return False + + # If no presets loaded, try to load them + if len(props.presets) == 0: + print("[TTG] 🛡️ No presets available - attempting to load...") + return refresh_presets_sync() + + return True + + except Exception as e: + print(f"[TTG] Error in ensure_presets_available: {e}") + return False + # ============================================================================ # PREVIEW GENERATION FUNCTIONS @@ -4903,6 +4947,11 @@ class TEXT_TEXTURE_OT_load_preset(Operator): def execute(self, context): props = context.scene.text_texture_props + # DEFENSIVE PROGRAMMING: Ensure presets are available before loading + if not ensure_presets_available(): + self.report({'ERROR'}, "Failed to ensure presets are loaded - cannot load preset") + return {'CANCELLED'} + # DETAILED LOGGING: Current shader properties before loading print(f"[PRESET LOAD DEBUG] ==================== LOADING PRESET: '{self.preset_name}' ====================") print(f"[PRESET LOAD DEBUG] Current shader properties BEFORE loading:") @@ -5240,16 +5289,21 @@ class TEXT_TEXTURE_OT_refresh_presets(Operator): def execute(self, context): try: - # Reload presets - initialize_presets() + # Use the reliable synchronous refresh system + success = refresh_presets_sync() - props = context.scene.text_texture_props - blend_presets = get_blend_file_presets() - total_presets = len(props.presets) - blend_count = len(blend_presets) - local_count = total_presets - blend_count - - self.report({'INFO'}, f"✅ Refreshed {total_presets} presets ({blend_count} from blend file, {local_count} local)") + if success: + props = context.scene.text_texture_props + total_presets = len(props.presets) + + # Count by source for detailed reporting + blend_count = len([p for p in props.presets if p.source == 'BLEND_FILE']) + persistent_count = len([p for p in props.presets if p.source == 'PERSISTENT_FILE']) + local_count = len([p for p in props.presets if p.source == 'LOCAL_FILE']) + + self.report({'INFO'}, f"✅ Refreshed {total_presets} presets ({blend_count} blend file, {persistent_count} persistent, {local_count} local)") + else: + self.report({'WARNING'}, "Preset refresh completed with issues - check console for details") # Force UI redraw for area in context.screen.areas: @@ -5259,6 +5313,8 @@ class TEXT_TEXTURE_OT_refresh_presets(Operator): except Exception as e: self.report({'ERROR'}, f"Failed to refresh presets: {str(e)}") + import traceback + traceback.print_exc() return {'CANCELLED'} class TEXT_TEXTURE_OT_add_overlay(Operator): @@ -5657,7 +5713,16 @@ class TEXT_TEXTURE_OT_duplicate_overlay(Operator): new_overlay.y_position = source_overlay.y_position new_overlay.scale = source_overlay.scale new_overlay.rotation = source_overlay.rotation - new_overlay.z_index = source_overlay.z_index + # Fix z-index assignment for duplicated overlays to ensure proper visibility + # For PREPEND/APPEND overlays, increment z_index to ensure duplicate appears above original + # This prevents the duplicate from being rendered behind the original overlay + if source_overlay.positioning_mode in ['PREPEND', 'APPEND']: + # Increment z_index to ensure proper layering order for duplicated overlay + new_overlay.z_index = min(source_overlay.z_index + 1, 10) # Cap at max z_index value + print(f"[DUPLICATE DEBUG] Fixed z_index for {source_overlay.positioning_mode} overlay: {source_overlay.z_index} -> {new_overlay.z_index}") + else: + # For ABSOLUTE overlays, keep same z_index (different positions so no overlap issue) + new_overlay.z_index = source_overlay.z_index new_overlay.positioning_mode = source_overlay.positioning_mode new_overlay.text_spacing = source_overlay.text_spacing new_overlay.image_spacing = source_overlay.image_spacing @@ -6841,14 +6906,25 @@ def register(): if hasattr(bpy.types, 'IMAGE_MT_image'): bpy.types.IMAGE_MT_image.append(menu_func_image) - # Initialize presets after registration to prevent race conditions - # Use a timer to ensure the scene is ready - def delayed_preset_init(): - initialize_presets() - return None # Stop timer + # Initialize presets using reliable synchronization - no timer chains needed + print("[TTG DEBUG] Registration completed - initializing presets with reliable system") - print("[TTG DEBUG] Registration completed - scheduling preset initialization") - bpy.app.timers.register(delayed_preset_init, first_interval=0.1) + # Use the new reliable initialization system + try: + # Perform migration check first + migration_result = migrate_presets_if_needed() + if migration_result['migrated_count'] > 0: + print(f"[TTG DEBUG] Migrated {migration_result['migrated_count']} presets during registration") + + # Initialize presets using the simplified system + initialize_presets() + print("[TTG DEBUG] Preset initialization completed successfully") + + except Exception as e: + print(f"[TTG DEBUG] Error during preset initialization: {e}") + # Don't fail registration if preset initialization fails + import traceback + traceback.print_exc() # Add handler to reload presets when blend file changes @bpy.app.handlers.persistent @@ -6856,12 +6932,18 @@ def register(): """Reload presets when a blend file is loaded""" try: print("[TTG] Reloading presets after file load...") - def delayed_reload(): - initialize_presets() - return None # Stop timer - bpy.app.timers.register(delayed_reload, first_interval=0.1) + + # Use reliable synchronous refresh instead of timer + success = refresh_presets_sync() + if success: + print("[TTG] Presets reloaded successfully after file load") + else: + print("[TTG] Warning: Preset reload failed after file load") + except Exception as e: print(f"[TTG] Error reloading presets: {e}") + import traceback + traceback.print_exc() # Register the file load handler if on_file_load not in bpy.app.handlers.load_post: