Files
Text-Texture-Generator-for-…/src/operators/utility_ops.py
2025-10-13 13:35:52 +02:00

668 lines
24 KiB
Python

"""
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',
]