""" Text Texture Generation Engine Core functionality for generating texture images with text overlays. """ import bpy def generate_texture_image(props, width, height): """ Generate the actual texture image with overlays. Args: props: Text texture properties object width: Target image width in pixels height: Target image height in pixels Returns: tuple: (diffuse_image, normal_map_image) or (diffuse_image, None) if normal map disabled Returns (None, None) on error """ try: print(f"[TTG DEBUG] Starting texture generation at {width}x{height}px") # TODO: Implement actual texture generation logic # This is a minimal stub to resolve the import error # The full implementation should include: # - Background color/image handling # - Text overlay processing and rendering # - Normal map generation if enabled # - Image composition and final output print(f"[TTG DEBUG] Step 1: Creating Blender image object") # Create Blender image directly without large numpy arrays img_name = f"TextTexture_{width}x{height}" if img_name in bpy.data.images: print(f"[TTG DEBUG] Step 1a: Removing existing image: {img_name}") bpy.data.images.remove(bpy.data.images[img_name]) print(f"[TTG DEBUG] Step 1b: Creating new Blender image: {img_name}") blender_img = bpy.data.images.new(img_name, width, height, alpha=True) print(f"[TTG DEBUG] Step 1c: Blender image created successfully") print(f"[TTG DEBUG] Step 2: Creating text texture with PIL") # Use PIL to create a proper text texture instead of solid color try: from PIL import Image, ImageDraw, ImageFont print(f"[TTG DEBUG] Step 2a: PIL imported successfully") # Create PIL image with transparent background if props.background_transparent: pil_img = Image.new('RGBA', (width, height), (0, 0, 0, 0)) print(f"[TTG DEBUG] Step 2b: Created transparent PIL image") else: bg_color = tuple(int(c * 255) for c in props.background_color[:3]) + (255,) pil_img = Image.new('RGBA', (width, height), bg_color) print(f"[TTG DEBUG] Step 2b: Created PIL image with background color: {bg_color}") # Draw text if provided (including prepend/append) full_text_parts = [] if props.prepend_text.strip(): full_text_parts.append(props.prepend_text.strip()) if props.text.strip(): full_text_parts.append(props.text.strip()) if props.append_text.strip(): full_text_parts.append(props.append_text.strip()) if full_text_parts: draw = ImageDraw.Draw(pil_img) # Combine text based on layout mode if props.prepend_append_layout == 'HORIZONTAL': # For horizontal layout, use simple space separation with margin control if len(full_text_parts) == 1: full_text = full_text_parts[0] else: # Calculate number of spaces based on margin (but reasonable amounts) prepend_spaces = max(1, int(props.prepend_margin / 10)) if props.prepend_text.strip() else 0 append_spaces = max(1, int(props.append_margin / 10)) if props.append_text.strip() else 0 # Build text with controlled spacing text_parts_with_spacing = [] for i, part in enumerate(full_text_parts): if i == 0 and prepend_spaces > 0: # Add spacing after prepend text text_parts_with_spacing.append(part + " " * prepend_spaces) elif i == len(full_text_parts) - 1 and append_spaces > 0: # Add spacing before append text text_parts_with_spacing.append(" " * append_spaces + part) else: text_parts_with_spacing.append(part) full_text = " ".join(text_parts_with_spacing) layout_mode = "horizontal" else: # Vertical layout - keep parts separate for individual positioning full_text = "\n".join(full_text_parts) layout_mode = "vertical" print(f"[TTG DEBUG] Step 2c: Full text composed: '{full_text}' (layout: {layout_mode})") # Determine font size (with text fitting if enabled) font_size = props.font_size if props.enable_text_fitting: print(f"[TTG DEBUG] Step 2d: Text fitting enabled, calculating optimal size") # Try to load font for sizing try: if props.use_custom_font and props.custom_font_path: test_font_path = props.custom_font_path elif props.font_path != "default": test_font_path = props.font_path else: test_font_path = None # Binary search for optimal font size min_size = props.min_font_size max_size = props.max_font_size target_width = width * (1.0 - props.text_fitting_margin / 100.0) target_height = height * (1.0 - props.text_fitting_margin / 100.0) optimal_size = min_size for test_size in range(min_size, max_size + 1, 2): # Step by 2 for performance try: if test_font_path: test_font = ImageFont.truetype(test_font_path, test_size) else: test_font = ImageFont.load_default() bbox = draw.textbbox((0, 0), full_text, font=test_font) test_width = bbox[2] - bbox[0] test_height = bbox[3] - bbox[1] if test_width <= target_width and test_height <= target_height: optimal_size = test_size else: break except (OSError, IOError): break font_size = optimal_size print(f"[TTG DEBUG] Step 2d: Optimal font size calculated: {font_size}") except Exception as e: print(f"[TTG DEBUG] Step 2d: Font fitting failed, using default size: {e}") font_size = props.font_size # Load final font try: if props.use_custom_font and props.custom_font_path: font = ImageFont.truetype(props.custom_font_path, font_size) print(f"[TTG DEBUG] Step 2e: Using custom font: {props.custom_font_path}, size: {font_size}") elif props.font_path != "default": font = ImageFont.truetype(props.font_path, font_size) print(f"[TTG DEBUG] Step 2e: Using system font: {props.font_path}, size: {font_size}") else: font = ImageFont.load_default() print(f"[TTG DEBUG] Step 2e: Using default font, size: {font_size}") except (OSError, IOError) as e: print(f"[TTG DEBUG] Step 2e: Font loading failed, using default: {e}") font = ImageFont.load_default() # Calculate text position and draw if layout_mode == "vertical" and props.prepend_append_layout == 'VERTICAL': # Draw each part separately for vertical layout y_offset = 0 total_height = 0 # Calculate total height first part_heights = [] for part in full_text_parts: bbox = draw.textbbox((0, 0), part, font=font) part_height = bbox[3] - bbox[1] part_heights.append(part_height) total_height += part_height # Add spacing between parts if len(full_text_parts) > 1: spacing = font_size // 4 # 25% of font size as line spacing total_height += spacing * (len(full_text_parts) - 1) # Center vertically start_y = (height - total_height) // 2 # Draw each part current_y = start_y text_color = tuple(int(c * 255) for c in props.text_color[:4]) for i, part in enumerate(full_text_parts): bbox = draw.textbbox((0, 0), part, font=font) part_width = bbox[2] - bbox[0] x = (width - part_width) // 2 # Center horizontally draw.text((x, current_y), part, font=font, fill=text_color) print(f"[TTG DEBUG] Step 2f: Drew part '{part}' at ({x}, {current_y})") current_y += part_heights[i] if i < len(full_text_parts) - 1: # Add spacing except after last part current_y += font_size // 4 else: # Horizontal layout or single text text_bbox = draw.textbbox((0, 0), full_text, font=font) text_width = text_bbox[2] - text_bbox[0] text_height = text_bbox[3] - text_bbox[1] # Center text x = (width - text_width) // 2 y = (height - text_height) // 2 # Draw text text_color = tuple(int(c * 255) for c in props.text_color[:4]) draw.text((x, y), full_text, font=font, fill=text_color) print(f"[TTG DEBUG] Step 2f: Full text '{full_text}' drawn at ({x}, {y}) with size {font_size}") else: print(f"[TTG DEBUG] Step 2d: No text provided, using solid background") # Convert PIL image to Blender format (flip vertically to fix mirroring) print(f"[TTG DEBUG] Step 2f: Converting PIL to Blender format with vertical flip") pil_img = pil_img.transpose(Image.FLIP_TOP_BOTTOM) # Fix mirroring issue pil_pixels = list(pil_img.getdata()) blender_pixels = [] for r, g, b, a in pil_pixels: blender_pixels.extend([r/255.0, g/255.0, b/255.0, a/255.0]) blender_img.pixels[:] = blender_pixels print(f"[TTG DEBUG] Step 2g: Pixel data conversion completed with flip correction") except ImportError: print(f"[TTG DEBUG] Step 2: PIL not available, falling back to solid color") # Fallback to solid color if PIL is not available pixel_pattern = [props.background_color[0], props.background_color[1], props.background_color[2], 1.0] total_pixels = width * height blender_img.pixels[:] = pixel_pattern * total_pixels print(f"[TTG DEBUG] Step 3: Packing image...") blender_img.pack() print(f"[TTG DEBUG] Step 3: Image packing completed") # Generate normal map if enabled normal_map_img = None if props.generate_normal_map: print(f"[TTG DEBUG] Step 4: Normal map generation enabled, starting...") from ..core.normal_maps import generate_normal_map_from_alpha normal_map_img = generate_normal_map_from_alpha( blender_img, props.normal_map_strength, props.normal_map_blur_radius, props.normal_map_invert ) print(f"[TTG DEBUG] Step 4: Normal map generation completed") else: print(f"[TTG DEBUG] Step 4: Normal map generation disabled, skipping") print(f"[TTG DEBUG] All steps completed successfully") return blender_img, normal_map_img except Exception as e: print(f"[TTG ERROR] Failed to generate texture image: {e}") import traceback traceback.print_exc() return None, None def generate_preview(props, preview_width=512, preview_height=512): """ Generate a preview version of the texture at lower resolution. Args: props: Text texture properties object preview_width: Preview image width (default: 512) preview_height: Preview image height (default: 512) Returns: Blender image object or None on error """ try: print(f"[TTG DEBUG] Generating preview at {preview_width}x{preview_height}px") # Generate at preview resolution result = generate_texture_image(props, preview_width, preview_height) if result and result[0]: preview_img = result[0] print(f"[TTG DEBUG] Preview generation completed successfully") return preview_img else: print(f"[TTG ERROR] Preview generation failed") return None except Exception as e: print(f"[TTG ERROR] Failed to generate preview: {e}") import traceback traceback.print_exc() return None