This commit is contained in:
2025-09-11 13:13:21 +03:00
parent 80a71c1dc1
commit c1ac5297f2
3 changed files with 280 additions and 60 deletions

View File

@@ -19,7 +19,7 @@ def generate_texture_image(props, width, height):
Returns (None, None) on error
"""
try:
print(f"[TTG DEBUG] Generating texture image at {width}x{height}px")
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
@@ -29,37 +29,228 @@ def generate_texture_image(props, width, height):
# - Normal map generation if enabled
# - Image composition and final output
# For now, create a basic placeholder image
import numpy as np
# Create a basic colored image as placeholder
image_data = np.ones((height, width, 4), dtype=np.float32)
image_data[:, :, 0] = props.background_color[0] # R
image_data[:, :, 1] = props.background_color[1] # G
image_data[:, :, 2] = props.background_color[2] # B
image_data[:, :, 3] = 1.0 # A
# Create Blender image
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)
blender_img.pixels[:] = image_data.flatten()
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.enable_normal_map:
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,
blender_img,
props.normal_map_strength,
props.normal_map_blur_radius,
props.invert_normal_map
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] Texture generation completed successfully")
print(f"[TTG DEBUG] All steps completed successfully")
return blender_img, normal_map_img
except Exception as e:

View File

@@ -47,8 +47,16 @@ class TEXT_TEXTURE_OT_generate(Operator):
self.report({'ERROR'}, "Failed to generate diffuse image")
return {'CANCELLED'}
# Create diffuse texture
img_name = f"TextTexture_{props.text[:20].replace(' ', '_')}"
# Create diffuse texture with proper sanitization
import re
# Remove non-ASCII and non-alphanumeric characters, keep spaces temporarily
sanitized_text = re.sub(r'[^\w\s-]', '', props.text[:20])
# Replace spaces and multiple underscores/dashes with single underscores
sanitized_text = re.sub(r'[\s_-]+', '_', sanitized_text).strip('_')
# Fallback if text becomes empty
if not sanitized_text:
sanitized_text = "Texture"
img_name = f"TextTexture_{sanitized_text}"
# Remove existing diffuse texture
if img_name in bpy.data.images:
@@ -57,20 +65,15 @@ class TEXT_TEXTURE_OT_generate(Operator):
# Create new diffuse image with USER SPECIFIED dimensions
blender_img = bpy.data.images.new(img_name, final_width, final_height, alpha=True)
# Convert PIL to Blender for diffuse texture
pixels = []
for y in range(final_height):
for x in range(final_width):
r, g, b, a = img.getpixel((x, final_height - 1 - y))
pixels.extend([r/255.0, g/255.0, b/255.0, a/255.0])
blender_img.pixels = pixels
# Directly copy pixel data from generated image to Blender image
# The generated image is already in the correct format, just copy it
blender_img.pixels[:] = img.pixels[:]
blender_img.pack() # PACK FINAL IMAGE
# Create normal map texture if enabled and generated
normal_map_blender_img = None
if normal_map_img and props.generate_normal_map:
normal_map_name = f"TextTexture_Normal_{props.text[:20].replace(' ', '_')}"
normal_map_name = f"TextTexture_Normal_{sanitized_text}"
# Remove existing normal map texture
if normal_map_name in bpy.data.images:
@@ -80,14 +83,12 @@ class TEXT_TEXTURE_OT_generate(Operator):
normal_map_blender_img = bpy.data.images.new(normal_map_name, final_width, final_height, alpha=False)
normal_map_blender_img.colorspace_settings.name = 'Non-Color' # Important for normal maps
# Convert PIL to Blender for normal map
normal_pixels = []
for y in range(final_height):
for x in range(final_width):
r, g, b = normal_map_img.getpixel((x, final_height - 1 - y))
normal_pixels.extend([r/255.0, g/255.0, b/255.0, 1.0]) # Alpha always 1.0 for normal maps
normal_map_blender_img.pixels = normal_pixels
# Directly copy normal map pixel data
# Copy RGB channels and set alpha to 1.0
temp_pixels = list(normal_map_img.pixels[:])
for i in range(3, len(temp_pixels), 4):
temp_pixels[i] = 1.0 # Set alpha to 1.0 for normal maps
normal_map_blender_img.pixels[:] = temp_pixels
normal_map_blender_img.pack() # PACK NORMAL MAP
print(f"[Normal Map] Created and packed normal map texture: {normal_map_name}")
@@ -134,82 +135,110 @@ class TEXT_TEXTURE_OT_generate_shader(Operator):
def execute(self, context):
try:
print(f"[TTG SHADER DEBUG] === SHADER GENERATION STARTED ===")
props = context.scene.text_texture_props
# Generate texture first
final_width = props.texture_width
final_height = props.texture_height
print(f"Generating shader with texture at {final_width}x{final_height}px")
print(f"[TTG SHADER DEBUG] Step 1: Starting texture generation at {final_width}x{final_height}px")
result = generate_texture_image(props, final_width, final_height)
print(f"[TTG SHADER DEBUG] Step 2: Texture generation returned: {type(result)}")
if not result:
self.report({'ERROR'}, "Failed to generate texture image")
return {'CANCELLED'}
print(f"[TTG SHADER DEBUG] Step 3: Processing result...")
# Extract diffuse and normal map images
if isinstance(result, tuple):
img, normal_map_img = result
print(f"[TTG SHADER DEBUG] Step 3a: Got tuple result - img: {type(img)}, normal: {type(normal_map_img)}")
else:
# Backward compatibility
img = result
normal_map_img = None
print(f"[TTG SHADER DEBUG] Step 3b: Got single result - img: {type(img)}")
if not img:
self.report({'ERROR'}, "Failed to generate diffuse image")
return {'CANCELLED'}
# Create diffuse texture name
img_name = f"TextTexture_{props.text[:20].replace(' ', '_')}"
print(f"[TTG SHADER DEBUG] Step 4: Image validation passed, proceeding with shader creation...")
print(f"[TTG SHADER DEBUG] Step 5: Creating diffuse texture...")
# Create diffuse texture name with proper sanitization
import re
# Remove non-ASCII and non-alphanumeric characters, keep spaces temporarily
sanitized_text = re.sub(r'[^\w\s-]', '', props.text[:20])
# Replace spaces and multiple underscores/dashes with single underscores
sanitized_text = re.sub(r'[\s_-]+', '_', sanitized_text).strip('_')
# Fallback if text becomes empty
if not sanitized_text:
sanitized_text = "Texture"
img_name = f"TextTexture_{sanitized_text}"
print(f"[TTG SHADER DEBUG] Step 5a: Sanitized texture name: {img_name}")
# Remove existing diffuse texture if it exists
if img_name in bpy.data.images:
print(f"[TTG SHADER DEBUG] Step 5b: Removing existing texture")
bpy.data.images.remove(bpy.data.images[img_name])
print(f"[TTG SHADER DEBUG] Step 5c: Creating new Blender image...")
# Create new diffuse texture
blender_img = bpy.data.images.new(img_name, final_width, final_height, alpha=True)
print(f"[TTG SHADER DEBUG] Step 5d: New image created, copying pixel data...")
# Convert PIL to Blender for diffuse texture
pixels = []
for y in range(final_height):
for x in range(final_width):
r, g, b, a = img.getpixel((x, final_height - 1 - y))
pixels.extend([r/255.0, g/255.0, b/255.0, a/255.0])
blender_img.pixels = pixels
# Directly copy pixel data from generated image to Blender image
# The generated image is already in the correct format, just copy it
blender_img.pixels[:] = img.pixels[:]
print(f"[TTG SHADER DEBUG] Step 5e: Pixel data copied, packing image...")
blender_img.pack()
print(f"[TTG SHADER DEBUG] Step 5f: Image packed successfully")
print(f"[TTG SHADER DEBUG] Step 6: Processing normal maps...")
# Create normal map texture if enabled and generated
normal_map_blender_img = None
if normal_map_img and props.generate_normal_map:
normal_map_name = f"TextTexture_Normal_{props.text[:20].replace(' ', '_')}"
print(f"[TTG SHADER DEBUG] Step 6a: Normal map enabled and generated, processing...")
# Use the same sanitized text for consistency
normal_map_name = f"TextTexture_Normal_{sanitized_text}"
print(f"[TTG SHADER DEBUG] Step 6b: Sanitized normal map name: {normal_map_name}")
# Remove existing normal map texture
if normal_map_name in bpy.data.images:
print(f"[TTG SHADER DEBUG] Step 6c: Removing existing normal map")
bpy.data.images.remove(bpy.data.images[normal_map_name])
print(f"[TTG SHADER DEBUG] Step 6d: Creating normal map image...")
# Create new normal map image
normal_map_blender_img = bpy.data.images.new(normal_map_name, final_width, final_height, alpha=False)
normal_map_blender_img.colorspace_settings.name = 'Non-Color' # Important for normal maps
print(f"[TTG SHADER DEBUG] Step 6e: Normal map image created, copying pixels...")
# Convert PIL to Blender for normal map
normal_pixels = []
for y in range(final_height):
for x in range(final_width):
r, g, b = normal_map_img.getpixel((x, final_height - 1 - y))
normal_pixels.extend([r/255.0, g/255.0, b/255.0, 1.0]) # Alpha always 1.0 for normal maps
normal_map_blender_img.pixels = normal_pixels
# Directly copy normal map pixel data
# Copy RGB channels and set alpha to 1.0
temp_pixels = list(normal_map_img.pixels[:])
for i in range(3, len(temp_pixels), 4):
temp_pixels[i] = 1.0 # Set alpha to 1.0 for normal maps
normal_map_blender_img.pixels[:] = temp_pixels
print(f"[TTG SHADER DEBUG] Step 6f: Normal map pixels copied, packing...")
normal_map_blender_img.pack()
print(f"[Normal Map] Created normal map texture for shader: {normal_map_name}")
print(f"[TTG SHADER DEBUG] Step 6g: Normal map packed successfully")
else:
print(f"[TTG SHADER DEBUG] Step 6: Normal map generation skipped")
print(f"[TTG SHADER DEBUG] Step 7: Creating material...")
# Create or get material using custom name or fallback
if props.custom_material_name.strip():
material_name = props.custom_material_name.strip()
print(f"[TTG SHADER DEBUG] Step 7a: Using custom material name: {material_name}")
else:
# Fallback to text-based naming
clean_text = props.text[:15].replace(' ', '_').replace('\n', '_')
material_name = f"TextTexture_Mat_{clean_text}"
# Use the same sanitized text for consistency
material_name = f"TextTexture_Mat_{sanitized_text}"
print(f"[TTG SHADER DEBUG] Step 7b: Using sanitized material name: {material_name}")
if material_name in bpy.data.materials:
mat = bpy.data.materials[material_name]