""" Text Texture Generator - UI Panels Module Contains all UI panel classes for different editor contexts. """ import bpy from bpy.types import Panel from ..core.generation_engine import generate_preview from ..utils.constants import ( is_free_version, get_version_badge_text, get_version_badge_icon, get_upgrade_hint_text, should_show_feature_lock, get_feature_availability_text, get_version_info_details, has_image_overlays, has_basic_utilities, has_presets, has_normal_maps, has_batch_processing, has_advanced_utilities, has_advanced_styling ) # ============================================================================ # SHARED UI COMPONENTS # ============================================================================ def is_operator_available(operator_id): """Check if an operator is available/registered in Blender""" try: import bpy # Try to get operator class - if it doesn't exist, this will fail return hasattr(bpy.ops, operator_id.split('.')[0]) and hasattr(getattr(bpy.ops, operator_id.split('.')[0]), operator_id.split('.')[1]) except: return False def create_premium_lock_function(feature_name=None): """Create a standard premium lock function for a specific feature Args: feature_name: Optional feature name for context-specific hints Returns: A function that can be used as fallback_content_func in safe_operator_call """ def show_premium_lock(fallback_layout): fallback_layout.enabled = False fallback_layout.label(text="🔒 Pro Feature", icon='LOCKED') return show_premium_lock def safe_operator_call(layout, operator_id, fallback_content_func=None, **kwargs): """Safely call an operator, showing premium content if operator doesn't exist Args: layout: Blender UI layout object operator_id: String ID of operator (e.g., 'text_texture.add_overlay') fallback_content_func: Function to call if operator doesn't exist **kwargs: Arguments to pass to layout.operator() Returns: Operator reference if successful, None if operator doesn't exist """ if is_operator_available(operator_id): return layout.operator(operator_id, **kwargs) else: # Operator doesn't exist - show premium lock content instead if fallback_content_func: fallback_content_func(layout) else: # Default fallback - show generic premium lock lock_row = layout.row() lock_row.enabled = False lock_row.label(text="🔒 Pro Feature", icon='LOCKED') return None def draw_collapsible_header(layout, prop_name, text, icon='TRIA_DOWN'): pass """Draw a collapsible section header with expand/collapse arrow""" props = bpy.context.scene.text_texture_props expanded = getattr(props, prop_name) box = layout.box() header_row = box.row() # Draw arrow icon and toggle expanded state arrow_icon = 'TRIA_DOWN' if expanded else 'TRIA_RIGHT' header_row.prop(props, prop_name, text="", icon=arrow_icon, emboss=False) header_row.label(text=text, icon=icon) return box if expanded else None def draw_version_badge(layout): pass """Draw version badge in panel header""" badge_row = layout.row(align=True) badge_row.scale_x = 0.7 badge_row.scale_y = 0.8 badge_row.alignment = 'RIGHT' # Simple label-based badge (more reliable than operator) badge_label = badge_row.label(text=get_version_badge_text()) if is_free_version(): pass badge_row.label(text="", icon='GIFT') else: pass badge_row.label(text="", icon='STAR') def draw_feature_lock_indicator(layout, feature_name, show_hint=True): """Draw feature lock indicator for premium features""" if should_show_feature_lock(feature_name): # Create a box for better visual separation lock_box = layout.box() # Main lock indicator with proper vertical centering using row alignment lock_row = lock_box.row(align=True) lock_row.scale_y = 1.2 # Slightly larger for better visual balance lock_row.alignment = 'CENTER' # Use a single row with proper alignment instead of split for better centering # Add icon with proper spacing icon_col = lock_row.column(align=True) icon_col.scale_x = 0.8 icon_col.alignment = 'CENTER' icon_col.label(text="", icon='LOCKED') # Add text with proper alignment text_col = lock_row.column(align=True) text_col.alignment = 'CENTER' text_col.label(text="🔒 Pro Feature") if show_hint: hint_row = lock_box.row() hint_row.scale_y = 0.8 hint_row.alignment = 'CENTER' hint_row.label(text=get_upgrade_hint_text(feature_name)) def draw_version_info_section(layout, props): pass """Draw version information display section""" info_box = layout.box() info_box.label(text="Version Information", icon='INFO') # Version details version_info = get_version_info_details() col = info_box.column(align=True) col.scale_y = 0.8 # Basic version info col.label(text=f"Version: {version_info['version_type']}") col.label(text=get_feature_availability_text()) # Current limits limits_col = col.column(align=True) limits_col.separator() limits_col.label(text="Current Limits:") limits_col.label(text=f"• Max texture size: {version_info['max_texture_size']}px") limits_col.label(text=f"• Active features: {version_info['features_enabled']}") # Feature availability status col.separator() availability_col = col.column(align=True) availability_col.label(text="Feature Status:") if has_presets(): pass availability_col.label(text="• Presets: ✓ Available", icon='CHECKMARK') else: pass availability_col.label(text="• Presets: 🔒 Pro Only", icon='LOCKED') if has_image_overlays(): pass availability_col.label(text="• Image Overlays: ✓ Available", icon='CHECKMARK') else: pass availability_col.label(text="• Image Overlays: 🔒 Pro Only", icon='LOCKED') if has_basic_utilities(): pass availability_col.label(text="• Basic Tools: ✓ Available", icon='CHECKMARK') else: pass availability_col.label(text="• Basic Tools: 🔒 Pro Only", icon='LOCKED') if has_advanced_utilities(): pass availability_col.label(text="• Advanced Tools: ✓ Available", icon='CHECKMARK') else: pass availability_col.label(text="• Advanced Tools: 🔒 Pro Only", icon='LOCKED') # Upgrade hint for free version if version_info['upgrade_available']: pass col.separator() upgrade_box = info_box.box() upgrade_box.scale_y = 0.9 upgrade_row = upgrade_box.row() upgrade_row.alignment = 'CENTER' upgrade_row.label(text="💡 Upgrade to Pro for all features", icon='STAR') def draw_text_input_section(layout, props): pass """Draw the main text input field""" layout.prop(props, "text", text="") def draw_font_dropdown_section(layout, props): pass """Draw basic font dropdown for top level""" row = layout.row(align=True) row.prop(props, "font_path", text="") row.operator("text_texture.refresh_fonts", text="", icon='FILE_REFRESH') def draw_generate_button(layout): pass """Draw the main generate button""" row = layout.row() row.scale_y = 1.5 row.operator("text_texture.generate", text="Generate Text Texture", icon='IMAGE_DATA') def draw_image_overlays_section(layout, props): pass """Draw image overlays controls with collapsible individual overlays""" # Check if image overlays are available if not has_image_overlays(): pass draw_feature_lock_indicator(layout, 'image_overlays') return # Add overlay button - use safe operator call row = layout.row() safe_operator_call(row, "text_texture.add_overlay", lambda layout: draw_feature_lock_indicator(layout, 'image_overlays'), text="Add Image Overlay", icon='ADD') # Individual overlay controls if props.image_overlays: pass layout.separator() for i, overlay in enumerate(props.image_overlays): pass # Create collapsible box for each overlay box = layout.box() # Header row with controls header_row = box.row(align=True) # Enable/Disable checkbox with clear label header_row.prop(overlay, "enabled", text="", icon='CHECKBOX_HLT' if overlay.enabled else 'CHECKBOX_DEHLT') # Overlay name name_row = header_row.row() name_row.enabled = overlay.enabled # Gray out name if disabled name_row.prop(overlay, "name", text="") # Action buttons # Duplicate button - use safe operator call premium_lock = create_premium_lock_function() dup_op = safe_operator_call(header_row, "text_texture.duplicate_overlay", premium_lock, text="", icon='DUPLICATE') if dup_op: dup_op.overlay_index = i # Delete button - use safe operator call del_op = safe_operator_call(header_row, "text_texture.delete_overlay", premium_lock, text="", icon='TRASH') if del_op: del_op.overlay_index = i # Move buttons for reordering - use safe operator calls if i > 0: move_up_op = safe_operator_call(header_row, "text_texture.move_overlay", premium_lock, text="", icon='TRIA_UP') if move_up_op: move_up_op.overlay_index = i move_up_op.direction = 'UP' if i < len(props.image_overlays) - 1: move_down_op = safe_operator_call(header_row, "text_texture.move_overlay", premium_lock, text="", icon='TRIA_DOWN') if move_down_op: move_down_op.overlay_index = i move_down_op.direction = 'DOWN' # Show details section with enable/disable state details_col = box.column() details_col.enabled = overlay.enabled # Gray out all controls if disabled # Status indicator if not overlay.enabled: pass status_row = details_col.row() status_row.label(text="⚠️ Overlay Disabled", icon='INFO') details_col.separator() # Image file selection details_col.prop(overlay, "image_path", text="Image File") # Positioning mode details_col.prop(overlay, "positioning_mode", text="Position Mode") if overlay.positioning_mode == 'ABSOLUTE': pass # Position controls for absolute mode pos_row = details_col.row(align=True) pos_row.prop(overlay, "x_position", text="X Position") pos_row.prop(overlay, "y_position", text="Y Position") elif overlay.positioning_mode in ['APPEND', 'PREPEND']: # Spacing controls for append/prepend modes with percentage labels details_col.prop(overlay, "text_spacing", text="Text Spacing (%)") details_col.prop(overlay, "image_spacing", text="Image Spacing (%)") # Transform controls trans_row = details_col.row(align=True) trans_row.prop(overlay, "scale", text="Scale") trans_row.prop(overlay, "rotation", text="Rotation") trans_row.prop(overlay, "z_index", text="Z-Level") def draw_positioning_section(layout, props): pass """Draw positioning and alignment controls""" from ..utils.system import get_anchor_point_matrix # Dimensions moved here from top level layout.label(text="Dimensions:", icon='SETTINGS') row = layout.row(align=True) row.prop(props, "texture_width", text="Width") row.prop(props, "texture_height", text="Height") layout.separator() # Prepend/Append Text Section layout.label(text="Additional Text:", icon='TEXT') # Compact layout selector layout.prop(props, "prepend_append_layout", text="Layout") # Minimal prepend/append controls row = layout.row(align=True) row.prop(props, "prepend_text", text="Prepend") if props.prepend_text.strip(): pass row.prop(props, "prepend_margin", text="", slider=True) row = layout.row(align=True) row.prop(props, "append_text", text="Append") if props.append_text.strip(): pass row.prop(props, "append_margin", text="", slider=True) layout.separator() layout.prop(props, "position_mode", text="Mode") if props.position_mode == 'PRESET': pass layout.separator() layout.label(text="Anchor Point:", icon='ANCHOR_TOP') anchor_matrix = get_anchor_point_matrix() for row_idx, row in enumerate(anchor_matrix): pass grid_row = layout.row(align=True) grid_row.scale_y = 1.2 for col_idx, anchor in enumerate(row): pass if anchor == props.anchor_point: premium_lock = create_premium_lock_function() op = safe_operator_call(grid_row, "text_texture.set_anchor_point", premium_lock, text="●", depress=True) else: premium_lock = create_premium_lock_function() op = safe_operator_call(grid_row, "text_texture.set_anchor_point", premium_lock, text="○") if op: op.anchor_point = anchor # Margins layout.separator() margin_row = layout.row(align=True) margin_row.label(text="Margins:") margin_row.prop(props, "margins_linked", text="", icon='LINKED' if props.margins_linked else 'UNLINKED', toggle=True) margins_row = layout.row(align=True) margins_row.prop(props, "margin_x", text="X") if not props.margins_linked: pass margins_row.prop(props, "margin_y", text="Y") # Offsets layout.separator() layout.label(text="Fine-tune (pixels):") offset_row = layout.row(align=True) offset_row.prop(props, "offset_x", text="X") offset_row.prop(props, "offset_y", text="Y") else: # MANUAL mode layout.separator() layout.prop(props, "manual_position_unit", text="Unit") manual_row = layout.row(align=True) manual_row.prop(props, "manual_position_x", text="X") manual_row.prop(props, "manual_position_y", text="Y") layout.separator() layout.prop(props, "constrain_to_canvas") def draw_font_settings_section(layout, props): pass """Draw extended font settings""" # Font size moved here from top level layout.prop(props, "font_size") layout.separator() layout.label(text="Font Selection:") # Default font dropdown moved here from top level row = layout.row(align=True) row.prop(props, "font_path", text="") row.operator("text_texture.refresh_fonts", text="", icon='FILE_REFRESH') layout.separator() # Custom font controls moved here from top level layout.prop(props, "use_custom_font") if props.use_custom_font: pass layout.prop(props, "custom_font_path", text="") layout.separator() row = layout.row(align=True) row.prop(props, "padding") row.prop(props, "text_align") def draw_colors_section(layout, props): pass """Draw color controls for text and background""" layout.label(text="Text Color:", icon='COLOR') layout.prop(props, "text_color", text="") layout.separator() layout.label(text="Background Color:", icon='IMAGE_PLANE') layout.prop(props, "background_transparent") if not props.background_transparent: pass layout.prop(props, "background_color", text="") def draw_preview_options_section(layout, props): pass """Draw preview options (preview-specific settings only)""" layout.prop(props, "preview_size", text="Preview Size") layout.separator() layout.label(text="Preview Background Display:", icon='SCENE') layout.prop(props, "preview_bg_type", text="Background") if props.preview_bg_type == 'color': pass layout.prop(props, "preview_bg_color", text="Preview Display Color") elif props.preview_bg_type == 'gradient': layout.prop(props, "preview_bg_color", text="Color 1") layout.prop(props, "preview_bg_color2", text="Color 2") def draw_presets_section(layout, props): pass """Draw unified presets save/load controls""" # Check if presets are available, show lock if not if not has_presets(): pass draw_feature_lock_indicator(layout, 'presets') return # Save preset section - use safe operator calls save_row = layout.row(align=True) save_row.prop(props, "new_preset_name", text="", placeholder="Preset Name") premium_lock = create_premium_lock_function() safe_operator_call(save_row, "text_texture.save_preset", premium_lock, text="Save", icon='FILE_NEW') # Save with blend file checkbox layout.prop(props, "save_with_blend_file", text="Project-specific (only available in this .blend file)") layout.separator() # Import/Export and refresh buttons - use safe operator calls backup_row = layout.row(align=True) export_lock = create_premium_lock_function() import_lock = create_premium_lock_function() safe_operator_call(backup_row, "text_texture.export_presets", export_lock, text="Export Backup", icon='EXPORT') safe_operator_call(backup_row, "text_texture.import_presets", import_lock, text="Import Backup", icon='IMPORT') utility_row = layout.row(align=True) refresh_lock = create_premium_lock_function() safe_operator_call(utility_row, "text_texture.refresh_presets", refresh_lock, text="Refresh", icon='FILE_REFRESH') layout.separator() # Load presets section - unified list if props.presets: pass layout.label(text="Presets", icon='PRESET') # Sort presets alphabetically for consistent display sorted_presets = sorted(props.presets, key=lambda p: p.name.lower()) for preset in sorted_presets: pass row = layout.row(align=True) row.scale_y = 0.9 # Load button - use safe operator call load_lock = create_premium_lock_function() load_btn = safe_operator_call(row, "text_texture.load_preset", load_lock, text=preset.name) if load_btn: load_btn.preset_name = preset.name # Quick overwrite button - use safe operator call overwrite_lock = create_premium_lock_function() overwrite_btn = safe_operator_call(row, "text_texture.save_preset", overwrite_lock, text="", icon='FILE_REFRESH') if overwrite_btn: overwrite_btn.overwrite = True overwrite_btn.preset_name = preset.name # Delete button - use safe operator call delete_lock = create_premium_lock_function() delete_btn = safe_operator_call(row, "text_texture.delete_preset", delete_lock, text="", icon='TRASH') if delete_btn: delete_btn.preset_name = preset.name else: pass layout.label(text="No presets yet") layout.label(text="Save your settings above to create your first preset.") def draw_shader_section(layout, props): pass """Draw shader generation controls""" # Check if shader generation is available from ..utils.constants import has_shader_generation if not has_shader_generation(): draw_feature_lock_indicator(layout, 'shader_generation') return # Create Shader Button (as requested) row = layout.row() row.scale_y = 1.5 row.operator("text_texture.generate_shader", text="Create Shader", icon='MATERIAL') layout.separator() # Clean minimalist controls col = layout.column(align=True) col.prop(props, "shader_type", text="Type") # Show relevant options based on shader type if props.shader_type == 'PRINCIPLED': pass col.prop(props, "shader_connection", text="Connect") if props.shader_connection == 'EMISSION': pass col.prop(props, "emission_strength", text="Strength") elif props.shader_type == 'EMISSION': col.prop(props, "emission_strength", text="Strength") layout.separator() # Simple toggles col = layout.column(align=True) col.prop(props, "use_alpha", text="Use Alpha Channel") col.prop(props, "auto_assign_material", text="Auto-Assign to Object") # Clean status info if props.auto_assign_material: pass layout.separator() info_row = layout.row() info_row.scale_y = 0.8 try: pass context = bpy.context if context and context.active_object and context.active_object.type in ['MESH', 'CURVE']: pass info_row.label(text=f"→ {context.active_object.name}", icon='CHECKMARK') else: pass info_row.label(text="Select mesh/curve object", icon='INFO') except: pass info_row.label(text="Select mesh/curve object", icon='INFO') def draw_shader_options_only(layout, props): pass """Draw only shader options without the Create button - for collapsible section""" # Check if shader generation is available from ..utils.constants import has_shader_generation if not has_shader_generation(): draw_feature_lock_indicator(layout, 'shader_generation') return # Clean grid layout for shader options grid = layout.grid_flow(row_major=True, columns=2, align=True) grid.prop(props, "shader_type", text="Type") if props.shader_type == 'PRINCIPLED': pass grid.prop(props, "shader_connection", text="Connect") if props.shader_connection == 'EMISSION': pass grid.prop(props, "emission_strength", text="Strength") elif props.shader_type == 'EMISSION': grid.prop(props, "emission_strength", text="Strength") # Simple row for toggles layout.separator() row = layout.row(align=True) row.prop(props, "use_alpha", toggle=True) row.prop(props, "auto_assign_material", toggle=True) # Shadow, reflection, and backfacing toggles row = layout.row(align=True) row.prop(props, "disable_shadows", toggle=True) row.prop(props, "disable_reflections", toggle=True) row.prop(props, "disable_backfacing", toggle=True) def draw_normal_maps_section(layout, props): pass """Draw normal map generation controls""" # Main toggle for normal map generation layout.prop(props, "generate_normal_map", text="Generate Normal Map", icon='TEXTURE') # Show controls only if normal map generation is enabled if props.generate_normal_map: pass layout.separator() # Normal map settings in a clean layout col = layout.column(align=True) col.prop(props, "normal_map_strength", text="Strength", slider=True) col.prop(props, "normal_map_blur_radius", text="Blur Radius", slider=True) layout.separator() layout.prop(props, "normal_map_invert", text="Invert (Emboss ↔ Deboss)") # Info box with usage tips layout.separator() info_box = layout.box() info_box.scale_y = 0.8 info_box.label(text="💡 Normal maps add depth and detail", icon='INFO') info_box.label(text="• Higher strength = more pronounced effect") info_box.label(text="• Blur smooths sharp edges") info_box.label(text="• Invert changes raised/recessed effect") def draw_text_fitting_section(layout, props): pass """Draw text fitting controls""" # Main toggle for text fitting layout.prop(props, "enable_text_fitting", text="Enable Text Fitting", icon='FULLSCREEN_ENTER') # Show controls only if text fitting is enabled if props.enable_text_fitting: pass layout.separator() # Fitting mode selection layout.prop(props, "text_fitting_mode", text="Fitting Mode") layout.separator() # Margin setting layout.prop(props, "text_fitting_margin", text="Margin", slider=True) layout.separator() # Font size limits col = layout.column(align=True) col.label(text="Font Size Limits:", icon='RESTRICT_SELECT_ON') row = col.row(align=True) row.prop(props, "min_font_size", text="Min") row.prop(props, "max_font_size", text="Max") # Info box with usage tips layout.separator() info_box = layout.box() info_box.scale_y = 0.8 info_box.label(text="💡 Text fitting automatically sizes text", icon='INFO') if props.text_fitting_mode == 'FIT_WIDTH': pass info_box.label(text="• Fits text width to canvas width") elif props.text_fitting_mode == 'FIT_HEIGHT': info_box.label(text="• Fits text height to canvas height") elif props.text_fitting_mode == 'FIT_BOTH': info_box.label(text="• Fits both width and height (may distort)") elif props.text_fitting_mode == 'FIT_CONTAIN': info_box.label(text="• Fits entirely within canvas (preserves ratio)") info_box.label(text="• Respects min/max font size limits") def draw_multiline_section(layout, props): pass """Draw multiline text controls""" # Main toggle for multiline text layout.prop(props, "enable_multiline", text="Enable Multiline", icon='TEXT') # Show controls only if multiline is enabled if props.enable_multiline: pass layout.separator() # Line height and alignment settings col = layout.column(align=True) col.prop(props, "line_height", text="Line Height", slider=True) col.prop(props, "text_alignment", text="Alignment") layout.separator() # Auto wrap settings layout.prop(props, "auto_wrap", text="Auto Wrap") if props.auto_wrap: pass layout.prop(props, "wrap_width", text="Wrap Width (0 = texture width)") layout.separator() # Line limits and spacing col = layout.column(align=True) col.prop(props, "max_lines", text="Max Lines (0 = unlimited)") col.prop(props, "line_spacing_pixels", text="Line Spacing (px)") # Info box with usage tips layout.separator() info_box = layout.box() info_box.scale_y = 0.8 info_box.label(text="💡 Multiline text supports:", icon='INFO') info_box.label(text="• Use \\n for manual line breaks") info_box.label(text="• Auto-wrap fits text to width") info_box.label(text="• Alignment works per line") info_box.label(text="• Line height controls spacing") def draw_stroke_section(layout, props): pass """Draw stroke and outline controls""" # Check if stroke effects are available from ..utils.constants import has_stroke_effects if not has_stroke_effects(): draw_feature_lock_indicator(layout, 'stroke_effects') return # Main toggle for stroke layout.prop(props, "enable_stroke", text="Enable Stroke", icon='MOD_OUTLINE') # Show controls only if stroke is enabled if props.enable_stroke: pass layout.separator() # Stroke width and color col = layout.column(align=True) col.prop(props, "stroke_width", text="Width", slider=True) col.prop(props, "stroke_color", text="Color") layout.separator() # Stroke position layout.prop(props, "stroke_position", text="Position") # Info box with usage tips layout.separator() info_box = layout.box() info_box.scale_y = 0.8 info_box.label(text="💡 Stroke adds outline to text:", icon='INFO') if props.stroke_position == 'OUTER': pass info_box.label(text="• Outer: Expands text bounds outward") elif props.stroke_position == 'INNER': info_box.label(text="• Inner: Shrinks text, stroke inside") elif props.stroke_position == 'CENTER': info_box.label(text="• Center: Stroke on text edge") info_box.label(text="• Width 0 = no stroke") info_box.label(text="• Works with multiline text") def draw_shadow_glow_section(layout, props): pass """Draw shadow and glow effects controls""" # Check if shadow/glow effects are available from ..utils.constants import has_shadow_glow_effects if not has_shadow_glow_effects(): draw_feature_lock_indicator(layout, 'shadow_glow_effects') return # Drop shadow section layout.prop(props, "enable_shadow", text="Enable Drop Shadow", icon='SHADERFX') if props.enable_shadow: pass layout.separator() # Shadow offset controls in a row offset_row = layout.row(align=True) offset_row.prop(props, "shadow_offset_x", text="X Offset") offset_row.prop(props, "shadow_offset_y", text="Y Offset") # Shadow properties col = layout.column(align=True) col.prop(props, "shadow_blur", text="Shadow Blur", slider=True) col.prop(props, "shadow_spread", text="Shadow Spread", slider=True) col.prop(props, "shadow_color", text="Shadow Color") layout.separator() # Glow section layout.prop(props, "enable_glow", text="Enable Glow", icon='LIGHT_SUN') if props.enable_glow: pass layout.separator() # Glow properties col = layout.column(align=True) col.prop(props, "glow_blur", text="Glow Blur", slider=True) col.prop(props, "glow_color", text="Glow Color") # Info box with usage tips if props.enable_shadow or props.enable_glow: pass layout.separator() info_box = layout.box() info_box.scale_y = 0.8 info_box.label(text="💡 Shadow & Glow effects:", icon='INFO') if props.enable_shadow: pass info_box.label(text="• Drop shadow: offset + blur for depth") if props.enable_glow: pass info_box.label(text="• Glow: blur without offset for aura effect") info_box.label(text="• Higher blur = softer effect") info_box.label(text="• Alpha controls effect intensity") def draw_blur_section(layout, props): pass """Draw blur effects controls""" # Check if blur effects are available from ..utils.constants import has_blur_effects if not has_blur_effects(): draw_feature_lock_indicator(layout, 'blur_effects') return # Main toggle for blur layout.prop(props, "enable_blur", text="Enable Blur", icon='MOD_SMOOTH') # Show controls only if blur is enabled if props.enable_blur: pass layout.separator() # Blur radius setting layout.prop(props, "blur_radius", text="Blur Radius", slider=True) # Info box with usage tips layout.separator() info_box = layout.box() info_box.scale_y = 0.8 info_box.label(text="💡 Blur effect softens text edges:", icon='INFO') info_box.label(text="• Applied to main text content only") info_box.label(text="• Higher radius = softer/more blurred") info_box.label(text="• Works with all other effects") info_box.label(text="• Radius 0 = no blur effect") # ============================================================================ # UI PANELS # ============================================================================ class TEXT_TEXTURE_PT_panel(Panel): pass """Main Panel in Shader Editor""" bl_label = "Text Texture Generator" bl_idname = "TEXT_TEXTURE_PT_panel" bl_space_type = 'NODE_EDITOR' bl_region_type = 'UI' bl_category = "Tool" @classmethod def poll(cls, context): pass # DEBUG: Log poll method calls if hasattr(context.space_data, 'tree_type'): pass else: pass # More permissive poll condition to ensure panel shows up if context.space_data.type == 'NODE_EDITOR': pass if hasattr(context.space_data, 'tree_type'): pass result = context.space_data.tree_type == 'ShaderNodeTree' return result else: pass # Allow panel even without tree_type (sometimes happens) return True return False def draw(self, context): pass layout = self.layout props = context.scene.text_texture_props # TOP LEVEL - Always Visible Controls # Text Input box = layout.box() box.label(text="Text:", icon='TEXT') draw_text_input_section(box, props) # Generate Button layout.separator(factor=0.5) draw_generate_button(layout) # CREATE SHADER BUTTON - WITH PROPER RESTRICTION layout.separator(factor=0.5) create_shader_row = layout.row() create_shader_row.scale_y = 1.5 # Check if shader generation is available from ..utils.constants import has_shader_generation if has_shader_generation(): create_shader_row.operator("text_texture.generate_shader", text="Create Shader", icon='MATERIAL') else: # Show locked shader button for free version lock_row = create_shader_row.row() lock_row.enabled = False lock_row.operator("text_texture.generate_shader", text="🔒 Create Shader (Pro Only)", icon='LOCKED') # Preview Button - ALWAYS available (core functionality for all users) layout.separator(factor=0.5) preview_row = layout.row() preview_row.scale_y = 1.5 preview_row.operator("text_texture.preview", text="Preview Texture", icon='HIDE_OFF') layout.separator(factor=1.2) # COLLAPSIBLE SECTIONS - FREE FEATURES FIRST # === FREE FEATURES === # Multiline Section (collapsed by default) multiline_section = draw_collapsible_header(layout, "expand_multiline", "Multiline Text", 'TEXT') if multiline_section: pass draw_multiline_section(multiline_section, props) # Font Settings Section (collapsed by default) font_section = draw_collapsible_header(layout, "expand_font_settings", "Font Settings", 'FONT_DATA') if font_section: pass draw_font_settings_section(font_section, props) # Positioning & Layout Section (collapsed by default) - FREE FEATURE moved to free group position_section = draw_collapsible_header(layout, "expand_positioning", "Positioning & Layout", 'ANCHOR_CENTER') if position_section: draw_positioning_section(position_section, props) # Text Fitting Section (collapsed by default) text_fitting_section = draw_collapsible_header(layout, "expand_text_fitting", "Text Fitting", 'FULLSCREEN_ENTER') if text_fitting_section: pass draw_text_fitting_section(text_fitting_section, props) # Colors Section (collapsed by default) colors_section = draw_collapsible_header(layout, "expand_colors", "Colors", 'COLOR') if colors_section: pass draw_colors_section(colors_section, props) # === PREMIUM FEATURES === # Shader Section (collapsed by default) - ONLY OPTIONS, NOT THE BUTTON shader_header_row = layout.row(align=True) # Gray out header if locked and prevent expansion from ..utils.constants import has_shader_generation if not has_shader_generation(): shader_header_row.enabled = False # Don't allow expansion for locked features shader_expanded = None # Show lock indicator instead of expandable header lock_header = shader_header_row.row() lock_header.label(text="Shader Options", icon='SETTINGS') lock_header.label(text="🔒 PRO", icon='LOCKED') else: shader_expanded = draw_collapsible_header(shader_header_row, "expand_shader", "Shader Options", 'SETTINGS') # Only show content if available and expanded if shader_expanded: draw_shader_options_only(shader_expanded, props) # Image Overlays Section (collapsed by default) - Show with premium indicator overlay_header_row = layout.row(align=True) # Gray out header if locked and prevent expansion from ..utils.constants import has_image_overlays if not has_image_overlays(): overlay_header_row.enabled = False # Don't allow expansion for locked features overlay_expanded = None # Show lock indicator instead of expandable header lock_header = overlay_header_row.row() lock_header.label(text="Image Overlays", icon='IMAGE_DATA') lock_header.label(text="🔒 PRO", icon='LOCKED') else: overlay_expanded = draw_collapsible_header(overlay_header_row, "expand_image_overlays", "Image Overlays", 'IMAGE_DATA') # Only show content if available and expanded if overlay_expanded: draw_image_overlays_section(overlay_expanded, props) # Stroke Section (collapsed by default) - Show with premium indicator stroke_header_row = layout.row(align=True) # Gray out header if locked and prevent expansion from ..utils.constants import has_stroke_effects if not has_stroke_effects(): stroke_header_row.enabled = False # Don't allow expansion for locked features stroke_expanded = None # Show lock indicator instead of expandable header lock_header = stroke_header_row.row() lock_header.label(text="Stroke & Outlines", icon='MOD_OUTLINE') lock_header.label(text="🔒 PRO", icon='LOCKED') else: stroke_expanded = draw_collapsible_header(stroke_header_row, "expand_stroke", "Stroke & Outlines", 'MOD_OUTLINE') # Only show content if available and expanded if stroke_expanded: draw_stroke_section(stroke_expanded, props) # Shadow & Glow Section (collapsed by default) - Show with premium indicator shadow_glow_header_row = layout.row(align=True) # Gray out header if locked and prevent expansion from ..utils.constants import has_shadow_glow_effects if not has_shadow_glow_effects(): shadow_glow_header_row.enabled = False # Don't allow expansion for locked features shadow_glow_expanded = None # Show lock indicator instead of expandable header lock_header = shadow_glow_header_row.row() lock_header.label(text="Shadow & Glow Effects", icon='SHADERFX') lock_header.label(text="🔒 PRO", icon='LOCKED') else: shadow_glow_expanded = draw_collapsible_header(shadow_glow_header_row, "expand_shadow_glow", "Shadow & Glow Effects", 'SHADERFX') # Only show content if available and expanded if shadow_glow_expanded: draw_shadow_glow_section(shadow_glow_expanded, props) # Blur Section (collapsed by default) - Show with premium indicator blur_header_row = layout.row(align=True) # Gray out header if locked and prevent expansion from ..utils.constants import has_blur_effects if not has_blur_effects(): blur_header_row.enabled = False # Don't allow expansion for locked features blur_expanded = None # Show lock indicator instead of expandable header lock_header = blur_header_row.row() lock_header.label(text="Blur Effects", icon='MOD_SMOOTH') lock_header.label(text="🔒 PRO", icon='LOCKED') else: blur_expanded = draw_collapsible_header(blur_header_row, "expand_blur", "Blur Effects", 'MOD_SMOOTH') # Only show content if available and expanded if blur_expanded: draw_blur_section(blur_expanded, props) # Normal Maps Section (collapsed by default) - Show with premium indicator normal_header_row = layout.row(align=True) # Gray out header if locked and prevent expansion if not has_normal_maps(): normal_header_row.enabled = False # Don't allow expansion for locked features normal_expanded = None # Show lock indicator instead of expandable header lock_header = normal_header_row.row() lock_header.label(text="Normal Maps", icon='TEXTURE') lock_header.label(text="🔒 PRO", icon='LOCKED') else: normal_expanded = draw_collapsible_header(normal_header_row, "expand_normal_maps", "Normal Maps", 'TEXTURE') # Only show content if available and expanded if normal_expanded: draw_normal_maps_section(normal_expanded, props) # Presets Section (collapsed by default) - Show with premium indicator presets_header_row = layout.row(align=True) # Gray out header if locked and prevent expansion if not has_presets(): presets_header_row.enabled = False # Don't allow expansion for locked features presets_expanded = None # Show lock indicator instead of expandable header lock_header = presets_header_row.row() lock_header.label(text="Presets", icon='PRESET') lock_header.label(text="🔒 PRO", icon='LOCKED') else: presets_expanded = draw_collapsible_header(presets_header_row, "expand_presets", "Presets", 'PRESET') # Only show content if available and expanded if presets_expanded: draw_presets_section(presets_expanded, props) class TEXT_TEXTURE_PT_panel_3d(Panel): pass """3D View Panel - Access from sidebar (N key)""" bl_label = "Text Texture Generator" bl_idname = "TEXT_TEXTURE_PT_panel_3d" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_category = "Tool" bl_options = {'DEFAULT_CLOSED'} def draw(self, context): pass layout = self.layout props = context.scene.text_texture_props # TOP LEVEL - Always Visible Controls # Text Input box = layout.box() box.label(text="Text:", icon='TEXT') draw_text_input_section(box, props) # Generate Button layout.separator(factor=0.5) draw_generate_button(layout) # CREATE SHADER BUTTON - WITH PROPER RESTRICTION layout.separator(factor=0.5) create_shader_row = layout.row() create_shader_row.scale_y = 1.5 # Check if shader generation is available from ..utils.constants import has_shader_generation if has_shader_generation(): create_shader_row.operator("text_texture.generate_shader", text="Create Shader", icon='MATERIAL') else: # Show locked shader button for free version lock_row = create_shader_row.row() lock_row.enabled = False lock_row.operator("text_texture.generate_shader", text="🔒 Create Shader (Pro Only)", icon='LOCKED') # Preview Button - ALWAYS available (core functionality for all users) layout.separator(factor=0.5) preview_row = layout.row() preview_row.scale_y = 1.5 preview_row.operator("text_texture.preview", text="Preview Texture", icon='HIDE_OFF') layout.separator(factor=1.2) # COLLAPSIBLE SECTIONS - FREE FEATURES FIRST # === FREE FEATURES === # Multiline Section (collapsed by default) multiline_section = draw_collapsible_header(layout, "expand_multiline", "Multiline Text", 'TEXT') if multiline_section: pass draw_multiline_section(multiline_section, props) # Font Settings Section (collapsed by default) font_section = draw_collapsible_header(layout, "expand_font_settings", "Font Settings", 'FONT_DATA') if font_section: pass draw_font_settings_section(font_section, props) # Positioning & Layout Section (collapsed by default) - FREE FEATURE moved to free group position_section = draw_collapsible_header(layout, "expand_positioning", "Positioning & Layout", 'ANCHOR_CENTER') if position_section: draw_positioning_section(position_section, props) # Text Fitting Section (collapsed by default) text_fitting_section = draw_collapsible_header(layout, "expand_text_fitting", "Text Fitting", 'FULLSCREEN_ENTER') if text_fitting_section: pass draw_text_fitting_section(text_fitting_section, props) # Colors Section (collapsed by default) colors_section = draw_collapsible_header(layout, "expand_colors", "Colors", 'COLOR') if colors_section: pass draw_colors_section(colors_section, props) # === PREMIUM FEATURES === # Shader Section (collapsed by default) - ONLY OPTIONS, NOT THE BUTTON shader_header_row = layout.row(align=True) # Gray out header if locked and prevent expansion from ..utils.constants import has_shader_generation if not has_shader_generation(): shader_header_row.enabled = False # Don't allow expansion for locked features shader_expanded = None # Show lock indicator instead of expandable header lock_header = shader_header_row.row() lock_header.label(text="Shader Options", icon='SETTINGS') lock_header.label(text="🔒 PRO", icon='LOCKED') else: shader_expanded = draw_collapsible_header(shader_header_row, "expand_shader", "Shader Options", 'SETTINGS') # Only show content if available and expanded if shader_expanded: draw_shader_options_only(shader_expanded, props) # Image Overlays Section (collapsed by default) - Show with premium indicator overlay_header_row = layout.row(align=True) # Gray out header if locked and prevent expansion from ..utils.constants import has_image_overlays if not has_image_overlays(): overlay_header_row.enabled = False # Don't allow expansion for locked features overlay_expanded = None # Show lock indicator instead of expandable header lock_header = overlay_header_row.row() lock_header.label(text="Image Overlays", icon='IMAGE_DATA') lock_header.label(text="🔒 PRO", icon='LOCKED') else: overlay_expanded = draw_collapsible_header(overlay_header_row, "expand_image_overlays", "Image Overlays", 'IMAGE_DATA') # Only show content if available and expanded if overlay_expanded: draw_image_overlays_section(overlay_expanded, props) # Stroke Section (collapsed by default) - Show with premium indicator stroke_header_row = layout.row(align=True) # Gray out header if locked and prevent expansion from ..utils.constants import has_stroke_effects if not has_stroke_effects(): stroke_header_row.enabled = False # Don't allow expansion for locked features stroke_expanded = None # Show lock indicator instead of expandable header lock_header = stroke_header_row.row() lock_header.label(text="Stroke & Outlines", icon='MOD_OUTLINE') lock_header.label(text="🔒 PRO", icon='LOCKED') else: stroke_expanded = draw_collapsible_header(stroke_header_row, "expand_stroke", "Stroke & Outlines", 'MOD_OUTLINE') # Only show content if available and expanded if stroke_expanded: draw_stroke_section(stroke_expanded, props) # Shadow & Glow Section (collapsed by default) - Show with premium indicator shadow_glow_header_row = layout.row(align=True) # Gray out header if locked and prevent expansion from ..utils.constants import has_shadow_glow_effects if not has_shadow_glow_effects(): shadow_glow_header_row.enabled = False # Don't allow expansion for locked features shadow_glow_expanded = None # Show lock indicator instead of expandable header lock_header = shadow_glow_header_row.row() lock_header.label(text="Shadow & Glow Effects", icon='SHADERFX') lock_header.label(text="🔒 PRO", icon='LOCKED') else: shadow_glow_expanded = draw_collapsible_header(shadow_glow_header_row, "expand_shadow_glow", "Shadow & Glow Effects", 'SHADERFX') # Only show content if available and expanded if shadow_glow_expanded: draw_shadow_glow_section(shadow_glow_expanded, props) # Blur Section (collapsed by default) - Show with premium indicator blur_header_row = layout.row(align=True) # Gray out header if locked and prevent expansion from ..utils.constants import has_blur_effects if not has_blur_effects(): blur_header_row.enabled = False # Don't allow expansion for locked features blur_expanded = None # Show lock indicator instead of expandable header lock_header = blur_header_row.row() lock_header.label(text="Blur Effects", icon='MOD_SMOOTH') lock_header.label(text="🔒 PRO", icon='LOCKED') else: blur_expanded = draw_collapsible_header(blur_header_row, "expand_blur", "Blur Effects", 'MOD_SMOOTH') # Only show content if available and expanded if blur_expanded: draw_blur_section(blur_expanded, props) # Normal Maps Section (collapsed by default) - Show with premium indicator normal_header_row = layout.row(align=True) # Gray out header if locked and prevent expansion if not has_normal_maps(): normal_header_row.enabled = False # Don't allow expansion for locked features normal_expanded = None # Show lock indicator instead of expandable header lock_header = normal_header_row.row() lock_header.label(text="Normal Maps", icon='TEXTURE') lock_header.label(text="🔒 PRO", icon='LOCKED') else: normal_expanded = draw_collapsible_header(normal_header_row, "expand_normal_maps", "Normal Maps", 'TEXTURE') # Only show content if available and expanded if normal_expanded: draw_normal_maps_section(normal_expanded, props) # Presets Section (collapsed by default) - Show with premium indicator presets_header_row = layout.row(align=True) # Gray out header if locked and prevent expansion if not has_presets(): presets_header_row.enabled = False # Don't allow expansion for locked features presets_expanded = None # Show lock indicator instead of expandable header lock_header = presets_header_row.row() lock_header.label(text="Presets", icon='PRESET') lock_header.label(text="🔒 PRO", icon='LOCKED') else: presets_expanded = draw_collapsible_header(presets_header_row, "expand_presets", "Presets", 'PRESET') # Only show content if available and expanded if presets_expanded: draw_presets_section(presets_expanded, props) class TEXT_TEXTURE_PT_panel_properties(Panel): pass """Properties Panel""" bl_label = "Text Texture Generator" bl_idname = "TEXT_TEXTURE_PT_panel_properties" bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' bl_context = "material" bl_category = "Tool" def draw(self, context): pass layout = self.layout props = context.scene.text_texture_props # TOP LEVEL - Always Visible Controls # Text Input box = layout.box() box.label(text="Text:", icon='TEXT') draw_text_input_section(box, props) # Generate Button layout.separator(factor=0.5) draw_generate_button(layout) # CREATE SHADER BUTTON - WITH PROPER RESTRICTION layout.separator(factor=0.5) create_shader_row = layout.row() create_shader_row.scale_y = 1.5 # Check if shader generation is available from ..utils.constants import has_shader_generation if has_shader_generation(): create_shader_row.operator("text_texture.generate_shader", text="Create Shader", icon='MATERIAL') else: # Show locked shader button for free version lock_row = create_shader_row.row() lock_row.enabled = False lock_row.operator("text_texture.generate_shader", text="🔒 Create Shader (Pro Only)", icon='LOCKED') # Preview Button - ALWAYS available (core functionality for all users) layout.separator(factor=0.5) preview_row = layout.row() preview_row.scale_y = 1.5 preview_row.operator("text_texture.preview", text="Preview Texture", icon='HIDE_OFF') layout.separator(factor=1.2) # COLLAPSIBLE SECTIONS - FREE FEATURES FIRST # === FREE FEATURES === # Multiline Section (collapsed by default) multiline_section = draw_collapsible_header(layout, "expand_multiline", "Multiline Text", 'TEXT') if multiline_section: pass draw_multiline_section(multiline_section, props) # Font Settings Section (collapsed by default) font_section = draw_collapsible_header(layout, "expand_font_settings", "Font Settings", 'FONT_DATA') if font_section: pass draw_font_settings_section(font_section, props) # Positioning & Layout Section (collapsed by default) - FREE FEATURE moved to free group position_section = draw_collapsible_header(layout, "expand_positioning", "Positioning & Layout", 'ANCHOR_CENTER') if position_section: draw_positioning_section(position_section, props) # Text Fitting Section (collapsed by default) text_fitting_section = draw_collapsible_header(layout, "expand_text_fitting", "Text Fitting", 'FULLSCREEN_ENTER') if text_fitting_section: pass draw_text_fitting_section(text_fitting_section, props) # Colors Section (collapsed by default) colors_section = draw_collapsible_header(layout, "expand_colors", "Colors", 'COLOR') if colors_section: pass draw_colors_section(colors_section, props) # === PREMIUM FEATURES === # Image Overlays Section (collapsed by default) - Show with premium indicator overlay_header_row = layout.row(align=True) # Gray out header if locked and prevent expansion from ..utils.constants import has_image_overlays if not has_image_overlays(): overlay_header_row.enabled = False # Don't allow expansion for locked features overlay_expanded = None # Show lock indicator instead of expandable header lock_header = overlay_header_row.row() lock_header.label(text="Image Overlays", icon='IMAGE_DATA') lock_header.label(text="🔒 PRO", icon='LOCKED') else: overlay_expanded = draw_collapsible_header(overlay_header_row, "expand_image_overlays", "Image Overlays", 'IMAGE_DATA') # Only show content if available and expanded if overlay_expanded: draw_image_overlays_section(overlay_expanded, props) # Stroke Section (collapsed by default) - Show with premium indicator stroke_header_row = layout.row(align=True) # Gray out header if locked and prevent expansion from ..utils.constants import has_stroke_effects if not has_stroke_effects(): stroke_header_row.enabled = False # Don't allow expansion for locked features stroke_expanded = None # Show lock indicator instead of expandable header lock_header = stroke_header_row.row() lock_header.label(text="Stroke & Outlines", icon='MOD_OUTLINE') lock_header.label(text="🔒 PRO", icon='LOCKED') else: stroke_expanded = draw_collapsible_header(stroke_header_row, "expand_stroke", "Stroke & Outlines", 'MOD_OUTLINE') # Only show content if available and expanded if stroke_expanded: draw_stroke_section(stroke_expanded, props) # Shadow & Glow Section (collapsed by default) - Show with premium indicator shadow_glow_header_row = layout.row(align=True) # Gray out header if locked and prevent expansion from ..utils.constants import has_shadow_glow_effects if not has_shadow_glow_effects(): shadow_glow_header_row.enabled = False # Don't allow expansion for locked features shadow_glow_expanded = None # Show lock indicator instead of expandable header lock_header = shadow_glow_header_row.row() lock_header.label(text="Shadow & Glow Effects", icon='SHADERFX') lock_header.label(text="🔒 PRO", icon='LOCKED') else: shadow_glow_expanded = draw_collapsible_header(shadow_glow_header_row, "expand_shadow_glow", "Shadow & Glow Effects", 'SHADERFX') # Only show content if available and expanded if shadow_glow_expanded: draw_shadow_glow_section(shadow_glow_expanded, props) # Blur Section (collapsed by default) - Show with premium indicator blur_header_row = layout.row(align=True) # Gray out header if locked and prevent expansion from ..utils.constants import has_blur_effects if not has_blur_effects(): blur_header_row.enabled = False # Don't allow expansion for locked features blur_expanded = None # Show lock indicator instead of expandable header lock_header = blur_header_row.row() lock_header.label(text="Blur Effects", icon='MOD_SMOOTH') lock_header.label(text="🔒 PRO", icon='LOCKED') else: blur_expanded = draw_collapsible_header(blur_header_row, "expand_blur", "Blur Effects", 'MOD_SMOOTH') # Only show content if available and expanded if blur_expanded: draw_blur_section(blur_expanded, props) # Normal Maps Section (collapsed by default) - Show with premium indicator normal_header_row = layout.row(align=True) # Gray out header if locked and prevent expansion if not has_normal_maps(): normal_header_row.enabled = False # Don't allow expansion for locked features normal_expanded = None # Show lock indicator instead of expandable header lock_header = normal_header_row.row() lock_header.label(text="Normal Maps", icon='TEXTURE') lock_header.label(text="🔒 PRO", icon='LOCKED') else: normal_expanded = draw_collapsible_header(normal_header_row, "expand_normal_maps", "Normal Maps", 'TEXTURE') # Only show content if available and expanded if normal_expanded: draw_normal_maps_section(normal_expanded, props) # Presets Section (collapsed by default) - Show with premium indicator presets_header_row = layout.row(align=True) # Gray out header if locked and prevent expansion if not has_presets(): presets_header_row.enabled = False # Don't allow expansion for locked features presets_expanded = None # Show lock indicator instead of expandable header lock_header = presets_header_row.row() lock_header.label(text="Presets", icon='PRESET') lock_header.label(text="🔒 PRO", icon='LOCKED') else: presets_expanded = draw_collapsible_header(presets_header_row, "expand_presets", "Presets", 'PRESET') # Only show content if available and expanded if presets_expanded: draw_presets_section(presets_expanded, props)