112 lines
4.9 KiB
Python
112 lines
4.9 KiB
Python
"""Verify cache identity against edits to real Blender images."""
|
|
|
|
from io import BytesIO
|
|
from types import SimpleNamespace
|
|
|
|
import bpy
|
|
import numpy as np
|
|
import pytest
|
|
from PIL import Image
|
|
|
|
from anyimage.common import image as images
|
|
from anyimage.common.selection import SelectionMask
|
|
from anyimage.operators.clipboard_image import actions
|
|
from anyimage.operators.mask_tool import EditImageAlpha
|
|
|
|
|
|
@pytest.fixture
|
|
def clipboard():
|
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
|
stream = BytesIO()
|
|
Image.fromarray(np.array([[[100, 150, 200, 255], [50, 80, 120, 0]]], np.uint8)).save(stream, format="PNG")
|
|
yield stream.getvalue()
|
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
|
|
|
|
|
@pytest.mark.parametrize("edit", ["paint", "packed", "hidden_rgb", "size", "space", "alpha", "unreadable"])
|
|
def test_changed_cache_is_skipped_without_modifying_candidate(clipboard, edit, monkeypatch):
|
|
original, reused = actions.acquire_packed_image(clipboard, ".png")
|
|
expected = images.image_pixels(original)
|
|
assert not reused
|
|
assert actions.acquire_packed_image(clipboard, ".png") == (original, True)
|
|
if edit in {"paint", "packed"}:
|
|
original.pixels[0] = 1
|
|
if edit == "packed":
|
|
original.pack()
|
|
elif edit == "hidden_rgb":
|
|
original.pixels[4] = 1
|
|
elif edit == "size":
|
|
original.scale(4, 2)
|
|
elif edit == "space":
|
|
original.colorspace_settings.name = "Non-Color"
|
|
elif edit == "alpha":
|
|
original.alpha_mode = "PREMUL"
|
|
else:
|
|
read = actions._clipboard_content_hash
|
|
def fail_candidate(image):
|
|
if image == original:
|
|
raise RuntimeError("Unreadable candidate")
|
|
return read(image)
|
|
monkeypatch.setattr(actions, "_clipboard_content_hash", fail_candidate)
|
|
state = images.image_content_state(original)
|
|
current_pixels = original.pixels[:]
|
|
replacement, reused = actions.acquire_packed_image(clipboard, ".png")
|
|
assert replacement != original and not reused
|
|
np.testing.assert_array_equal(images.image_pixels(replacement), expected)
|
|
assert (original.colorspace_settings.name, original.alpha_mode) == (state["colorspace"], state["alpha_mode"])
|
|
assert bytes(original.packed_file.data) == state["packed"]
|
|
np.testing.assert_array_equal(original.pixels[:], current_pixels)
|
|
|
|
|
|
@pytest.mark.parametrize("shared", [False, True])
|
|
def test_mask_edit_then_paste_preserves_original_clipboard_content(clipboard, shared):
|
|
original, _ = actions.acquire_packed_image(clipboard, ".png")
|
|
expected = images.image_pixels(original)
|
|
owner = bpy.data.objects.new("Clipboard Empty", None)
|
|
owner.empty_display_type = "IMAGE"
|
|
owner.data = original
|
|
bpy.context.collection.objects.link(owner)
|
|
if shared:
|
|
other = owner.copy()
|
|
bpy.context.collection.objects.link(other)
|
|
mask = SelectionMask(np.full((1, 2), .5, np.float32), (0, 0, 2, 1))
|
|
assert EditImageAlpha._apply_mask(SimpleNamespace(mode="SUBTRACT"), bpy.context, owner, mask) == {"FINISHED"}
|
|
edited = owner.data
|
|
edited_pixels = images.image_pixels(edited)
|
|
assert edited.packed_file
|
|
assert (edited == original) == (not shared)
|
|
pasted, reused = actions.acquire_packed_image(clipboard, ".png")
|
|
assert reused == shared
|
|
assert pasted != edited
|
|
np.testing.assert_array_equal(images.image_pixels(pasted), expected)
|
|
np.testing.assert_array_equal(images.image_pixels(edited), edited_pixels)
|
|
|
|
|
|
def test_painted_clipboard_plane_keeps_reference_node_and_brush_cache(clipboard, monkeypatch):
|
|
original, _ = actions.acquire_packed_image(clipboard, ".png")
|
|
expected = images.image_pixels(original)
|
|
monkeypatch.setattr(actions, "_place_in_view", lambda *_: None)
|
|
monkeypatch.setattr(actions, "_select_only", lambda *_: None)
|
|
obj = actions.add_image_plane(bpy.context, original, shadeless=False)
|
|
color = next(n.image for n in obj.data.materials[0].node_tree.nodes if n.type == "TEX_IMAGE")
|
|
assert color != original and actions.CLIPBOARD_HASH_PROPERTY not in color
|
|
assert actions.CLIPBOARD_CONTENT_PROPERTY not in color
|
|
color.pixels[0] = 1
|
|
color.pack()
|
|
for _ in range(3):
|
|
pasted, reused = actions.acquire_packed_image(clipboard, ".png")
|
|
assert pasted == original and reused
|
|
np.testing.assert_array_equal(images.image_pixels(pasted), expected)
|
|
|
|
|
|
def test_failed_clipboard_plane_placement_releases_created_data(clipboard, monkeypatch):
|
|
original, _ = actions.acquire_packed_image(clipboard, ".png")
|
|
before = {name: set(getattr(bpy.data, name)) for name in ("images", "objects", "meshes", "materials")}
|
|
def fail(*_):
|
|
raise RuntimeError("Placement failed")
|
|
monkeypatch.setattr(actions, "_place_in_view", fail)
|
|
with pytest.raises(RuntimeError, match="Placement failed"):
|
|
actions.add_image_plane(bpy.context, original)
|
|
for name, items in before.items():
|
|
assert set(getattr(bpy.data, name)) == items
|