wip
This commit is contained in:
BIN
dist/text_texture_generator_v1.0.0.zip
vendored
BIN
dist/text_texture_generator_v1.0.0.zip
vendored
Binary file not shown.
BIN
dist/text_texture_generator_v1.0.0_free.zip
vendored
BIN
dist/text_texture_generator_v1.0.0_free.zip
vendored
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -52,8 +52,8 @@ def generate_texture_image(props, width, height):
|
|||||||
lanczos_filter = getattr(resampling_module, 'LANCZOS', getattr(Image, 'LANCZOS', Image.BICUBIC))
|
lanczos_filter = getattr(resampling_module, 'LANCZOS', getattr(Image, 'LANCZOS', Image.BICUBIC))
|
||||||
bicubic_filter = getattr(resampling_module, 'BICUBIC', getattr(Image, 'BICUBIC', Image.BILINEAR))
|
bicubic_filter = getattr(resampling_module, 'BICUBIC', getattr(Image, 'BICUBIC', Image.BILINEAR))
|
||||||
|
|
||||||
def _iter_enabled_overlays():
|
def _iter_enabled_components():
|
||||||
"""Yield (overlay, image_path) tuples for overlays that should be applied."""
|
"""Yield enabled composition components."""
|
||||||
overlays = getattr(props, 'image_overlays', [])
|
overlays = getattr(props, 'image_overlays', [])
|
||||||
try:
|
try:
|
||||||
overlay_list = list(overlays)
|
overlay_list = list(overlays)
|
||||||
@@ -62,17 +62,14 @@ def generate_texture_image(props, width, height):
|
|||||||
for overlay in overlay_list:
|
for overlay in overlay_list:
|
||||||
if not getattr(overlay, 'enabled', True):
|
if not getattr(overlay, 'enabled', True):
|
||||||
continue
|
continue
|
||||||
image_path = getattr(overlay, 'image_path', '')
|
yield overlay
|
||||||
if not image_path:
|
|
||||||
continue
|
|
||||||
yield overlay, image_path
|
|
||||||
|
|
||||||
def _calculate_overlay_position(overlay, overlay_size):
|
def _calculate_overlay_position(overlay, overlay_size):
|
||||||
"""Calculate top-left pixel position for an overlay image."""
|
"""Calculate top-left pixel position for an overlay image."""
|
||||||
overlay_width, overlay_height = overlay_size
|
overlay_width, overlay_height = overlay_size
|
||||||
canvas_width = width
|
canvas_width = width
|
||||||
canvas_height = height
|
canvas_height = height
|
||||||
mode = getattr(overlay, 'positioning_mode', 'ABSOLUTE') or 'ABSOLUTE'
|
mode = getattr(overlay, 'placement_mode', 'ABSOLUTE') or 'ABSOLUTE'
|
||||||
|
|
||||||
x_norm = float(getattr(overlay, 'x_position', 0.5) or 0.5)
|
x_norm = float(getattr(overlay, 'x_position', 0.5) or 0.5)
|
||||||
y_norm = float(getattr(overlay, 'y_position', 0.5) or 0.5)
|
y_norm = float(getattr(overlay, 'y_position', 0.5) or 0.5)
|
||||||
@@ -95,16 +92,57 @@ def generate_texture_image(props, width, height):
|
|||||||
y = max(0, min(max_y, y))
|
y = max(0, min(max_y, y))
|
||||||
return x, y
|
return x, y
|
||||||
|
|
||||||
def _apply_image_overlays(pil_canvas):
|
def _apply_composition_components(pil_canvas):
|
||||||
"""Composite enabled image overlays onto the base canvas."""
|
"""Composite enabled components (text or image) onto the base canvas."""
|
||||||
overlays_to_apply = sorted(
|
components = sorted(
|
||||||
_iter_enabled_overlays(),
|
_iter_enabled_components(),
|
||||||
key=lambda item: getattr(item[0], 'z_index', 0)
|
key=lambda comp: getattr(comp, 'z_index', 0)
|
||||||
)
|
)
|
||||||
|
|
||||||
for overlay, image_path in overlays_to_apply:
|
for component in components:
|
||||||
resolved_path = image_path
|
if getattr(component, 'component_type', 'IMAGE') == 'TEXT':
|
||||||
scale = getattr(overlay, 'scale', 1.0)
|
text_value = getattr(component, 'text_content', '').strip()
|
||||||
|
if not text_value:
|
||||||
|
continue
|
||||||
|
scale = getattr(component, 'scale', 1.0)
|
||||||
|
try:
|
||||||
|
scale = float(scale if scale is not None else 1.0)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
scale = 1.0
|
||||||
|
text_size = max(8, int(round(props.font_size * max(scale, 0.1))))
|
||||||
|
text_font = load_font_for_size(text_size)
|
||||||
|
try:
|
||||||
|
text_bbox = text_font.getbbox(text_value)
|
||||||
|
text_width = text_bbox[2] - text_bbox[0]
|
||||||
|
text_height = text_bbox[3] - text_bbox[1]
|
||||||
|
except AttributeError:
|
||||||
|
try:
|
||||||
|
text_width, text_height = text_font.getsize(text_value)
|
||||||
|
except AttributeError:
|
||||||
|
# Fallback to a temporary draw context
|
||||||
|
tmp_draw = ImageDraw.Draw(Image.new('RGBA', (1, 1)))
|
||||||
|
text_width, text_height = tmp_draw.textsize(text_value, font=text_font)
|
||||||
|
|
||||||
|
overlay_img = Image.new('RGBA', (text_width, text_height), (0, 0, 0, 0))
|
||||||
|
overlay_draw = ImageDraw.Draw(overlay_img)
|
||||||
|
text_color = tuple(int(c * 255) for c in props.text_color[:4])
|
||||||
|
overlay_draw.text((0, 0), text_value, font=text_font, fill=text_color)
|
||||||
|
|
||||||
|
rotation = float(getattr(component, 'rotation', 0.0) or 0.0)
|
||||||
|
if rotation % 360:
|
||||||
|
overlay_img = overlay_img.rotate(-rotation, expand=True, resample=bicubic_filter)
|
||||||
|
|
||||||
|
x_pos, y_pos = _calculate_overlay_position(component, overlay_img.size)
|
||||||
|
|
||||||
|
overlay_layer = Image.new('RGBA', (width, height), (0, 0, 0, 0))
|
||||||
|
overlay_layer.paste(overlay_img, (x_pos, y_pos), overlay_img)
|
||||||
|
pil_canvas = Image.alpha_composite(pil_canvas, overlay_layer)
|
||||||
|
continue
|
||||||
|
|
||||||
|
resolved_path = getattr(component, 'image_path', '')
|
||||||
|
if not resolved_path:
|
||||||
|
continue
|
||||||
|
scale = getattr(component, 'scale', 1.0)
|
||||||
try:
|
try:
|
||||||
scale = float(scale if scale is not None else 1.0)
|
scale = float(scale if scale is not None else 1.0)
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
@@ -121,7 +159,7 @@ def generate_texture_image(props, width, height):
|
|||||||
resolved_path = raster_info.path
|
resolved_path = raster_info.path
|
||||||
scale_to_apply = raster_info.applied_scale
|
scale_to_apply = raster_info.applied_scale
|
||||||
except SVGConversionError as svg_error:
|
except SVGConversionError as svg_error:
|
||||||
print(f"WARNING: Failed to rasterize SVG overlay '{image_path}': {svg_error}")
|
print(f"WARNING: Failed to rasterize SVG overlay '{component.image_path}': {svg_error}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -139,11 +177,11 @@ def generate_texture_image(props, width, height):
|
|||||||
if new_size != overlay_img.size:
|
if new_size != overlay_img.size:
|
||||||
overlay_img = overlay_img.resize(new_size, lanczos_filter)
|
overlay_img = overlay_img.resize(new_size, lanczos_filter)
|
||||||
|
|
||||||
rotation = float(getattr(overlay, 'rotation', 0.0) or 0.0)
|
rotation = float(getattr(component, 'rotation', 0.0) or 0.0)
|
||||||
if rotation % 360:
|
if rotation % 360:
|
||||||
overlay_img = overlay_img.rotate(-rotation, expand=True, resample=bicubic_filter)
|
overlay_img = overlay_img.rotate(-rotation, expand=True, resample=bicubic_filter)
|
||||||
|
|
||||||
x_pos, y_pos = _calculate_overlay_position(overlay, overlay_img.size)
|
x_pos, y_pos = _calculate_overlay_position(component, overlay_img.size)
|
||||||
|
|
||||||
overlay_layer = Image.new('RGBA', (width, height), (0, 0, 0, 0))
|
overlay_layer = Image.new('RGBA', (width, height), (0, 0, 0, 0))
|
||||||
overlay_layer.paste(overlay_img, (x_pos, y_pos), overlay_img)
|
overlay_layer.paste(overlay_img, (x_pos, y_pos), overlay_img)
|
||||||
@@ -358,7 +396,7 @@ def generate_texture_image(props, width, height):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
# Apply any configured image overlays before converting to Blender pixels
|
# Apply any configured image overlays before converting to Blender pixels
|
||||||
pil_img = _apply_image_overlays(pil_img)
|
pil_img = _apply_composition_components(pil_img)
|
||||||
|
|
||||||
# Convert PIL image to Blender format (flip vertically to fix mirroring)
|
# Convert PIL image to Blender format (flip vertically to fix mirroring)
|
||||||
pil_img = pil_img.transpose(Image.FLIP_TOP_BOTTOM) # Fix mirroring issue
|
pil_img = pil_img.transpose(Image.FLIP_TOP_BOTTOM) # Fix mirroring issue
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -139,6 +139,11 @@ class TEXT_TEXTURE_OT_save_preset(Operator):
|
|||||||
pass
|
pass
|
||||||
overlay_data = {
|
overlay_data = {
|
||||||
'name': overlay.name,
|
'name': overlay.name,
|
||||||
|
'component_type': overlay.component_type,
|
||||||
|
'placement_mode': overlay.placement_mode,
|
||||||
|
'text_content': overlay.text_content,
|
||||||
|
'text_spacing': overlay.text_spacing,
|
||||||
|
'image_spacing': overlay.image_spacing,
|
||||||
'image_path': overlay.image_path,
|
'image_path': overlay.image_path,
|
||||||
'x_position': overlay.x_position,
|
'x_position': overlay.x_position,
|
||||||
'y_position': overlay.y_position,
|
'y_position': overlay.y_position,
|
||||||
@@ -397,6 +402,11 @@ class TEXT_TEXTURE_OT_load_preset(Operator):
|
|||||||
pass
|
pass
|
||||||
overlay = props.image_overlays.add()
|
overlay = props.image_overlays.add()
|
||||||
overlay.name = overlay_data.get('name', 'Overlay')
|
overlay.name = overlay_data.get('name', 'Overlay')
|
||||||
|
overlay.component_type = overlay_data.get('component_type', overlay.component_type)
|
||||||
|
overlay.placement_mode = overlay_data.get('placement_mode', 'ABSOLUTE')
|
||||||
|
overlay.text_content = overlay_data.get('text_content', '')
|
||||||
|
overlay.text_spacing = overlay_data.get('text_spacing', overlay.text_spacing)
|
||||||
|
overlay.image_spacing = overlay_data.get('image_spacing', overlay.image_spacing)
|
||||||
overlay.image_path = overlay_data.get('image_path', '')
|
overlay.image_path = overlay_data.get('image_path', '')
|
||||||
overlay.x_position = overlay_data.get('x_position', 0.5)
|
overlay.x_position = overlay_data.get('x_position', 0.5)
|
||||||
overlay.y_position = overlay_data.get('y_position', 0.5)
|
overlay.y_position = overlay_data.get('y_position', 0.5)
|
||||||
|
|||||||
@@ -341,18 +341,30 @@ class TEXT_TEXTURE_OT_refresh_presets(Operator):
|
|||||||
|
|
||||||
class TEXT_TEXTURE_OT_add_overlay(Operator):
|
class TEXT_TEXTURE_OT_add_overlay(Operator):
|
||||||
pass
|
pass
|
||||||
"""Add new image overlay"""
|
"""Add new composition component"""
|
||||||
bl_idname = "text_texture.add_overlay"
|
bl_idname = "text_texture.add_overlay"
|
||||||
bl_label = "Add Overlay"
|
bl_label = "Add Component"
|
||||||
bl_description = "Add new image overlay"
|
bl_description = "Add new composition component"
|
||||||
bl_options = {'REGISTER', 'UNDO'}
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
|
placement_mode: EnumProperty(
|
||||||
|
name="Placement Mode",
|
||||||
|
items=[
|
||||||
|
('ABSOLUTE', "Absolute", ""),
|
||||||
|
('PREPEND', "Prepend", ""),
|
||||||
|
('APPEND', "Append", "")
|
||||||
|
],
|
||||||
|
default='ABSOLUTE'
|
||||||
|
)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
pass
|
pass
|
||||||
props = context.scene.text_texture_props
|
props = context.scene.text_texture_props
|
||||||
|
|
||||||
overlay = props.image_overlays.add()
|
overlay = props.image_overlays.add()
|
||||||
overlay.name = f"Overlay {len(props.image_overlays)}"
|
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
|
props.active_overlay_index = len(props.image_overlays) - 1
|
||||||
|
|
||||||
# Force UI redraw
|
# Force UI redraw
|
||||||
@@ -360,7 +372,7 @@ class TEXT_TEXTURE_OT_add_overlay(Operator):
|
|||||||
pass
|
pass
|
||||||
area.tag_redraw()
|
area.tag_redraw()
|
||||||
|
|
||||||
self.report({'INFO'}, f"Added overlay: {overlay.name}")
|
self.report({'INFO'}, f"Added component: {overlay.name}")
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
@@ -435,10 +447,10 @@ class TEXT_TEXTURE_OT_sync_margins(Operator):
|
|||||||
|
|
||||||
class TEXT_TEXTURE_OT_duplicate_overlay(Operator):
|
class TEXT_TEXTURE_OT_duplicate_overlay(Operator):
|
||||||
pass
|
pass
|
||||||
"""Duplicate image overlay"""
|
"""Duplicate a composition component"""
|
||||||
bl_idname = "text_texture.duplicate_overlay"
|
bl_idname = "text_texture.duplicate_overlay"
|
||||||
bl_label = "Duplicate Overlay"
|
bl_label = "Duplicate Component"
|
||||||
bl_description = "Duplicate this image overlay"
|
bl_description = "Duplicate this composition component"
|
||||||
bl_options = {'REGISTER', 'UNDO'}
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
overlay_index: IntProperty()
|
overlay_index: IntProperty()
|
||||||
@@ -457,7 +469,9 @@ class TEXT_TEXTURE_OT_duplicate_overlay(Operator):
|
|||||||
|
|
||||||
# Copy all properties with explicit verification
|
# Copy all properties with explicit verification
|
||||||
new_overlay.name = f"{source_overlay.name} Copy"
|
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.image_path = source_overlay.image_path
|
||||||
|
new_overlay.text_content = source_overlay.text_content
|
||||||
new_overlay.x_position = source_overlay.x_position
|
new_overlay.x_position = source_overlay.x_position
|
||||||
new_overlay.y_position = source_overlay.y_position
|
new_overlay.y_position = source_overlay.y_position
|
||||||
new_overlay.scale = source_overlay.scale
|
new_overlay.scale = source_overlay.scale
|
||||||
@@ -465,7 +479,7 @@ class TEXT_TEXTURE_OT_duplicate_overlay(Operator):
|
|||||||
# Fix z-index assignment for duplicated overlays to ensure proper visibility
|
# Fix z-index assignment for duplicated overlays to ensure proper visibility
|
||||||
# For PREPEND/APPEND overlays, increment z_index to ensure duplicate appears above original
|
# For PREPEND/APPEND overlays, increment z_index to ensure duplicate appears above original
|
||||||
# This prevents the duplicate from being rendered behind the original overlay
|
# This prevents the duplicate from being rendered behind the original overlay
|
||||||
if source_overlay.positioning_mode in ['PREPEND', 'APPEND']:
|
if source_overlay.placement_mode in ['PREPEND', 'APPEND']:
|
||||||
pass
|
pass
|
||||||
# Increment z_index to ensure proper layering order for duplicated overlay
|
# 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
|
new_overlay.z_index = min(source_overlay.z_index + 1, 10) # Cap at max z_index value
|
||||||
@@ -473,7 +487,7 @@ class TEXT_TEXTURE_OT_duplicate_overlay(Operator):
|
|||||||
pass
|
pass
|
||||||
# For ABSOLUTE overlays, keep same z_index (different positions so no overlap issue)
|
# For ABSOLUTE overlays, keep same z_index (different positions so no overlap issue)
|
||||||
new_overlay.z_index = source_overlay.z_index
|
new_overlay.z_index = source_overlay.z_index
|
||||||
new_overlay.positioning_mode = source_overlay.positioning_mode
|
new_overlay.placement_mode = source_overlay.placement_mode
|
||||||
new_overlay.text_spacing = source_overlay.text_spacing
|
new_overlay.text_spacing = source_overlay.text_spacing
|
||||||
new_overlay.image_spacing = source_overlay.image_spacing
|
new_overlay.image_spacing = source_overlay.image_spacing
|
||||||
new_overlay.enabled = source_overlay.enabled
|
new_overlay.enabled = source_overlay.enabled
|
||||||
@@ -517,7 +531,7 @@ class TEXT_TEXTURE_OT_duplicate_overlay(Operator):
|
|||||||
props.is_updating = False
|
props.is_updating = False
|
||||||
|
|
||||||
|
|
||||||
self.report({'INFO'}, f"✅ Duplicated overlay: {source_overlay.name}")
|
self.report({'INFO'}, f"✅ Duplicated component: {source_overlay.name}")
|
||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
self.report({'ERROR'}, "Invalid overlay index")
|
self.report({'ERROR'}, "Invalid overlay index")
|
||||||
@@ -528,10 +542,10 @@ class TEXT_TEXTURE_OT_duplicate_overlay(Operator):
|
|||||||
|
|
||||||
class TEXT_TEXTURE_OT_delete_overlay(Operator):
|
class TEXT_TEXTURE_OT_delete_overlay(Operator):
|
||||||
pass
|
pass
|
||||||
"""Delete image overlay"""
|
"""Delete a composition component"""
|
||||||
bl_idname = "text_texture.delete_overlay"
|
bl_idname = "text_texture.delete_overlay"
|
||||||
bl_label = "Delete Overlay"
|
bl_label = "Delete Component"
|
||||||
bl_description = "Delete this image overlay"
|
bl_description = "Delete this composition component"
|
||||||
bl_options = {'REGISTER', 'UNDO'}
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
overlay_index: IntProperty()
|
overlay_index: IntProperty()
|
||||||
@@ -567,7 +581,7 @@ class TEXT_TEXTURE_OT_delete_overlay(Operator):
|
|||||||
pass
|
pass
|
||||||
area.tag_redraw()
|
area.tag_redraw()
|
||||||
|
|
||||||
self.report({'INFO'}, f"Deleted overlay: {overlay_name}")
|
self.report({'INFO'}, f"Deleted component: {overlay_name}")
|
||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
self.report({'ERROR'}, "Invalid overlay index")
|
self.report({'ERROR'}, "Invalid overlay index")
|
||||||
@@ -578,10 +592,10 @@ class TEXT_TEXTURE_OT_delete_overlay(Operator):
|
|||||||
|
|
||||||
class TEXT_TEXTURE_OT_move_overlay(Operator):
|
class TEXT_TEXTURE_OT_move_overlay(Operator):
|
||||||
pass
|
pass
|
||||||
"""Move image overlay up or down in the list"""
|
"""Move a composition component within its placement group"""
|
||||||
bl_idname = "text_texture.move_overlay"
|
bl_idname = "text_texture.move_overlay"
|
||||||
bl_label = "Move Overlay"
|
bl_label = "Move Component"
|
||||||
bl_description = "Move overlay up or down in the list"
|
bl_description = "Move component up or down within its placement group"
|
||||||
bl_options = {'REGISTER', 'UNDO'}
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
overlay_index: IntProperty()
|
overlay_index: IntProperty()
|
||||||
@@ -601,12 +615,15 @@ class TEXT_TEXTURE_OT_move_overlay(Operator):
|
|||||||
pass
|
pass
|
||||||
current_index = self.overlay_index
|
current_index = self.overlay_index
|
||||||
new_index = current_index
|
new_index = current_index
|
||||||
|
target_mode = props.image_overlays[current_index].placement_mode
|
||||||
|
|
||||||
if self.direction == 'UP' and current_index > 0:
|
step = -1 if self.direction == 'UP' else 1
|
||||||
pass
|
probe = current_index + step
|
||||||
new_index = current_index - 1
|
while 0 <= probe < len(props.image_overlays):
|
||||||
elif self.direction == 'DOWN' and current_index < len(props.image_overlays) - 1:
|
if props.image_overlays[probe].placement_mode == target_mode:
|
||||||
new_index = current_index + 1
|
new_index = probe
|
||||||
|
break
|
||||||
|
probe += step
|
||||||
|
|
||||||
if new_index != current_index:
|
if new_index != current_index:
|
||||||
pass
|
pass
|
||||||
@@ -622,10 +639,10 @@ class TEXT_TEXTURE_OT_move_overlay(Operator):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self.report({'INFO'}, f"Moved overlay {self.direction.lower()}")
|
self.report({'INFO'}, f"Moved component {self.direction.lower()}")
|
||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
self.report({'INFO'}, f"Overlay is already at {self.direction.lower()} edge")
|
self.report({'INFO'}, f"Component is already at {self.direction.lower()} edge")
|
||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
self.report({'ERROR'}, "Invalid overlay index")
|
self.report({'ERROR'}, "Invalid overlay index")
|
||||||
@@ -647,4 +664,4 @@ __all__ = [
|
|||||||
'TEXT_TEXTURE_OT_duplicate_overlay',
|
'TEXT_TEXTURE_OT_duplicate_overlay',
|
||||||
'TEXT_TEXTURE_OT_delete_overlay',
|
'TEXT_TEXTURE_OT_delete_overlay',
|
||||||
'TEXT_TEXTURE_OT_move_overlay',
|
'TEXT_TEXTURE_OT_move_overlay',
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,13 +6,15 @@ for the text texture generator addon.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from .property_groups import (
|
from .property_groups import (
|
||||||
|
TEXT_TEXTURE_CompositionComponent,
|
||||||
TEXT_TEXTURE_ImageOverlay,
|
TEXT_TEXTURE_ImageOverlay,
|
||||||
TEXT_TEXTURE_Preset,
|
TEXT_TEXTURE_Preset,
|
||||||
TEXT_TEXTURE_Properties
|
TEXT_TEXTURE_Properties
|
||||||
)
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
'TEXT_TEXTURE_CompositionComponent',
|
||||||
'TEXT_TEXTURE_ImageOverlay',
|
'TEXT_TEXTURE_ImageOverlay',
|
||||||
'TEXT_TEXTURE_Preset',
|
'TEXT_TEXTURE_Preset',
|
||||||
'TEXT_TEXTURE_Properties',
|
'TEXT_TEXTURE_Properties',
|
||||||
]
|
]
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -50,19 +50,77 @@ def get_font_enum_items(self, context):
|
|||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
||||||
class TEXT_TEXTURE_ImageOverlay(PropertyGroup):
|
class TEXT_TEXTURE_CompositionComponent(PropertyGroup):
|
||||||
pass
|
pass
|
||||||
"""Single image overlay item"""
|
"""Single composition component that can be text or image."""
|
||||||
name: StringProperty(
|
name: StringProperty(
|
||||||
name="Image Name",
|
name="Component Name",
|
||||||
description="Name identifier for this image overlay",
|
description="Name identifier for this component",
|
||||||
default="Overlay",
|
default="Component",
|
||||||
|
update=update_live_preview
|
||||||
|
)
|
||||||
|
|
||||||
|
enabled: BoolProperty(
|
||||||
|
name="Enabled",
|
||||||
|
description="Enable this component",
|
||||||
|
default=True,
|
||||||
|
update=update_live_preview
|
||||||
|
)
|
||||||
|
|
||||||
|
component_type: EnumProperty(
|
||||||
|
name="Component Type",
|
||||||
|
description="Select whether this component renders text or an image",
|
||||||
|
items=[
|
||||||
|
('TEXT', "Text", "Render a text snippet as part of the composition"),
|
||||||
|
('IMAGE', "Image", "Render an image file as part of the composition"),
|
||||||
|
],
|
||||||
|
default='IMAGE',
|
||||||
|
update=update_live_preview
|
||||||
|
)
|
||||||
|
|
||||||
|
placement_mode: EnumProperty(
|
||||||
|
name="Placement",
|
||||||
|
description="Control how this component attaches to the main layout",
|
||||||
|
items=[
|
||||||
|
('ABSOLUTE', "Absolute", "Position using explicit coordinates"),
|
||||||
|
('PREPEND', "Prepend", "Place before the main text"),
|
||||||
|
('APPEND', "Append", "Place after the main text"),
|
||||||
|
],
|
||||||
|
default='ABSOLUTE',
|
||||||
|
update=update_live_preview
|
||||||
|
)
|
||||||
|
|
||||||
|
text_content: StringProperty(
|
||||||
|
name="Text",
|
||||||
|
description="Text content for this component when using text mode",
|
||||||
|
default="",
|
||||||
|
maxlen=512,
|
||||||
|
update=update_live_preview
|
||||||
|
)
|
||||||
|
|
||||||
|
text_spacing: FloatProperty(
|
||||||
|
name="Spacing (%)",
|
||||||
|
description="Spacing between main text and this component as percentage (0-100%)",
|
||||||
|
default=10.0,
|
||||||
|
min=0.0,
|
||||||
|
max=100.0,
|
||||||
|
subtype='PERCENTAGE',
|
||||||
|
update=update_live_preview
|
||||||
|
)
|
||||||
|
|
||||||
|
image_spacing: FloatProperty(
|
||||||
|
name="Component Spacing (%)",
|
||||||
|
description="Spacing between consecutive components (0-100%)",
|
||||||
|
default=5.0,
|
||||||
|
min=0.0,
|
||||||
|
max=100.0,
|
||||||
|
subtype='PERCENTAGE',
|
||||||
update=update_live_preview
|
update=update_live_preview
|
||||||
)
|
)
|
||||||
|
|
||||||
image_path: StringProperty(
|
image_path: StringProperty(
|
||||||
name="Image File",
|
name="Image File",
|
||||||
description="Path to image file",
|
description="Path to image file for image components",
|
||||||
subtype='FILE_PATH',
|
subtype='FILE_PATH',
|
||||||
default="",
|
default="",
|
||||||
update=update_live_preview
|
update=update_live_preview
|
||||||
@@ -88,7 +146,7 @@ class TEXT_TEXTURE_ImageOverlay(PropertyGroup):
|
|||||||
|
|
||||||
scale: FloatProperty(
|
scale: FloatProperty(
|
||||||
name="Scale",
|
name="Scale",
|
||||||
description="Scale of the image",
|
description="Scale factor for this component",
|
||||||
default=1.0,
|
default=1.0,
|
||||||
min=0.1,
|
min=0.1,
|
||||||
max=5.0,
|
max=5.0,
|
||||||
@@ -97,7 +155,7 @@ class TEXT_TEXTURE_ImageOverlay(PropertyGroup):
|
|||||||
|
|
||||||
rotation: FloatProperty(
|
rotation: FloatProperty(
|
||||||
name="Rotation",
|
name="Rotation",
|
||||||
description="Rotation in degrees",
|
description="Rotation in degrees (image components only)",
|
||||||
default=0.0,
|
default=0.0,
|
||||||
min=-360.0,
|
min=-360.0,
|
||||||
max=360.0,
|
max=360.0,
|
||||||
@@ -112,45 +170,9 @@ class TEXT_TEXTURE_ImageOverlay(PropertyGroup):
|
|||||||
max=10,
|
max=10,
|
||||||
update=update_live_preview
|
update=update_live_preview
|
||||||
)
|
)
|
||||||
|
|
||||||
positioning_mode: EnumProperty(
|
# Backwards compatibility alias (legacy name used throughout codebase)
|
||||||
name="Positioning Mode",
|
TEXT_TEXTURE_ImageOverlay = TEXT_TEXTURE_CompositionComponent
|
||||||
description="How the image overlay is positioned relative to the text",
|
|
||||||
items=[
|
|
||||||
('ABSOLUTE', "Absolute", "Position overlay at absolute coordinates (legacy mode)"),
|
|
||||||
('APPEND', "Append", "Position overlay after the text content"),
|
|
||||||
('PREPEND', "Prepend", "Position overlay before the text content")
|
|
||||||
],
|
|
||||||
default='ABSOLUTE',
|
|
||||||
update=update_live_preview
|
|
||||||
)
|
|
||||||
|
|
||||||
text_spacing: FloatProperty(
|
|
||||||
name="Text Spacing (%)",
|
|
||||||
description="Spacing between text and image as percentage of canvas width (0-100%)",
|
|
||||||
default=10.0,
|
|
||||||
min=0.0,
|
|
||||||
max=100.0,
|
|
||||||
subtype='PERCENTAGE',
|
|
||||||
update=update_live_preview
|
|
||||||
)
|
|
||||||
|
|
||||||
image_spacing: FloatProperty(
|
|
||||||
name="Image Spacing (%)",
|
|
||||||
description="Spacing between consecutive images as percentage of canvas width (0-100%)",
|
|
||||||
default=5.0,
|
|
||||||
min=0.0,
|
|
||||||
max=100.0,
|
|
||||||
subtype='PERCENTAGE',
|
|
||||||
update=update_live_preview
|
|
||||||
)
|
|
||||||
|
|
||||||
enabled: BoolProperty(
|
|
||||||
name="Enabled",
|
|
||||||
description="Enable this image overlay",
|
|
||||||
default=True,
|
|
||||||
update=update_live_preview
|
|
||||||
)
|
|
||||||
|
|
||||||
class TEXT_TEXTURE_Preset(PropertyGroup):
|
class TEXT_TEXTURE_Preset(PropertyGroup):
|
||||||
pass
|
pass
|
||||||
@@ -281,16 +303,6 @@ class TEXT_TEXTURE_Properties(PropertyGroup):
|
|||||||
update=update_live_preview
|
update=update_live_preview
|
||||||
)
|
)
|
||||||
|
|
||||||
composition_active_tab: EnumProperty(
|
|
||||||
name="Composition Mode",
|
|
||||||
description="Select which composition controls to display",
|
|
||||||
items=[
|
|
||||||
('TEXT', "Text", "Configure text layering options"),
|
|
||||||
('IMAGE', "Image", "Configure image overlay options"),
|
|
||||||
],
|
|
||||||
default='TEXT'
|
|
||||||
)
|
|
||||||
|
|
||||||
texture_width: IntProperty(
|
texture_width: IntProperty(
|
||||||
name="Width",
|
name="Width",
|
||||||
description="Texture width in pixels",
|
description="Texture width in pixels",
|
||||||
@@ -603,8 +615,8 @@ class TEXT_TEXTURE_Properties(PropertyGroup):
|
|||||||
update=update_live_preview
|
update=update_live_preview
|
||||||
)
|
)
|
||||||
|
|
||||||
# Image overlays
|
# Composition components (legacy name retained for compatibility)
|
||||||
image_overlays: CollectionProperty(type=TEXT_TEXTURE_ImageOverlay)
|
image_overlays: CollectionProperty(type=TEXT_TEXTURE_CompositionComponent)
|
||||||
active_overlay_index: IntProperty(default=0)
|
active_overlay_index: IntProperty(default=0)
|
||||||
|
|
||||||
# Presets
|
# Presets
|
||||||
|
|||||||
Binary file not shown.
247
src/ui/panels.py
247
src/ui/panels.py
@@ -229,163 +229,122 @@ def draw_generate_button(layout):
|
|||||||
row.scale_y = 1.5
|
row.scale_y = 1.5
|
||||||
row.operator("text_texture.generate", text="Generate Text Texture", icon='IMAGE_DATA')
|
row.operator("text_texture.generate", text="Generate Text Texture", icon='IMAGE_DATA')
|
||||||
|
|
||||||
def draw_composition_image_section(layout, props):
|
def _iter_components_by_placement(props, placement_mode):
|
||||||
pass
|
"""Yield (index, component) pairs filtered by placement mode."""
|
||||||
"""Draw image overlay controls for the composition panel."""
|
try:
|
||||||
|
overlays = list(props.image_overlays)
|
||||||
|
except TypeError:
|
||||||
|
overlays = props.image_overlays
|
||||||
|
for index, component in enumerate(overlays):
|
||||||
|
if getattr(component, "placement_mode", 'ABSOLUTE') == placement_mode:
|
||||||
|
yield index, component
|
||||||
|
|
||||||
|
|
||||||
|
def _draw_component_header(row, component):
|
||||||
|
"""Draw shared header controls for a component."""
|
||||||
|
row.prop(component, "enabled", text="", icon='CHECKBOX_HLT' if component.enabled else 'CHECKBOX_DEHLT')
|
||||||
|
name_row = row.row(align=True)
|
||||||
|
name_row.prop(component, "name", text="")
|
||||||
|
name_row.prop(component, "component_type", text="")
|
||||||
|
|
||||||
|
|
||||||
|
def _draw_component_footer(row, props, index):
|
||||||
|
"""Draw the footer action buttons for a component."""
|
||||||
|
premium_lock = create_premium_lock_function()
|
||||||
|
|
||||||
|
dup_op = safe_operator_call(row, "text_texture.duplicate_overlay", premium_lock, text="", icon='DUPLICATE')
|
||||||
|
if dup_op:
|
||||||
|
dup_op.overlay_index = index
|
||||||
|
|
||||||
|
del_op = safe_operator_call(row, "text_texture.delete_overlay", premium_lock, text="", icon='TRASH')
|
||||||
|
if del_op:
|
||||||
|
del_op.overlay_index = index
|
||||||
|
|
||||||
|
move_up = safe_operator_call(row, "text_texture.move_overlay", premium_lock, text="", icon='TRIA_UP')
|
||||||
|
if move_up:
|
||||||
|
move_up.overlay_index = index
|
||||||
|
move_up.direction = 'UP'
|
||||||
|
|
||||||
|
move_down = safe_operator_call(row, "text_texture.move_overlay", premium_lock, text="", icon='TRIA_DOWN')
|
||||||
|
if move_down:
|
||||||
|
move_down.overlay_index = index
|
||||||
|
move_down.direction = 'DOWN'
|
||||||
|
|
||||||
|
|
||||||
|
def _draw_component_details(container, component):
|
||||||
|
"""Render component-specific detail controls."""
|
||||||
|
details_col = container.column(align=True)
|
||||||
|
details_col.enabled = component.enabled
|
||||||
|
|
||||||
|
details_col.prop(component, "placement_mode", text="Placement")
|
||||||
|
|
||||||
|
if component.component_type == 'TEXT':
|
||||||
|
details_col.prop(component, "text_content", text="Text")
|
||||||
|
details_col.prop(component, "text_spacing", text="Spacing (%)")
|
||||||
|
else:
|
||||||
|
details_col.prop(component, "image_path", text="Image File")
|
||||||
|
details_col.prop(component, "text_spacing", text="Spacing (%)")
|
||||||
|
details_col.prop(component, "image_spacing", text="Component Spacing (%)")
|
||||||
|
|
||||||
|
if component.placement_mode == 'ABSOLUTE':
|
||||||
|
pos_row = details_col.row(align=True)
|
||||||
|
pos_row.prop(component, "x_position", text="X")
|
||||||
|
pos_row.prop(component, "y_position", text="Y")
|
||||||
|
|
||||||
|
transform_row = details_col.row(align=True)
|
||||||
|
transform_row.prop(component, "scale", text="Scale")
|
||||||
|
transform_row.prop(component, "rotation", text="Rotation")
|
||||||
|
transform_row.prop(component, "z_index", text="Z-Level")
|
||||||
|
|
||||||
|
|
||||||
|
def draw_component_section(layout, props, placement_mode, title, icon):
|
||||||
|
"""Render a section for a specific placement mode."""
|
||||||
section = layout.box()
|
section = layout.box()
|
||||||
section.use_property_split = False
|
section.use_property_split = False
|
||||||
section.use_property_decorate = False
|
section.use_property_decorate = False
|
||||||
section.label(text="Image Elements", icon='IMAGE_DATA')
|
|
||||||
|
|
||||||
# Check if image overlays are available
|
header = section.row(align=True)
|
||||||
if not has_image_overlays():
|
header.label(text=title, icon=icon)
|
||||||
pass
|
|
||||||
draw_feature_lock_indicator(section, 'image_overlays')
|
# Add new component button
|
||||||
|
add_row = section.row(align=True)
|
||||||
|
premium_lock = create_premium_lock_function()
|
||||||
|
add_op = safe_operator_call(
|
||||||
|
add_row,
|
||||||
|
"text_texture.add_overlay",
|
||||||
|
premium_lock,
|
||||||
|
text="Add Component",
|
||||||
|
icon='ADD'
|
||||||
|
)
|
||||||
|
if add_op:
|
||||||
|
add_op.placement_mode = placement_mode
|
||||||
|
|
||||||
|
components = list(_iter_components_by_placement(props, placement_mode))
|
||||||
|
if not components:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Add overlay button - use safe operator call
|
for index, component in components:
|
||||||
row = section.row()
|
box = section.box()
|
||||||
safe_operator_call(row, "text_texture.add_overlay",
|
header_row = box.row(align=True)
|
||||||
lambda layout: draw_feature_lock_indicator(layout, 'image_overlays'),
|
_draw_component_header(header_row, component)
|
||||||
text="Add Image Overlay", icon='ADD')
|
_draw_component_footer(header_row, props, index)
|
||||||
|
_draw_component_details(box, component)
|
||||||
# Individual overlay controls
|
|
||||||
if props.image_overlays:
|
|
||||||
pass
|
|
||||||
section.separator()
|
|
||||||
|
|
||||||
for i, overlay in enumerate(props.image_overlays):
|
|
||||||
pass
|
|
||||||
# Create collapsible box for each overlay
|
|
||||||
box = section.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_text_composition_section(layout, props):
|
|
||||||
pass
|
|
||||||
"""Draw additional text controls consolidated in the composition panel."""
|
|
||||||
section = layout.box()
|
|
||||||
section.use_property_split = False
|
|
||||||
section.use_property_decorate = False
|
|
||||||
section.label(text="Text Elements", icon='OUTLINER_OB_FONT')
|
|
||||||
|
|
||||||
layout_row = section.row(align=True)
|
|
||||||
layout_row.prop(props, "prepend_append_layout", text="Layout")
|
|
||||||
|
|
||||||
section.separator(factor=0.6)
|
|
||||||
|
|
||||||
def draw_text_block(label_text, icon, text_prop, margin_prop):
|
|
||||||
block = section.box()
|
|
||||||
block.use_property_split = False
|
|
||||||
block.use_property_decorate = False
|
|
||||||
header = block.row(align=True)
|
|
||||||
header.label(text=label_text, icon=icon)
|
|
||||||
|
|
||||||
text_row = block.row(align=True)
|
|
||||||
text_row.prop(props, text_prop, text="")
|
|
||||||
|
|
||||||
spacing_row = block.row(align=True)
|
|
||||||
spacing_row.enabled = bool(getattr(props, text_prop).strip())
|
|
||||||
spacing_row.prop(props, margin_prop, text="Spacing", slider=True)
|
|
||||||
|
|
||||||
draw_text_block("Prepend Text", 'TRIA_RIGHT', "prepend_text", "prepend_margin")
|
|
||||||
section.separator(factor=0.4)
|
|
||||||
draw_text_block("Append Text", 'TRIA_LEFT', "append_text", "append_margin")
|
|
||||||
|
|
||||||
def draw_composition_section(layout, props):
|
def draw_composition_section(layout, props):
|
||||||
pass
|
pass
|
||||||
"""Draw combined composition controls for text and images."""
|
"""Draw combined composition controls for text and image components."""
|
||||||
|
if not has_image_overlays():
|
||||||
|
pass
|
||||||
|
draw_feature_lock_indicator(layout, 'image_overlays')
|
||||||
|
return
|
||||||
|
|
||||||
content = layout.column(align=True)
|
content = layout.column(align=True)
|
||||||
content.use_property_split = False
|
content.use_property_split = False
|
||||||
content.use_property_decorate = False
|
content.use_property_decorate = False
|
||||||
|
|
||||||
tabs_row = content.row(align=True)
|
draw_component_section(content, props, 'ABSOLUTE', "Absolute Components", 'CURSOR')
|
||||||
tabs_row.prop(props, "composition_active_tab", expand=True)
|
draw_component_section(content, props, 'PREPEND', "Prepended Components", 'TRIA_LEFT_BAR')
|
||||||
|
draw_component_section(content, props, 'APPEND', "Appended Components", 'TRIA_RIGHT_BAR')
|
||||||
content.separator(factor=0.6)
|
|
||||||
|
|
||||||
if props.composition_active_tab == 'IMAGE':
|
|
||||||
pass
|
|
||||||
if not has_image_overlays():
|
|
||||||
pass
|
|
||||||
draw_feature_lock_indicator(content, 'image_overlays')
|
|
||||||
return
|
|
||||||
draw_composition_image_section(content, props)
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
draw_text_composition_section(content, props)
|
|
||||||
|
|
||||||
def draw_positioning_section(layout, props):
|
def draw_positioning_section(layout, props):
|
||||||
pass
|
pass
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -485,7 +485,8 @@ class TestGenerateTextureImage:
|
|||||||
overlay = MagicMock()
|
overlay = MagicMock()
|
||||||
overlay.enabled = True
|
overlay.enabled = True
|
||||||
overlay.image_path = str(overlay_path)
|
overlay.image_path = str(overlay_path)
|
||||||
overlay.positioning_mode = 'ABSOLUTE'
|
overlay.component_type = 'IMAGE'
|
||||||
|
overlay.placement_mode = 'ABSOLUTE'
|
||||||
overlay.x_position = 0.5
|
overlay.x_position = 0.5
|
||||||
overlay.y_position = 0.5
|
overlay.y_position = 0.5
|
||||||
overlay.scale = 1.0
|
overlay.scale = 1.0
|
||||||
@@ -537,7 +538,8 @@ class TestGenerateTextureImage:
|
|||||||
overlay = MagicMock()
|
overlay = MagicMock()
|
||||||
overlay.enabled = True
|
overlay.enabled = True
|
||||||
overlay.image_path = str(svg_path)
|
overlay.image_path = str(svg_path)
|
||||||
overlay.positioning_mode = 'ABSOLUTE'
|
overlay.component_type = 'IMAGE'
|
||||||
|
overlay.placement_mode = 'ABSOLUTE'
|
||||||
overlay.x_position = 0.5
|
overlay.x_position = 0.5
|
||||||
overlay.y_position = 0.5
|
overlay.y_position = 0.5
|
||||||
overlay.scale = 1.0
|
overlay.scale = 1.0
|
||||||
@@ -580,7 +582,8 @@ class TestGenerateTextureImage:
|
|||||||
overlay = MagicMock()
|
overlay = MagicMock()
|
||||||
overlay.enabled = True
|
overlay.enabled = True
|
||||||
overlay.image_path = ""
|
overlay.image_path = ""
|
||||||
overlay.positioning_mode = 'ABSOLUTE'
|
overlay.component_type = 'IMAGE'
|
||||||
|
overlay.placement_mode = 'ABSOLUTE'
|
||||||
overlay.x_position = 0.5
|
overlay.x_position = 0.5
|
||||||
overlay.y_position = 0.5
|
overlay.y_position = 0.5
|
||||||
overlay.scale = 1.0
|
overlay.scale = 1.0
|
||||||
@@ -619,7 +622,8 @@ class TestGenerateTextureImage:
|
|||||||
overlay = MagicMock()
|
overlay = MagicMock()
|
||||||
overlay.enabled = True
|
overlay.enabled = True
|
||||||
overlay.image_path = str(overlay_path)
|
overlay.image_path = str(overlay_path)
|
||||||
overlay.positioning_mode = 'APPEND'
|
overlay.component_type = 'IMAGE'
|
||||||
|
overlay.placement_mode = 'APPEND'
|
||||||
overlay.text_spacing = 10.0 # push further right
|
overlay.text_spacing = 10.0 # push further right
|
||||||
overlay.image_spacing = 0.0
|
overlay.image_spacing = 0.0
|
||||||
overlay.scale = 1.0
|
overlay.scale = 1.0
|
||||||
@@ -676,7 +680,8 @@ class TestGenerateTextureImage:
|
|||||||
overlay = MagicMock()
|
overlay = MagicMock()
|
||||||
overlay.enabled = True
|
overlay.enabled = True
|
||||||
overlay.image_path = str(overlay_path)
|
overlay.image_path = str(overlay_path)
|
||||||
overlay.positioning_mode = 'PREPEND'
|
overlay.component_type = 'IMAGE'
|
||||||
|
overlay.placement_mode = 'PREPEND'
|
||||||
overlay.text_spacing = 10.0 # push further left
|
overlay.text_spacing = 10.0 # push further left
|
||||||
overlay.image_spacing = 0.0
|
overlay.image_spacing = 0.0
|
||||||
overlay.scale = 1.0
|
overlay.scale = 1.0
|
||||||
|
|||||||
@@ -94,7 +94,6 @@ def make_default_props(**overrides):
|
|||||||
manual_position_x=50.0,
|
manual_position_x=50.0,
|
||||||
manual_position_y=50.0,
|
manual_position_y=50.0,
|
||||||
constrain_to_canvas=True,
|
constrain_to_canvas=True,
|
||||||
composition_active_tab="TEXT",
|
|
||||||
image_overlays=[],
|
image_overlays=[],
|
||||||
active_overlay_index=0,
|
active_overlay_index=0,
|
||||||
)
|
)
|
||||||
@@ -134,14 +133,24 @@ def test_positioning_section_drops_additional_text(monkeypatch):
|
|||||||
assert "append_text" not in prop_names
|
assert "append_text" not in prop_names
|
||||||
|
|
||||||
|
|
||||||
def test_composition_section_shows_text_controls(monkeypatch):
|
def test_composition_section_renders_text_component(monkeypatch):
|
||||||
"""Composition panel should surface text controls when text tab is active."""
|
"""Composition panel should show text component controls within the proper section."""
|
||||||
layout = FakeLayout()
|
layout = FakeLayout()
|
||||||
props = make_default_props(
|
text_component = SimpleNamespace(
|
||||||
composition_active_tab="TEXT",
|
enabled=True,
|
||||||
prepend_text="Intro",
|
name="Intro Text",
|
||||||
append_text="Outro",
|
component_type="TEXT",
|
||||||
|
placement_mode="PREPEND",
|
||||||
|
text_content="Intro",
|
||||||
|
text_spacing=10.0,
|
||||||
|
image_spacing=5.0,
|
||||||
|
x_position=0.5,
|
||||||
|
y_position=0.5,
|
||||||
|
scale=1.0,
|
||||||
|
rotation=0.0,
|
||||||
|
z_index=0,
|
||||||
)
|
)
|
||||||
|
props = make_default_props(image_overlays=[text_component])
|
||||||
|
|
||||||
monkeypatch.setattr(panels, "safe_operator_call", lambda *args, **kwargs: SimpleNamespace())
|
monkeypatch.setattr(panels, "safe_operator_call", lambda *args, **kwargs: SimpleNamespace())
|
||||||
monkeypatch.setattr(panels, "has_image_overlays", lambda: True)
|
monkeypatch.setattr(panels, "has_image_overlays", lambda: True)
|
||||||
@@ -151,21 +160,24 @@ def test_composition_section_shows_text_controls(monkeypatch):
|
|||||||
calls = collect_calls(layout)
|
calls = collect_calls(layout)
|
||||||
prop_names = [data["name"] for kind, data in calls if kind == "prop"]
|
prop_names = [data["name"] for kind, data in calls if kind == "prop"]
|
||||||
labels = [data["text"] for kind, data in calls if kind == "label"]
|
labels = [data["text"] for kind, data in calls if kind == "label"]
|
||||||
|
sections = [text for text in labels if "Components" in text]
|
||||||
|
|
||||||
assert "composition_active_tab" in prop_names
|
assert "component_type" in prop_names
|
||||||
assert "prepend_text" in prop_names
|
assert "text_content" in prop_names
|
||||||
assert "append_text" in prop_names
|
assert "placement_mode" in prop_names
|
||||||
assert any("Text" in text for text in labels)
|
assert "text_spacing" in prop_names
|
||||||
|
assert "Prepended Components" in sections
|
||||||
|
|
||||||
|
|
||||||
def test_composition_section_shows_overlay_controls(monkeypatch):
|
def test_composition_section_renders_image_component(monkeypatch):
|
||||||
"""Composition panel should surface overlay controls when image tab is active."""
|
"""Composition panel should show image component controls within the proper section."""
|
||||||
layout = FakeLayout()
|
layout = FakeLayout()
|
||||||
overlay = SimpleNamespace(
|
image_component = SimpleNamespace(
|
||||||
enabled=True,
|
enabled=True,
|
||||||
name="Logo",
|
name="Logo",
|
||||||
|
component_type="IMAGE",
|
||||||
|
placement_mode="ABSOLUTE",
|
||||||
image_path="/tmp/logo.png",
|
image_path="/tmp/logo.png",
|
||||||
positioning_mode="ABSOLUTE",
|
|
||||||
x_position=0.5,
|
x_position=0.5,
|
||||||
y_position=0.5,
|
y_position=0.5,
|
||||||
text_spacing=10.0,
|
text_spacing=10.0,
|
||||||
@@ -174,10 +186,7 @@ def test_composition_section_shows_overlay_controls(monkeypatch):
|
|||||||
rotation=0.0,
|
rotation=0.0,
|
||||||
z_index=1,
|
z_index=1,
|
||||||
)
|
)
|
||||||
props = make_default_props(
|
props = make_default_props(image_overlays=[image_component])
|
||||||
composition_active_tab="IMAGE",
|
|
||||||
image_overlays=[overlay],
|
|
||||||
)
|
|
||||||
|
|
||||||
monkeypatch.setattr(panels, "safe_operator_call", lambda *args, **kwargs: SimpleNamespace())
|
monkeypatch.setattr(panels, "safe_operator_call", lambda *args, **kwargs: SimpleNamespace())
|
||||||
monkeypatch.setattr(panels, "has_image_overlays", lambda: True)
|
monkeypatch.setattr(panels, "has_image_overlays", lambda: True)
|
||||||
@@ -187,7 +196,10 @@ def test_composition_section_shows_overlay_controls(monkeypatch):
|
|||||||
calls = collect_calls(layout)
|
calls = collect_calls(layout)
|
||||||
prop_names = [data["name"] for kind, data in calls if kind == "prop"]
|
prop_names = [data["name"] for kind, data in calls if kind == "prop"]
|
||||||
labels = [data["text"] for kind, data in calls if kind == "label"]
|
labels = [data["text"] for kind, data in calls if kind == "label"]
|
||||||
|
sections = [text for text in labels if "Components" in text]
|
||||||
|
|
||||||
assert "composition_active_tab" in prop_names
|
assert "component_type" in prop_names
|
||||||
assert "positioning_mode" in prop_names
|
assert "image_path" in prop_names
|
||||||
assert any("Image" in text for text in labels)
|
assert "placement_mode" in prop_names
|
||||||
|
assert "x_position" in prop_names
|
||||||
|
assert "Absolute Components" in sections
|
||||||
|
|||||||
Reference in New Issue
Block a user