Files
AnyImage/tests/anyimage/operators/test_clipboard_cache.py
nan 489003c2cc Add new tests and functionality for image processing and geometry handling
- Introduced color_image.py for preparing standalone color images for tests.
- Added hdr_image.py to create HDR textures for testing.
- Implemented test_image_depth_panorama.py to evaluate panorama topology, UVs, and depth discontinuities.
- Created test_shadeless.py to validate the opacity and HDR color endpoints of the Shadeless group.
- Developed moge3_export.py for exporting dynamic MoGe-3 backbone and ONNX graphs.
- Added material.py to inherit material slots onto generated geometry.
- Built image_depth_panorama.py to construct a quad panorama with depth separation and sampling.
2026-09-10 14:42:17 +08:00

114 lines
5.0 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", "legacy", "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"
elif edit == "legacy":
del original[actions.CLIPBOARD_CONTENT_PROPERTY]
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