feat: Add new Geometry Nodes groups for image relief and shadeless shader
- Implemented the "O Image Relief Plane" Geometry Nodes group to create a fixed-base image relief using depth images and various parameters. - Created the "O Shadeless" shader node group to display color without scene lighting while preserving shadows and reflections. - Introduced a preview feature for node groups, allowing for interactive visualization of node arrangements in an HTML format. - Added a template HTML file for rendering node previews with customizable views and controls.
This commit is contained in:
@@ -55,88 +55,90 @@ class PlaneOperatorsTest(BlenderTestCase):
|
||||
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_and_relief_start_jobs_with_their_plane_type(self):
|
||||
for plane_type, operator_class in (("DEPTH", self.convert_to_plane.ConvertToDepthPlane), ("RELIEF", self.convert_to_plane.ConvertToReliefPlane)):
|
||||
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(operator_class.execute(operator, context), {"FINISHED"})
|
||||
self.assertNotIn("depth_mode", generate.call_args.kwargs)
|
||||
self.assertEqual(generate.call_args.kwargs["source_object_name"], "Source")
|
||||
self.assertEqual(generate.call_args.kwargs["plane_type"], plane_type)
|
||||
|
||||
def test_depth_and_relief_results_use_their_normal_space(self):
|
||||
for plane_type, normal_space in (("DEPTH", "OBJECT"), ("RELIEF", "TANGENT")):
|
||||
normal_key = f"{normal_space.lower()}_normal"
|
||||
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"),
|
||||
normal_key: Path(f"{normal_key}.png"),
|
||||
}
|
||||
result = SimpleNamespace(
|
||||
directory=Path("result"),
|
||||
file=lambda key: files[key],
|
||||
)
|
||||
operator = SimpleNamespace(source_object_name="Source", mesh_detail=6, plane_type=plane_type)
|
||||
context = SimpleNamespace(scene=object())
|
||||
ops = SimpleNamespace(
|
||||
ed=SimpleNamespace(undo_push=Mock(return_value={"FINISHED"}))
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
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,
|
||||
)
|
||||
load_depth.assert_called_once_with(files["depth"], source, metadata)
|
||||
ops.ed.undo_push.assert_called_once_with(message=f"Convert to {plane_type.title()} Plane")
|
||||
load_normal.assert_called_once_with(files[normal_key], source)
|
||||
create_material.assert_called_once_with(
|
||||
source.data,
|
||||
color_image,
|
||||
normal_image=normal_image,
|
||||
normal_space=normal_space,
|
||||
scene=context.scene,
|
||||
)
|
||||
create_object.assert_called_once_with(
|
||||
context,
|
||||
source,
|
||||
6,
|
||||
material,
|
||||
depth_image,
|
||||
metadata,
|
||||
plane_type,
|
||||
)
|
||||
|
||||
|
||||
def test_depth_plane_result_cleans_unused_depth_and_normal_on_failure(self):
|
||||
@@ -149,7 +151,7 @@ class PlaneOperatorsTest(BlenderTestCase):
|
||||
files = {
|
||||
"depth_metadata": Path("depth.json"),
|
||||
"depth": Path("depth.exr"),
|
||||
"tangent_normal": Path("tangent-normal.png"),
|
||||
"object_normal": Path("object-normal.png"),
|
||||
}
|
||||
result = SimpleNamespace(
|
||||
directory=Path("result"),
|
||||
@@ -184,7 +186,7 @@ class PlaneOperatorsTest(BlenderTestCase):
|
||||
module.create_depth_plane_from_result(
|
||||
SimpleNamespace(scene=object()),
|
||||
result,
|
||||
SimpleNamespace(source_object_name="Source", mesh_detail=6, generate_depth=True),
|
||||
SimpleNamespace(source_object_name="Source", mesh_detail=6, plane_type="DEPTH"),
|
||||
)
|
||||
|
||||
remove_material.assert_called_once_with(material, do_unlink=True)
|
||||
|
||||
Reference in New Issue
Block a user