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 return False
def initialize_presets(): def initialize_presets():
"""Initialize presets during addon registration - prevents race conditions""" """Initialize presets with simplified, reliable synchronization"""
try: try:
import bpy import bpy
if not hasattr(bpy.context, 'scene') or not bpy.context.scene: if not hasattr(bpy.context, 'scene') or not bpy.context.scene:
@@ -509,113 +509,157 @@ def initialize_presets():
if not props: if not props:
return return
# Check if migration is needed first print(f"[TTG] Starting preset initialization...")
migration_result = migrate_presets_if_needed()
# Show migration notification if there were significant changes # Clear existing presets to avoid duplicates
if migration_result.get('migrated_count', 0) > 0 or migration_result.get('errors'): props.presets.clear()
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: # Collect all presets from all sources
print(f"[TTG] 🎉 MIGRATION SUCCESS: {migrated_count} presets migrated to persistent storage!") all_presets = {}
print(f"[TTG] 🛡️ Your presets are now protected from addon updates.")
if errors: # 1. Load from blend file (highest priority - travels with file)
print(f"[TTG] ⚠️ WARNING: {len(errors)} presets had migration errors.") try:
print(f"[TTG] Use 'Migration Report' button in Presets section for details.") blend_presets = get_blend_file_presets()
except Exception as e: for name, data in blend_presets.items():
print(f"[TTG] Could not show migration notification: {e}") all_presets[name] = {
return None # Stop timer 'data': data,
'source': 'BLEND_FILE'
# Schedule notification }
bpy.app.timers.register(show_migration_notification, first_interval=2.0) print(f"[TTG] Found {len(blend_presets)} presets in blend file")
except Exception as e:
# Get existing preset names for optimization print(f"[TTG] Error loading blend file presets: {e}")
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'
# 2. Load from persistent storage (survives addon updates) # 2. Load from persistent storage (survives addon updates)
persistent_dir = get_persistent_preset_path() try:
if os.path.exists(persistent_dir): persistent_dir = get_persistent_preset_path()
try: if os.path.exists(persistent_dir):
for filename in os.listdir(persistent_dir): for filename in os.listdir(persistent_dir):
if filename.endswith('.json'): if filename.endswith('.json'):
preset_name = os.path.splitext(filename)[0] preset_name = os.path.splitext(filename)[0]
# Only add if not already present from blend file # Only add if not already in blend file (blend file has priority)
if preset_name not in blend_presets: if preset_name not in all_presets:
updated_presets.add(preset_name) try:
if preset_name in existing_presets: with open(os.path.join(persistent_dir, filename), 'r') as f:
# Update existing preset source if needed preset_data = json.load(f)
if existing_presets[preset_name].source != 'PERSISTENT_FILE': all_presets[preset_name] = {
existing_presets[preset_name].source = 'PERSISTENT_FILE' 'data': preset_data,
else: 'source': 'PERSISTENT_FILE'
# Add new preset }
preset = props.presets.add() except (json.JSONDecodeError, OSError) as e:
preset.name = preset_name print(f"[TTG] Error reading persistent preset {preset_name}: {e}")
preset.source = 'PERSISTENT_FILE' print(f"[TTG] Found {len([p for p in all_presets.values() if p['source'] == 'PERSISTENT_FILE'])} persistent presets")
print(f"[TTG] Loaded persistent presets from {persistent_dir}") except Exception as e:
except OSError as e: print(f"[TTG] Error loading persistent presets: {e}")
print(f"[TTG] Error loading persistent presets during initialization: {e}")
# 3. Load legacy local presets as fallback (lowest priority) # 3. Load from legacy storage (backward compatibility)
legacy_dir = get_preset_path() try:
if os.path.exists(legacy_dir) and legacy_dir != persistent_dir: legacy_dir = get_preset_path()
try: if os.path.exists(legacy_dir) and legacy_dir != get_persistent_preset_path():
for filename in os.listdir(legacy_dir): for filename in os.listdir(legacy_dir):
if filename.endswith('.json'): if filename.endswith('.json'):
preset_name = os.path.splitext(filename)[0] preset_name = os.path.splitext(filename)[0]
# Only add if not already present from higher priority sources # Only add if not already found in higher priority sources
if preset_name not in blend_presets and preset_name not in updated_presets: if preset_name not in all_presets:
updated_presets.add(preset_name) try:
if preset_name in existing_presets: with open(os.path.join(legacy_dir, filename), 'r') as f:
# Update existing preset source if needed preset_data = json.load(f)
if existing_presets[preset_name].source != 'LOCAL_FILE': all_presets[preset_name] = {
existing_presets[preset_name].source = 'LOCAL_FILE' 'data': preset_data,
else: 'source': 'LOCAL_FILE'
# Add new preset }
preset = props.presets.add() except (json.JSONDecodeError, OSError) as e:
preset.name = preset_name print(f"[TTG] Error reading legacy preset {preset_name}: {e}")
preset.source = 'LOCAL_FILE' print(f"[TTG] Found {len([p for p in all_presets.values() if p['source'] == 'LOCAL_FILE'])} legacy presets")
print(f"[TTG] Loaded legacy presets from {legacy_dir}") except Exception as e:
except OSError as e: print(f"[TTG] Error loading legacy presets: {e}")
print(f"[TTG] Error loading legacy presets during initialization: {e}")
# Remove presets that no longer exist # Add all presets to UI list
for i in range(len(props.presets) - 1, -1, -1): for preset_name, preset_info in all_presets.items():
if props.presets[i].name not in updated_presets: preset = props.presets.add()
props.presets.remove(i) 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']) 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']) 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']) 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: 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 # PREVIEW GENERATION FUNCTIONS
@@ -4903,6 +4947,11 @@ class TEXT_TEXTURE_OT_load_preset(Operator):
def execute(self, context): def execute(self, context):
props = context.scene.text_texture_props 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 # DETAILED LOGGING: Current shader properties before loading
print(f"[PRESET LOAD DEBUG] ==================== LOADING PRESET: '{self.preset_name}' ====================") print(f"[PRESET LOAD DEBUG] ==================== LOADING PRESET: '{self.preset_name}' ====================")
print(f"[PRESET LOAD DEBUG] Current shader properties BEFORE loading:") 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): def execute(self, context):
try: try:
# Reload presets # Use the reliable synchronous refresh system
initialize_presets() success = refresh_presets_sync()
props = context.scene.text_texture_props if success:
blend_presets = get_blend_file_presets() props = context.scene.text_texture_props
total_presets = len(props.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)") # 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 # Force UI redraw
for area in context.screen.areas: for area in context.screen.areas:
@@ -5259,6 +5313,8 @@ class TEXT_TEXTURE_OT_refresh_presets(Operator):
except Exception as e: except Exception as e:
self.report({'ERROR'}, f"Failed to refresh presets: {str(e)}") self.report({'ERROR'}, f"Failed to refresh presets: {str(e)}")
import traceback
traceback.print_exc()
return {'CANCELLED'} return {'CANCELLED'}
class TEXT_TEXTURE_OT_add_overlay(Operator): 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.y_position = source_overlay.y_position
new_overlay.scale = source_overlay.scale new_overlay.scale = source_overlay.scale
new_overlay.rotation = source_overlay.rotation 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.positioning_mode = source_overlay.positioning_mode
new_overlay.text_spacing = source_overlay.text_spacing new_overlay.text_spacing = source_overlay.text_spacing
new_overlay.image_spacing = source_overlay.image_spacing new_overlay.image_spacing = source_overlay.image_spacing
@@ -6841,14 +6906,25 @@ def register():
if hasattr(bpy.types, 'IMAGE_MT_image'): if hasattr(bpy.types, 'IMAGE_MT_image'):
bpy.types.IMAGE_MT_image.append(menu_func_image) bpy.types.IMAGE_MT_image.append(menu_func_image)
# Initialize presets after registration to prevent race conditions # Initialize presets using reliable synchronization - no timer chains needed
# Use a timer to ensure the scene is ready print("[TTG DEBUG] Registration completed - initializing presets with reliable system")
def delayed_preset_init():
initialize_presets()
return None # Stop timer
print("[TTG DEBUG] Registration completed - scheduling preset initialization") # Use the new reliable initialization system
bpy.app.timers.register(delayed_preset_init, first_interval=0.1) 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 # Add handler to reload presets when blend file changes
@bpy.app.handlers.persistent @bpy.app.handlers.persistent
@@ -6856,12 +6932,18 @@ def register():
"""Reload presets when a blend file is loaded""" """Reload presets when a blend file is loaded"""
try: try:
print("[TTG] Reloading presets after file load...") print("[TTG] Reloading presets after file load...")
def delayed_reload():
initialize_presets() # Use reliable synchronous refresh instead of timer
return None # Stop timer success = refresh_presets_sync()
bpy.app.timers.register(delayed_reload, first_interval=0.1) if success:
print("[TTG] Presets reloaded successfully after file load")
else:
print("[TTG] Warning: Preset reload failed after file load")
except Exception as e: except Exception as e:
print(f"[TTG] Error reloading presets: {e}") print(f"[TTG] Error reloading presets: {e}")
import traceback
traceback.print_exc()
# Register the file load handler # Register the file load handler
if on_file_load not in bpy.app.handlers.load_post: if on_file_load not in bpy.app.handlers.load_post: