""" Text Texture Generator - Utility Operators Handles preview refresh, font refresh, UI operations, overlay management, and positioning utilities. """ import bpy from bpy.types import Operator from bpy.props import StringProperty, IntProperty, EnumProperty import os # Import functions from parent module try: pass # First, let's see what's actually available in ui.preview from text_texture_generator import ui # Now try the actual import that's failing from .generation_engine import generate_preview from ..utils.system import get_system_fonts, get_font_enum_items from .storage_system import refresh_presets_sync, ensure_presets_available from ..utils.constants import sync_margin_values except ImportError as e: pass # Let's check if it's available in core.generation_engine try: pass from .generation_engine import generate_preview from ..utils.system import get_system_fonts, get_font_enum_items from .storage_system import refresh_presets_sync, ensure_presets_available from ..utils.constants import sync_margin_values except ImportError as fallback_error: pass # Fallback imports for development/testing def generate_preview(context): pass print("Fallback generate_preview called") return None def get_system_fonts(): pass return {} def get_font_enum_items(): pass return [("default", "Default Font", "Use system default font", 0)] def refresh_presets_sync(): pass return True def ensure_presets_available(): pass return True def sync_margin_values(props, changed_margin): pass pass class TEXT_TEXTURE_OT_refresh_preview(Operator): pass """Force refresh the preview""" bl_idname = "text_texture.refresh_preview" bl_label = "Refresh Preview" bl_description = "Force refresh the preview image" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): pass props = context.scene.text_texture_props # Clear existing preview first props.preview_image = None # Generate new preview result = generate_preview(context) if result: pass self.report({'INFO'}, f"Preview refreshed at {props.preview_size}px") else: pass self.report({'ERROR'}, "Failed to generate preview - check console for details") # Force UI update for area in context.screen.areas: pass area.tag_redraw() return {'FINISHED'} class TEXT_TEXTURE_OT_view_preview_zoom(Operator): pass """Open preview in new Image Editor window with zoom support""" bl_idname = "text_texture.view_preview_zoom" bl_label = "Enable Zoom" bl_description = "Open preview in new Image Editor window with zoom and pan controls" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): pass props = context.scene.text_texture_props # Generate preview image when button is clicked result = generate_preview(context) if not result: pass self.report({'ERROR'}, "Failed to generate preview") return {'CANCELLED'} if not props.preview_image: pass self.report({'ERROR'}, "No preview image to display") return {'CANCELLED'} # Open a new window with Image Editor try: pass # Create a new window bpy.ops.screen.userpref_show('INVOKE_DEFAULT') # Wait a moment and then change the newly opened window to Image Editor def setup_image_window(): pass # Find the newest window (should be the one we just opened) newest_window = None for window in bpy.context.window_manager.windows: pass if window.screen.name.startswith("temp"): pass newest_window = window break if not newest_window: pass # Fallback: use any non-main window for window in bpy.context.window_manager.windows: pass if window != bpy.context.window: pass newest_window = window break if newest_window: pass # Set the area type to Image Editor for area in newest_window.screen.areas: pass if area.type == 'PREFERENCES': pass area.type = 'IMAGE_EDITOR' # Set the preview image for space in area.spaces: pass if space.type == 'IMAGE_EDITOR': pass space.image = props.preview_image # Set up proper viewing override = { 'window': newest_window, 'screen': newest_window.screen, 'area': area, 'region': area.regions[0] if area.regions else None } with bpy.context.temp_override(**override): try: pass bpy.ops.image.view_all(fit_view=True) except Exception as e: pass break break self.report({'INFO'}, "✅ NEW WINDOW OPENED! Use mouse wheel to zoom, middle-mouse to pan") else: pass self.report({'ERROR'}, "Failed to setup new window") return None # Stop timer # Use a timer to set up the window after it opens bpy.app.timers.register(setup_image_window, first_interval=0.1) except Exception as e: pass # Fallback to old behavior if new window creation fails self.report({'WARNING'}, "New window failed, trying area split instead") # Find any area to split for area in context.screen.areas: pass if area.type in ['NODE_EDITOR', 'VIEW_3D', 'PROPERTIES']: pass # Split horizontally to create a new area with context.temp_override(area=area): bpy.ops.screen.area_split(direction='HORIZONTAL', factor=0.6) # Find the newly created area for new_area in context.screen.areas: pass if new_area != area and new_area.y != area.y: pass new_area.type = 'IMAGE_EDITOR' # Set the image in the Image Editor for space in new_area.spaces: pass if space.type == 'IMAGE_EDITOR': pass space.image = props.preview_image break # Tag for redraw and fit to view new_area.tag_redraw() override = context.copy() override['area'] = new_area override['region'] = new_area.regions[0] with context.temp_override(**override): try: pass bpy.ops.image.view_all(fit_view=True) except Exception as e: pass self.report({'INFO'}, "✅ ZOOM ENABLED! Use mouse wheel to zoom, middle-mouse to pan") return {'FINISHED'} break self.report({'ERROR'}, "Failed to create Image Editor") return {'CANCELLED'} return {'FINISHED'} class TEXT_TEXTURE_OT_open_panel(Operator): pass """Open Text Texture Generator panel""" bl_idname = "text_texture.open_panel" bl_label = "Open Text Texture Panel" bl_description = "Open the Text Texture Generator panel in the sidebar" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): pass # Ensure the sidebar is visible for area in context.screen.areas: pass if area.type == 'VIEW_3D': pass for space in area.spaces: pass if space.type == 'VIEW_3D': pass space.show_region_ui = True # Switch to Tool tab area.tag_redraw() self.report({'INFO'}, "Text Texture panel opened in 3D View sidebar (press N if not visible)") return {'FINISHED'} elif area.type == 'NODE_EDITOR': for space in area.spaces: pass if space.type == 'NODE_EDITOR': pass space.show_region_ui = True area.tag_redraw() self.report({'INFO'}, "Text Texture panel opened in Shader Editor sidebar") return {'FINISHED'} self.report({'WARNING'}, "Please open a 3D View or Shader Editor first") return {'CANCELLED'} class TEXT_TEXTURE_OT_refresh_fonts(Operator): pass """Refresh fonts""" bl_idname = "text_texture.refresh_fonts" bl_label = "Refresh Fonts" bl_description = "Refresh font list" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): pass if hasattr(get_font_enum_items, 'cached_fonts'): pass del get_font_enum_items.cached_fonts get_font_enum_items.cached_fonts = get_system_fonts() self.report({'INFO'}, f"Found {len(get_font_enum_items.cached_fonts)} fonts") return {'FINISHED'} class TEXT_TEXTURE_OT_refresh_presets(Operator): pass """Refresh presets from blend file and local storage""" bl_idname = "text_texture.refresh_presets" bl_label = "Refresh Presets" bl_description = "Reload presets from blend file and local storage" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): pass try: pass # Use the reliable synchronous refresh system success = refresh_presets_sync() if success: pass 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: pass self.report({'WARNING'}, "Preset refresh completed with issues - check console for details") # Force UI redraw for area in context.screen.areas: pass area.tag_redraw() return {'FINISHED'} except Exception as e: pass self.report({'ERROR'}, f"Failed to refresh presets: {str(e)}") import traceback traceback.print_exc() return {'CANCELLED'} class TEXT_TEXTURE_OT_add_overlay(Operator): pass """Add new composition component""" bl_idname = "text_texture.add_overlay" bl_label = "Add Component" bl_description = "Add new composition component" bl_options = {'REGISTER', 'UNDO'} placement_mode: EnumProperty( name="Placement Mode", items=[ ('ABSOLUTE', "Absolute", ""), ('PREPEND', "Prepend", ""), ('APPEND', "Append", "") ], default='ABSOLUTE' ) def execute(self, context): pass props = context.scene.text_texture_props overlay = props.image_overlays.add() overlay.name = f"Component {len(props.image_overlays)}" overlay.placement_mode = self.placement_mode overlay.component_type = 'TEXT' if self.placement_mode in {'PREPEND', 'APPEND'} else 'IMAGE' props.active_overlay_index = len(props.image_overlays) - 1 # Force UI redraw for area in context.screen.areas: pass area.tag_redraw() self.report({'INFO'}, f"Added component: {overlay.name}") return {'FINISHED'} class TEXT_TEXTURE_OT_remove_overlay(Operator): pass """Remove active image overlay""" bl_idname = "text_texture.remove_overlay" bl_label = "Remove Overlay" bl_description = "Remove active image overlay" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): pass props = context.scene.text_texture_props if props.image_overlays and props.active_overlay_index < len(props.image_overlays): pass overlay_name = props.image_overlays[props.active_overlay_index].name props.image_overlays.remove(props.active_overlay_index) # Update active index if props.active_overlay_index >= len(props.image_overlays) and props.image_overlays: pass props.active_overlay_index = len(props.image_overlays) - 1 # Force UI redraw for area in context.screen.areas: pass area.tag_redraw() self.report({'INFO'}, f"Removed overlay: {overlay_name}") else: pass self.report({'WARNING'}, "No overlay to remove") return {'FINISHED'} class TEXT_TEXTURE_OT_set_anchor_point(Operator): pass """Set anchor point for text positioning""" bl_idname = "text_texture.set_anchor_point" bl_label = "Set Anchor Point" bl_description = "Set text anchor position" bl_options = {'REGISTER', 'UNDO'} anchor_point: StringProperty() def execute(self, context): pass props = context.scene.text_texture_props props.anchor_point = self.anchor_point return {'FINISHED'} class TEXT_TEXTURE_OT_sync_margins(Operator): pass """Sync margin values when linked""" bl_idname = "text_texture.sync_margins" bl_label = "Sync Margins" bl_description = "Synchronize margin values" bl_options = {'REGISTER', 'UNDO'} margin_type: StringProperty() def execute(self, context): pass props = context.scene.text_texture_props sync_margin_values(props, self.margin_type) return {'FINISHED'} class TEXT_TEXTURE_OT_duplicate_overlay(Operator): pass """Duplicate a composition component""" bl_idname = "text_texture.duplicate_overlay" bl_label = "Duplicate Component" bl_description = "Duplicate this composition component" bl_options = {'REGISTER', 'UNDO'} overlay_index: IntProperty() def execute(self, context): pass props = context.scene.text_texture_props if 0 <= self.overlay_index < len(props.image_overlays): pass source_overlay = props.image_overlays[self.overlay_index] # Debug logging new_overlay = props.image_overlays.add() # Copy all properties with explicit verification new_overlay.name = f"{source_overlay.name} Copy" new_overlay.component_type = source_overlay.component_type new_overlay.image_path = source_overlay.image_path new_overlay.text_content = source_overlay.text_content new_overlay.x_position = source_overlay.x_position new_overlay.y_position = source_overlay.y_position new_overlay.scale = source_overlay.scale new_overlay.rotation = source_overlay.rotation # 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.placement_mode in ['PREPEND', 'APPEND']: pass # 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 else: pass # For ABSOLUTE overlays, keep same z_index (different positions so no overlap issue) new_overlay.z_index = source_overlay.z_index new_overlay.placement_mode = source_overlay.placement_mode new_overlay.text_spacing = source_overlay.text_spacing new_overlay.image_spacing = source_overlay.image_spacing new_overlay.enabled = source_overlay.enabled # Verify the copy was successful # Move to position after source new_index = len(props.image_overlays) - 1 target_index = min(self.overlay_index + 1, new_index) if new_index != target_index: pass props.image_overlays.move(new_index, target_index) props.active_overlay_index = target_index # Clear any cached images to force reload # Set the updating flag to prevent recursive updates props.is_updating = True try: pass # Force immediate preview regeneration preview_result = generate_preview(context) if preview_result: pass else: pass # Force UI redraw for area in context.screen.areas: pass area.tag_redraw() except Exception as e: pass import traceback traceback.print_exc() finally: props.is_updating = False self.report({'INFO'}, f"✅ Duplicated component: {source_overlay.name}") else: pass self.report({'ERROR'}, "Invalid overlay index") return {'CANCELLED'} return {'FINISHED'} class TEXT_TEXTURE_OT_delete_overlay(Operator): pass """Delete a composition component""" bl_idname = "text_texture.delete_overlay" bl_label = "Delete Component" bl_description = "Delete this composition component" bl_options = {'REGISTER', 'UNDO'} overlay_index: IntProperty() def execute(self, context): pass props = context.scene.text_texture_props if 0 <= self.overlay_index < len(props.image_overlays): pass overlay_name = props.image_overlays[self.overlay_index].name props.image_overlays.remove(self.overlay_index) # Update active index if props.active_overlay_index >= len(props.image_overlays) and props.image_overlays: pass props.active_overlay_index = len(props.image_overlays) - 1 elif not props.image_overlays: props.active_overlay_index = 0 # Force preview update after deletion try: pass generate_preview(context) except Exception as e: pass # Force UI redraw for area in context.screen.areas: pass area.tag_redraw() self.report({'INFO'}, f"Deleted component: {overlay_name}") else: pass self.report({'ERROR'}, "Invalid overlay index") return {'CANCELLED'} return {'FINISHED'} class TEXT_TEXTURE_OT_move_overlay(Operator): pass """Move a composition component within its placement group""" bl_idname = "text_texture.move_overlay" bl_label = "Move Component" bl_description = "Move component up or down within its placement group" bl_options = {'REGISTER', 'UNDO'} overlay_index: IntProperty() direction: EnumProperty( items=[ ('UP', 'Up', 'Move overlay up in list'), ('DOWN', 'Down', 'Move overlay down in list') ] ) def execute(self, context): pass props = context.scene.text_texture_props if 0 <= self.overlay_index < len(props.image_overlays): pass current_index = self.overlay_index new_index = current_index target_mode = props.image_overlays[current_index].placement_mode step = -1 if self.direction == 'UP' else 1 probe = current_index + step while 0 <= probe < len(props.image_overlays): if props.image_overlays[probe].placement_mode == target_mode: new_index = probe break probe += step if new_index != current_index: pass overlay_name = props.image_overlays[current_index].name props.image_overlays.move(current_index, new_index) props.active_overlay_index = new_index # Force preview update after moving (order affects rendering) try: pass generate_preview(context) except Exception as e: pass self.report({'INFO'}, f"Moved component {self.direction.lower()}") else: pass self.report({'INFO'}, f"Component is already at {self.direction.lower()} edge") else: pass self.report({'ERROR'}, "Invalid overlay index") return {'CANCELLED'} return {'FINISHED'} # Export all operator classes for wildcard imports __all__ = [ 'TEXT_TEXTURE_OT_refresh_preview', 'TEXT_TEXTURE_OT_view_preview_zoom', 'TEXT_TEXTURE_OT_open_panel', 'TEXT_TEXTURE_OT_refresh_fonts', 'TEXT_TEXTURE_OT_refresh_presets', 'TEXT_TEXTURE_OT_add_overlay', 'TEXT_TEXTURE_OT_remove_overlay', 'TEXT_TEXTURE_OT_set_anchor_point', 'TEXT_TEXTURE_OT_sync_margins', 'TEXT_TEXTURE_OT_duplicate_overlay', 'TEXT_TEXTURE_OT_delete_overlay', 'TEXT_TEXTURE_OT_move_overlay', ]