This commit is contained in:
2025-09-10 14:36:36 +03:00
parent 42267e7d68
commit 32fc626caf
2 changed files with 193 additions and 111 deletions

Binary file not shown.

View File

@@ -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', [])
# Clear existing presets to avoid duplicates
props.presets.clear()
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.")
# Collect all presets from all sources
all_presets = {}
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}
# Three-tier loading priority: Blend File > Persistent File > Legacy File
# 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)
# 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']
# Count presets by source for reporting
# 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
if success:
props = context.scene.text_texture_props
total_presets = len(props.presets)
self.report({'INFO'}, f"✅ Refreshed {total_presets} presets ({blend_count} from blend file, {local_count} local)")
# 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: