- Implemented a preview tool for rectifying images with interactive aspect controls in `preview.py`. - Added tests for depth metadata loading to ensure consistent schema usage in `test_depth_metadata.py`. - Developed comprehensive tests for cutout calibration processes in `test_cutout_calibration.py`, including stability checks and depth calibration. - Created tests for frame compositing operations in `test_frame_compositing.py`, ensuring correct handling of depth and transparency. - Introduced material slot inheritance functionality in `material_slots.py` to manage geometry material assignments. - Built shared shader nodes for material processing in `shader.py`, including roughness and bump mapping setups.
116 lines
5.2 KiB
Python
116 lines
5.2 KiB
Python
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
from unittest.mock import patch
|
|
|
|
import bpy
|
|
import numpy as np
|
|
import pytest
|
|
|
|
from anyimage.common.hdr_image import HdrBackgroundInput
|
|
from anyimage.common.image import cleanup_image_input, image_rgba
|
|
from anyimage.common.image_target import ImageEditTarget
|
|
from anyimage.operators import remove_background as operators
|
|
from anyimage.runtime import JobOperatorBase
|
|
from tests.support.hdr_image import hdr_texture
|
|
from tests.support.image_texture import texture, texture_context
|
|
|
|
|
|
@pytest.mark.parametrize("owner_kind", ["texture", "empty"])
|
|
@pytest.mark.parametrize("shared", [False, True])
|
|
def test_hdr_background_undo_redo(hdr_texture, tmp_path, owner_kind, shared):
|
|
node = hdr_texture
|
|
source = node.image
|
|
before = image_rgba(source).copy()
|
|
if owner_kind == "empty":
|
|
owner = bpy.data.objects.new("HDR Empty", None)
|
|
owner.empty_display_type = "IMAGE"
|
|
owner.data = source
|
|
bpy.context.scene.collection.objects.link(owner)
|
|
node.image = source if shared else None
|
|
elif shared:
|
|
other = node.id_data.nodes.new("ShaderNodeTexImage")
|
|
other.image = source
|
|
alpha_path = tmp_path / "alpha.npy"
|
|
np.save(alpha_path, np.full((3, 4), 0.37123, np.float32))
|
|
|
|
def current_owner():
|
|
if owner_kind == "empty":
|
|
return bpy.data.objects["HDR Empty"]
|
|
return bpy.data.materials["Texture material"].node_tree.nodes["Source texture"]
|
|
|
|
def current_image():
|
|
owner = current_owner()
|
|
return owner.data if owner_kind == "empty" else owner.image
|
|
|
|
def execute(operator, context):
|
|
owner = current_owner()
|
|
target_context = SimpleNamespace(object=owner, space_data=None) if owner_kind == "empty" else texture_context(owner)
|
|
operator._image_target = ImageEditTarget.capture(target_context)
|
|
operator._hdr_input = HdrBackgroundInput.prepare(current_image())
|
|
operator.input_path, operator.delete_input = str(operator._hdr_input.path), True
|
|
try:
|
|
operator.response(context, SimpleNamespace(file=lambda key: alpha_path))
|
|
finally:
|
|
operator.cleanup()
|
|
return {"FINISHED"}
|
|
|
|
operation = operators.RunBackgroundRemoval
|
|
bpy.context.preferences.edit.use_global_undo = True
|
|
with patch.object(operation, "execute", execute), patch.object(operation, "poll", classmethod(lambda cls, context: True), create=True):
|
|
bpy.utils.register_class(operation)
|
|
try:
|
|
bpy.ops.ed.undo_push(message="Before HDR removal")
|
|
assert bpy.ops.anyimage.run_background_removal("EXEC_DEFAULT", True) == {"FINISHED"}
|
|
after = image_rgba(current_image()).copy()
|
|
np.testing.assert_array_equal(after[..., :3], before[..., :3])
|
|
np.testing.assert_allclose(after[..., 3], before[..., 3] * np.float32(0.37123), atol=1e-7)
|
|
assert bpy.ops.ed.undo() == {"FINISHED"}
|
|
np.testing.assert_array_equal(image_rgba(current_image()), before)
|
|
assert bpy.ops.ed.redo() == {"FINISHED"}
|
|
np.testing.assert_array_equal(image_rgba(current_image()), after)
|
|
assert current_image().is_float
|
|
finally:
|
|
bpy.utils.unregister_class(operation)
|
|
|
|
|
|
@pytest.mark.parametrize("outcome", ["cancel", "raise"])
|
|
def test_hdr_submission_failure_releases_preview_and_snapshot(hdr_texture, outcome):
|
|
target = ImageEditTarget.capture(texture_context(hdr_texture))
|
|
paths = []
|
|
|
|
def submit(operator, context):
|
|
paths.append(Path(operator.input_path))
|
|
assert paths[-1].is_file()
|
|
if outcome == "raise":
|
|
raise RuntimeError("Submission failed")
|
|
return {"CANCELLED"}
|
|
|
|
# Use the registered type so super().execute follows the real Operator inheritance.
|
|
operation = operators.RunBackgroundRemoval
|
|
with patch.object(ImageEditTarget, "capture", return_value=target), patch.object(JobOperatorBase, "execute", submit):
|
|
bpy.utils.register_class(operation)
|
|
try:
|
|
if outcome == "raise":
|
|
with pytest.raises(RuntimeError, match="Submission failed"):
|
|
bpy.ops.anyimage.run_background_removal("EXEC_DEFAULT")
|
|
else:
|
|
assert bpy.ops.anyimage.run_background_removal("EXEC_DEFAULT") == {"CANCELLED"}
|
|
finally:
|
|
bpy.utils.unregister_class(operation)
|
|
assert paths and not paths[0].parent.exists()
|
|
|
|
|
|
def test_hdr_request_uses_alpha_output_and_current_still(hdr_texture):
|
|
operation = operators.RunBackgroundRemoval
|
|
snapshot = HdrBackgroundInput.prepare(hdr_texture.image)
|
|
operator = SimpleNamespace(input_path=str(snapshot.path), _hdr_input=snapshot,
|
|
_image_target=SimpleNamespace())
|
|
context = SimpleNamespace(scene=SimpleNamespace(anyimage_settings=SimpleNamespace()))
|
|
try:
|
|
with patch.object(operators, "require_environment"), patch.object(operators, "require_model"), patch.object(operators, "production_device", return_value="CPU"):
|
|
parameters = operation.request(operator, context)
|
|
assert parameters["output_kind"] == "alpha"
|
|
assert "start_frame" not in parameters and "video_frames" not in parameters
|
|
finally:
|
|
cleanup_image_input(snapshot.path, True)
|