From 9f56721334c8a7d42aa88c34ee4ef01befeb153d Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Tue, 22 Aug 2023 15:56:23 +0100 Subject: [PATCH] Implemented AOVs rendering in deadline and publishing --- openpype/hosts/blender/api/colorspace.py | 52 +++++++++++ .../blender/plugins/publish/collect_render.py | 92 ++++++++++++++----- .../publish/submit_blender_deadline.py | 3 +- 3 files changed, 122 insertions(+), 25 deletions(-) create mode 100644 openpype/hosts/blender/api/colorspace.py diff --git a/openpype/hosts/blender/api/colorspace.py b/openpype/hosts/blender/api/colorspace.py new file mode 100644 index 0000000000..59deb514f8 --- /dev/null +++ b/openpype/hosts/blender/api/colorspace.py @@ -0,0 +1,52 @@ +import attr + +import bpy + + +@attr.s +class LayerMetadata(object): + """Data class for Render Layer metadata.""" + frameStart = attr.ib() + frameEnd = attr.ib() + + +@attr.s +class RenderProduct(object): + """Getting Colorspace as + Specific Render Product Parameter for submitting + publish job. + """ + colorspace = attr.ib() # colorspace + view = attr.ib() + productName = attr.ib(default=None) + + +class ARenderProduct(object): + + def __init__(self): + """Constructor.""" + # Initialize + self.layer_data = self._get_layer_data() + self.layer_data.products = self.get_colorspace_data() + + def _get_layer_data(self): + scene = bpy.context.scene + + return LayerMetadata( + frameStart=int(scene.frame_start), + frameEnd=int(scene.frame_end), + ) + + def get_colorspace_data(self): + """To be implemented by renderer class. + This should return a list of RenderProducts. + Returns: + list: List of RenderProduct + """ + return [ + RenderProduct( + colorspace="sRGB", + view="ACES 1.0", + productName="" + ) + ] diff --git a/openpype/hosts/blender/plugins/publish/collect_render.py b/openpype/hosts/blender/plugins/publish/collect_render.py index cd3b922697..becb735c21 100644 --- a/openpype/hosts/blender/plugins/publish/collect_render.py +++ b/openpype/hosts/blender/plugins/publish/collect_render.py @@ -12,10 +12,7 @@ from openpype.pipeline import ( from openpype.settings import ( get_project_settings, ) -from openpype.hosts.blender.api.ops import ( - MainThreadItem, - execute_in_main_thread -) +from openpype.hosts.blender.api import colorspace import pyblish.api @@ -73,12 +70,13 @@ class CollectBlenderRender(pyblish.api.InstancePlugin): return render_product @staticmethod - def generate_expected_files( - render_product, frame_start, frame_end, frame_step + def generate_expected_beauty( + render_product, frame_start, frame_end, frame_step, ext ): - """Generate the expected files for the render product. - This returns a list of files that should be rendered. It replaces - the sequence of `#` with the frame number. + """ + Generate the expected files for the render product for the beauty + render. This returns a list of files that should be rendered. It + replaces the sequence of `#` with the frame number. """ path = os.path.dirname(render_product) file = os.path.basename(render_product) @@ -87,9 +85,39 @@ class CollectBlenderRender(pyblish.api.InstancePlugin): for frame in range(frame_start, frame_end + 1, frame_step): frame_str = str(frame).rjust(4, "0") - expected_file = os.path.join(path, re.sub("#+", frame_str, file)) + filename = re.sub("#+", frame_str, file) + expected_file = f"{os.path.join(path, filename)}.{ext}" expected_files.append(expected_file.replace("\\", "/")) + return { + "beauty": expected_files + } + + @staticmethod + def generate_expected_aovs( + aov_file_product, frame_start, frame_end, frame_step, ext + ): + """ + Generate the expected files for the render product for the beauty + render. This returns a list of files that should be rendered. It + replaces the sequence of `#` with the frame number. + """ + expected_files = {} + + for aov_name, aov_file in aov_file_product: + path = os.path.dirname(aov_file) + file = os.path.basename(aov_file) + + aov_files = [] + + for frame in range(frame_start, frame_end + 1, frame_step): + frame_str = str(frame).rjust(4, "0") + filename = re.sub("#+", frame_str, file) + expected_file = f"{os.path.join(path, filename)}.{ext}" + aov_files.append(expected_file.replace("\\", "/")) + + expected_files[aov_name] = aov_files + return expected_files @staticmethod @@ -117,7 +145,7 @@ class CollectBlenderRender(pyblish.api.InstancePlugin): elif ext == "tif": image_settings.file_format = "TIFF" - def _create_context(): + def _create_context(self): context = bpy.context.copy() win = bpy.context.window_manager.windows[0] @@ -132,7 +160,7 @@ class CollectBlenderRender(pyblish.api.InstancePlugin): return context - def _set_node_tree(self, output_path, instance): + def set_node_tree(self, output_path, instance): # Set the scene to use the compositor node tree to render bpy.context.scene.use_nodes = True @@ -153,8 +181,9 @@ class CollectBlenderRender(pyblish.api.InstancePlugin): # render. # We also exclude some layers. exclude_sockets = ["Image", "Alpha"] - passes = [ - socket for socket in rl_node.outputs + passes = [ + socket + for socket in rl_node.outputs if socket.enabled and socket.name not in exclude_sockets ] @@ -182,11 +211,16 @@ class CollectBlenderRender(pyblish.api.InstancePlugin): image_settings = bpy.context.scene.render.image_settings output.format.file_format = image_settings.file_format + aov_file_products = [] + # For each active render pass, we add a new socket to the output node # and link it for render_pass in passes: - bpy.ops.node.output_file_add_socket( - context, file_path=f"{instance.name}_{render_pass.name}.####") + filepath = f"{instance.name}_{render_pass.name}.####" + bpy.ops.node.output_file_add_socket(context, file_path=filepath) + + aov_file_products.append( + (render_pass.name, os.path.join(output_path, filepath))) node_input = output.inputs[-1] @@ -195,10 +229,7 @@ class CollectBlenderRender(pyblish.api.InstancePlugin): # Restore the area type area.ui_type = old_area_type - def set_node_tree(self, output_path, instance): - """ Run the creator on Blender main thread""" - mti = MainThreadItem(self._set_node_tree, output_path, instance) - execute_in_main_thread(mti) + return aov_file_products @staticmethod def set_render_camera(instance): @@ -230,7 +261,7 @@ class CollectBlenderRender(pyblish.api.InstancePlugin): output_path = os.path.join(file_path, render_folder, file_name) render_product = self.get_render_product(output_path, instance) - self.set_node_tree(output_path, instance) + aov_file_product = self.set_node_tree(output_path, instance) # We set the render path, the format and the camera bpy.context.scene.render.filepath = render_product @@ -245,9 +276,15 @@ class CollectBlenderRender(pyblish.api.InstancePlugin): frame_handle_start = context.data["frameStartHandle"] frame_handle_end = context.data["frameEndHandle"] - expected_files = self.generate_expected_files( + expected_beauty = self.generate_expected_beauty( render_product, int(frame_start), int(frame_end), - int(bpy.context.scene.frame_step)) + int(bpy.context.scene.frame_step), ext) + + expected_aovs = self.generate_expected_aovs( + aov_file_product, int(frame_start), int(frame_end), + int(bpy.context.scene.frame_step), ext) + + expected_files = expected_beauty | expected_aovs instance.data.update({ "frameStart": frame_start, @@ -257,7 +294,14 @@ class CollectBlenderRender(pyblish.api.InstancePlugin): "fps": context.data["fps"], "byFrameStep": bpy.context.scene.frame_step, "farm": True, - "expectedFiles": expected_files, + "expectedFiles": [expected_files], + # OCIO not currently implemented in Blender, but the following + # settings are required by the schema, so it is hardcoded. + # TODO: Implement OCIO in Blender + "colorspaceConfig": "", + "colorspaceDisplay": "sRGB", + "colorspaceView": "ACES 1.0 SDR-video", + "renderProducts": colorspace.ARenderProduct(), }) self.log.info(f"data: {instance.data}") diff --git a/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py b/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py index 9bfc4fbb07..ad456c0d13 100644 --- a/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py @@ -163,7 +163,8 @@ class BlenderSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): if not expected_files: raise RuntimeError("No Render Elements found!") - output_dir = os.path.dirname(expected_files[0]) + first_file = next(iter_expected_files(expected_files)) + output_dir = os.path.dirname(first_file) instance.data["outputDir"] = output_dir instance.data["toBeRenderedOn"] = "deadline"