from pathlib import Path from types import SimpleNamespace from unittest.mock import Mock, patch from blender_test_support import BlenderTestCase class PlaneOperatorsTest(BlenderTestCase): def test_plane_depth_off_accepts_animated_image_without_server(self): operator = SimpleNamespace( depth_mode="OFF", remove_background=False, shadeless=False, profile="SURFACE", mesh_detail=64, thickness=0.1, inflation=0.1, algorithm="POISSON", ) source = SimpleNamespace(data=SimpleNamespace(name="Sequence")) context = SimpleNamespace(object=source, scene=object()) with ( patch.object(self.convert_to_plane, "video_total_frames", return_value=24), patch.object( self.convert_to_plane, "image_user_settings", return_value=(1, 0, 24, True), ), patch.object(self.convert_to_plane, "create_image_material", return_value="Material"), patch.object(self.convert_to_plane, "create_color_image", return_value="Color"), patch.object(self.convert_to_plane, "create_plane_object") as build_mesh, ): result = self.convert_to_plane.ConvertToPlane.execute(operator, context) self.assertEqual(result, {"FINISHED"}) build_mesh.assert_called_once() def test_depth_plane_rejects_animation_and_requests_ai_setup(self): operator = SimpleNamespace(mesh_detail=6, report=Mock()) context = SimpleNamespace(object=SimpleNamespace(data=object())) with ( patch.object(self.convert_to_plane, "video_total_frames", return_value=2), patch.object(self.convert_to_plane, "invoke_ai_setup_if_needed") as setup, ): self.assertEqual(self.convert_to_plane.ConvertToDepthPlane.execute(operator, context), {"CANCELLED"}) setup.assert_not_called() with ( patch.object(self.convert_to_plane, "video_total_frames", return_value=1), patch.object(self.convert_to_plane, "invoke_ai_setup_if_needed", return_value={"FINISHED"}) as setup, patch.object(self.convert_to_plane, "prepare_image_input") as prepare, ): self.assertEqual(self.convert_to_plane.ConvertToDepthPlane.execute(operator, context), {"FINISHED"}) setup.assert_called_once() prepare.assert_not_called() def test_depth_plane_starts_one_job_without_a_format_selection(self): module = self.convert_to_plane source = SimpleNamespace(name="Source", data=object()) context = SimpleNamespace(object=source, scene=object()) operator = SimpleNamespace(mesh_detail=4, report=Mock()) generate = Mock(return_value={"FINISHED"}) with ( patch.object(module, "video_total_frames", return_value=1), patch.object(module, "invoke_ai_setup_if_needed", return_value=None), patch.object(module, "prepare_image_input", return_value=(Path("input.png"), True)), patch.object(module, "depth_plane_settings", return_value=("moge", 5)), patch.object(module, "configured_max_ai_input_size", return_value=1024), patch.object(module.bpy, "ops", SimpleNamespace(anyimage=SimpleNamespace(generate_depth_plane=generate)), create=True), ): self.assertEqual(module.ConvertToDepthPlane.execute(operator, context), {"FINISHED"}) self.assertNotIn("depth_mode", generate.call_args.kwargs) self.assertEqual(generate.call_args.kwargs["source_object_name"], "Source") def test_depth_plane_result_connects_the_tangent_normal_material(self): module = self.convert_to_plane_object source = SimpleNamespace(data=SimpleNamespace(name="Source")) color_image = SimpleNamespace(name="", users=0) depth_image = SimpleNamespace(users=0) normal_image = SimpleNamespace(users=0) material = SimpleNamespace(users=0) metadata = {"reference_depth": 1.0} files = { "depth_metadata": Path("depth.json"), "depth": Path("depth.exr"), "tangent_normal": Path("tangent-normal.png"), } result = SimpleNamespace( directory=Path("result"), file=lambda key: files[key], ) operator = SimpleNamespace(source_object_name="Source", mesh_detail=6, generate_depth=True) context = SimpleNamespace(scene=object()) ops = SimpleNamespace( ed=SimpleNamespace(undo_push=Mock(return_value={"FINISHED"})) ) with ( patch.object(module, "require_image_empty", return_value=source), patch.object(module, "load_color_result_image", return_value=color_image), patch.object(module, "load_depth_metadata", return_value=metadata), patch.object(module, "load_depth_result_image", return_value=depth_image) as load_depth, patch.object( module, "load_normal_result_image", return_value=normal_image, ) as load_normal, patch.object( module, "create_image_material", return_value=material, ) as create_material, patch.object(module, "create_depth_plane_object") as create_object, patch.object(module.bpy, "ops", ops, create=True), ): module.create_depth_plane_from_result(context, result, operator) load_depth.assert_called_once_with(files["depth"], source, metadata) ops.ed.undo_push.assert_called_once_with(message="Convert to Depth Plane") load_normal.assert_called_once_with(files["tangent_normal"], source) create_material.assert_called_once_with( source, color_image, 0.0, normal_image=normal_image, normal_space="TANGENT", ior=1.2, scene=context.scene, ) create_object.assert_called_once_with( context, source, 6, material, depth_image, metadata, ) def test_depth_plane_result_cleans_unused_depth_and_normal_on_failure(self): module = self.convert_to_plane_object source = SimpleNamespace(data=SimpleNamespace(name="Source")) color_image = SimpleNamespace(name="", users=0) depth_image = SimpleNamespace(users=0) normal_image = SimpleNamespace(users=0) material = SimpleNamespace(users=0) files = { "depth_metadata": Path("depth.json"), "depth": Path("depth.exr"), "tangent_normal": Path("tangent-normal.png"), } result = SimpleNamespace( directory=Path("result"), file=lambda key: files[key], ) remove_material = Mock() remove_image = Mock() data = SimpleNamespace( materials=SimpleNamespace(remove=remove_material), images=SimpleNamespace(remove=remove_image), ) with ( patch.object(module, "require_image_empty", return_value=source), patch.object(module, "load_color_result_image", return_value=color_image), patch.object( module, "load_depth_metadata", return_value={"reference_depth": 1.0}, ), patch.object(module, "load_depth_result_image", return_value=depth_image), patch.object(module, "load_normal_result_image", return_value=normal_image), patch.object(module, "create_image_material", return_value=material), patch.object( module, "create_depth_plane_object", side_effect=RuntimeError("failed"), ), patch.object(module.bpy, "data", data), self.assertRaisesRegex(RuntimeError, "failed"), ): module.create_depth_plane_from_result( SimpleNamespace(scene=object()), result, SimpleNamespace(source_object_name="Source", mesh_detail=6, generate_depth=True), ) remove_material.assert_called_once_with(material, do_unlink=True) self.assertEqual( [entry.args[0] for entry in remove_image.call_args_list], [color_image, depth_image, normal_image], ) self.assertTrue( all( entry.kwargs == {"do_unlink": True} for entry in remove_image.call_args_list ) )