fix overlays

This commit is contained in:
2025-10-13 09:57:59 +02:00
parent ad55572aa8
commit 9a6543c530
14 changed files with 289 additions and 7 deletions

View File

@@ -94,6 +94,7 @@ def create_mock_props(**kwargs):
props = MagicMock()
# Set default values
props.image_overlays = kwargs.get('image_overlays', [])
props.text = kwargs.get('text', '')
props.prepend_text = kwargs.get('prepend_text', '')
props.append_text = kwargs.get('append_text', '')
@@ -417,12 +418,204 @@ class TestGenerateTextureImage:
# Call function
result, normal_map = generate_texture_image(props, 256, 512)
# Assertions
assert result is not None
# Verify multiple draw.text calls for vertical layout
assert mock_draw.text.call_count >= 3 # One per text part
def test_applies_absolute_overlay_to_texture(self, tmp_path):
"""Image overlays should be composited onto the generated texture."""
Image = pytest.importorskip("PIL.Image")
with patch('src.core.generation_engine.bpy') as mock_bpy:
width = height = 64
overlay_path = tmp_path / "overlay.png"
# Create a solid red overlay image
overlay_source = Image.new("RGBA", (16, 16), (255, 0, 0, 255))
overlay_source.save(overlay_path)
mock_blender_image = MagicMock()
mock_blender_image.pixels = [0.0] * (width * height * 4)
mock_bpy.data.images.new.return_value = mock_blender_image
mock_bpy.data.images.__contains__.return_value = False
props = create_mock_props(
text="",
background_transparent=False,
background_color=(1.0, 1.0, 1.0, 1.0)
)
overlay = MagicMock()
overlay.enabled = True
overlay.image_path = str(overlay_path)
overlay.positioning_mode = 'ABSOLUTE'
overlay.x_position = 0.5
overlay.y_position = 0.5
overlay.scale = 1.0
overlay.rotation = 0.0
overlay.z_index = 0
overlay.text_spacing = 0.0
overlay.image_spacing = 0.0
props.image_overlays = [overlay]
result, normal_map = generate_texture_image(props, width, height)
assert result is mock_blender_image
assert normal_map is None
center_index = ((height // 2) * width + (width // 2)) * 4
center_pixel = mock_blender_image.pixels[center_index:center_index + 4]
# Center pixel should now be red from the overlay
assert center_pixel[0] == pytest.approx(1.0, rel=1e-3)
assert center_pixel[1] == pytest.approx(0.0, abs=1e-3)
assert center_pixel[2] == pytest.approx(0.0, abs=1e-3)
assert center_pixel[3] == pytest.approx(1.0, rel=1e-3)
def test_skips_overlays_without_valid_path(self, tmp_path):
"""Overlays without a valid image path should be ignored."""
pytest.importorskip("PIL.Image")
with patch('PIL.Image.open') as mock_image_open, \
patch('src.core.generation_engine.bpy') as mock_bpy:
width = height = 32
mock_blender_image = MagicMock()
mock_blender_image.pixels = [0.0] * (width * height * 4)
mock_bpy.data.images.new.return_value = mock_blender_image
mock_bpy.data.images.__contains__.return_value = False
props = create_mock_props(
text="",
background_transparent=True
)
overlay = MagicMock()
overlay.enabled = True
overlay.image_path = ""
overlay.positioning_mode = 'ABSOLUTE'
overlay.x_position = 0.5
overlay.y_position = 0.5
overlay.scale = 1.0
overlay.rotation = 0.0
overlay.z_index = 0
overlay.text_spacing = 0.0
overlay.image_spacing = 0.0
props.image_overlays = [overlay]
result, normal_map = generate_texture_image(props, width, height)
assert result is mock_blender_image
assert normal_map is None
mock_image_open.assert_not_called()
def test_append_overlay_positions_to_right(self, tmp_path):
"""Append overlays should be positioned to the right side with spacing applied."""
Image = pytest.importorskip("PIL.Image")
with patch('src.core.generation_engine.bpy') as mock_bpy:
width = height = 64
overlay_path = tmp_path / "append.png"
overlay_source = Image.new("RGBA", (10, 10), (0, 255, 0, 255))
overlay_source.save(overlay_path)
mock_blender_image = MagicMock()
mock_blender_image.pixels = [0.0] * (width * height * 4)
mock_bpy.data.images.new.return_value = mock_blender_image
mock_bpy.data.images.__contains__.return_value = False
props = create_mock_props(
text="",
background_transparent=False,
background_color=(1.0, 1.0, 1.0, 1.0)
)
overlay = MagicMock()
overlay.enabled = True
overlay.image_path = str(overlay_path)
overlay.positioning_mode = 'APPEND'
overlay.text_spacing = 10.0 # push further right
overlay.image_spacing = 0.0
overlay.scale = 1.0
overlay.rotation = 0.0
overlay.z_index = 0
overlay.x_position = 0.5
overlay.y_position = 0.5
props.image_overlays = [overlay]
result, normal_map = generate_texture_image(props, width, height)
assert result is mock_blender_image
assert normal_map is None
spacing_ratio = overlay.text_spacing / 100.0
center_x = (width * 0.5) + (spacing_ratio * width) + (overlay_source.width / 2.0)
expected_x = int(round(center_x - overlay_source.width / 2.0))
expected_y = int(round((height * overlay.y_position) - (overlay_source.height / 2.0)))
expected_y = max(0, min(height - overlay_source.height, expected_y))
sample_row = expected_y + overlay_source.height // 2
green_columns = []
for x in range(width):
idx = (sample_row * width + x) * 4
pixel = mock_blender_image.pixels[idx:idx + 4]
if pixel and pixel[1] >= 0.95 and pixel[0] <= 0.1 and pixel[2] <= 0.1:
green_columns.append(x)
assert green_columns, "Append overlay pixels not found"
assert max(green_columns) >= expected_x + overlay_source.width - 2
assert min(green_columns) >= expected_x - 1
assert max(green_columns) > width // 2 # should be on right half
def test_prepend_overlay_positions_to_left(self, tmp_path):
"""Prepend overlays should be positioned to the left side with spacing applied."""
Image = pytest.importorskip("PIL.Image")
with patch('src.core.generation_engine.bpy') as mock_bpy:
width = height = 64
overlay_path = tmp_path / "prepend.png"
overlay_source = Image.new("RGBA", (10, 10), (0, 0, 255, 255))
overlay_source.save(overlay_path)
mock_blender_image = MagicMock()
mock_blender_image.pixels = [0.0] * (width * height * 4)
mock_bpy.data.images.new.return_value = mock_blender_image
mock_bpy.data.images.__contains__.return_value = False
props = create_mock_props(
text="",
background_transparent=False,
background_color=(1.0, 1.0, 1.0, 1.0)
)
overlay = MagicMock()
overlay.enabled = True
overlay.image_path = str(overlay_path)
overlay.positioning_mode = 'PREPEND'
overlay.text_spacing = 10.0 # push further left
overlay.image_spacing = 0.0
overlay.scale = 1.0
overlay.rotation = 0.0
overlay.z_index = 0
overlay.x_position = 0.5
overlay.y_position = 0.5
props.image_overlays = [overlay]
result, normal_map = generate_texture_image(props, width, height)
assert result is mock_blender_image
assert normal_map is None
spacing_ratio = overlay.text_spacing / 100.0
center_x = (width * 0.5) - (spacing_ratio * width) - (overlay_source.width / 2.0)
expected_x = int(round(center_x - overlay_source.width / 2.0))
expected_y = int(round((height * overlay.y_position) - (overlay_source.height / 2.0)))
expected_y = max(0, min(height - overlay_source.height, expected_y))
sample_row = expected_y + overlay_source.height // 2
blue_columns = []
for x in range(width):
idx = (sample_row * width + x) * 4
pixel = mock_blender_image.pixels[idx:idx + 4]
if pixel and pixel[2] >= 0.95 and pixel[0] <= 0.1 and pixel[1] <= 0.1:
blue_columns.append(x)
assert blue_columns, "Prepend overlay pixels not found"
assert min(blue_columns) <= expected_x + 1
assert max(blue_columns) <= expected_x + overlay_source.width
assert max(blue_columns) < width // 2 # should remain on left half
def test_text_fitting_enabled(self):
"""Test text fitting integration."""
with patch('PIL.Image') as mock_pil_image_module, \
@@ -696,4 +889,4 @@ class TestGeneratePreview:
result = generate_preview(props, preview_width=128, preview_height=128)
# Should handle error gracefully
assert result is None
assert result is None