diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 90608737c2..eaf5015ba8 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -37,6 +37,95 @@ class RenderProducts(object): ) } + def get_multiple_beauty(self, outputs, cameras): + beauty_output_frames = dict() + for output, camera in zip(outputs, cameras): + filename, ext = os.path.splitext(output) + filename = filename.replace(".", "") + ext = ext.replace(".", "") + start_frame = int(rt.rendStart) + end_frame = int(rt.rendEnd) + 1 + new_beauty = self.get_expected_beauty( + filename, start_frame, end_frame, ext + ) + beauty_output = ({ + f"{camera}_beauty": new_beauty + }) + beauty_output_frames.update(beauty_output) + return beauty_output_frames + + def get_multiple_aovs(self, outputs, cameras): + renderer_class = get_current_renderer() + renderer = str(renderer_class).split(":")[0] + aovs_frames = {} + for output, camera in zip(outputs, cameras): + filename, ext = os.path.splitext(output) + filename = filename.replace(".", "") + ext = ext.replace(".", "") + start_frame = int(rt.rendStart) + end_frame = int(rt.rendEnd) + 1 + + if renderer in [ + "ART_Renderer", + "V_Ray_6_Hotfix_3", + "V_Ray_GPU_6_Hotfix_3", + "Default_Scanline_Renderer", + "Quicksilver_Hardware_Renderer", + ]: + render_name = self.get_render_elements_name() + if render_name: + for name in render_name: + aovs_frames.update({ + f"{camera}_{name}": self.get_expected_aovs( + filename, name, start_frame, + end_frame, ext) + }) + elif renderer == "Redshift_Renderer": + render_name = self.get_render_elements_name() + if render_name: + rs_aov_files = rt.Execute("renderers.current.separateAovFiles") # noqa + # this doesn't work, always returns False + # rs_AovFiles = rt.RedShift_Renderer().separateAovFiles + if ext == "exr" and not rs_aov_files: + for name in render_name: + if name == "RsCryptomatte": + aovs_frames.update({ + f"{camera}_{name}": self.get_expected_aovs( + filename, name, start_frame, + end_frame, ext) + }) + else: + for name in render_name: + aovs_frames.update({ + f"{camera}_{name}": self.get_expected_aovs( + filename, name, start_frame, + end_frame, ext) + }) + elif renderer == "Arnold": + render_name = self.get_arnold_product_name() + if render_name: + for name in render_name: + aovs_frames.update({ + f"{camera}_{name}": self.get_expected_arnold_product( # noqa + filename, name, start_frame, + end_frame, ext) + }) + elif renderer in [ + "V_Ray_6_Hotfix_3", + "V_Ray_GPU_6_Hotfix_3" + ]: + if ext != "exr": + render_name = self.get_render_elements_name() + if render_name: + for name in render_name: + aovs_frames.update({ + f"{camera}_{name}": self.get_expected_aovs( + filename, name, start_frame, + end_frame, ext) + }) + + return aovs_frames + def get_aovs(self, container): render_dir = os.path.dirname(rt.rendOutputFilename) @@ -63,7 +152,7 @@ class RenderProducts(object): if render_name: for name in render_name: render_dict.update({ - name: self.get_expected_render_elements( + name: self.get_expected_aovs( output_file, name, start_frame, end_frame, img_fmt) }) @@ -77,14 +166,14 @@ class RenderProducts(object): for name in render_name: if name == "RsCryptomatte": render_dict.update({ - name: self.get_expected_render_elements( + name: self.get_expected_aovs( output_file, name, start_frame, end_frame, img_fmt) }) else: for name in render_name: render_dict.update({ - name: self.get_expected_render_elements( + name: self.get_expected_aovs( output_file, name, start_frame, end_frame, img_fmt) }) @@ -95,7 +184,8 @@ class RenderProducts(object): for name in render_name: render_dict.update({ name: self.get_expected_arnold_product( - output_file, name, start_frame, end_frame, img_fmt) + output_file, name, start_frame, + end_frame, img_fmt) }) elif renderer in [ "V_Ray_6_Hotfix_3", @@ -106,7 +196,7 @@ class RenderProducts(object): if render_name: for name in render_name: render_dict.update({ - name: self.get_expected_render_elements( + name: self.get_expected_aovs( output_file, name, start_frame, end_frame, img_fmt) # noqa }) @@ -169,8 +259,8 @@ class RenderProducts(object): return render_name - def get_expected_render_elements(self, folder, name, - start_frame, end_frame, fmt): + def get_expected_aovs(self, folder, name, + start_frame, end_frame, fmt): """Get all the expected render element output files. """ render_elements = [] for f in range(start_frame, end_frame): diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index 26e176aa8d..be50e296eb 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -74,13 +74,13 @@ class RenderSettings(object): output = os.path.join(output_dir, container) try: aov_separator = self._aov_chars[( - self._project_settings["maya"] + self._project_settings["max"] ["RenderSettings"] ["aov_separator"] )] except KeyError: aov_separator = "." - output_filename = "{0}..{1}".format(output, img_fmt) + output_filename = f"{output}..{img_fmt}" output_filename = output_filename.replace("{aov_separator}", aov_separator) rt.rendOutputFilename = output_filename @@ -146,13 +146,13 @@ class RenderSettings(object): for i in range(render_elem_num): renderlayer_name = render_elem.GetRenderElement(i) target, renderpass = str(renderlayer_name).split(":") - aov_name = "{0}_{1}..{2}".format(dir, renderpass, ext) + aov_name = f"{dir}_{renderpass}..{ext}" render_elem.SetRenderElementFileName(i, aov_name) def get_render_output(self, container, output_dir): output = os.path.join(output_dir, container) img_fmt = self._project_settings["max"]["RenderSettings"]["image_format"] # noqa - output_filename = "{0}..{1}".format(output, img_fmt) + output_filename = f"{output}..{img_fmt}" return output_filename def get_render_element(self): @@ -167,3 +167,61 @@ class RenderSettings(object): orig_render_elem.append(render_element) return orig_render_elem + + def get_batch_render_elements(self, container, + output_dir, camera): + render_element_list = list() + output = os.path.join(output_dir, container) + render_elem = rt.maxOps.GetCurRenderElementMgr() + render_elem_num = render_elem.NumRenderElements() + if render_elem_num < 0: + return + img_fmt = self._project_settings["max"]["RenderSettings"]["image_format"] # noqa + + for i in range(render_elem_num): + renderlayer_name = render_elem.GetRenderElement(i) + target, renderpass = str(renderlayer_name).split(":") + aov_name = f"{output}_{camera}_{renderpass}..{img_fmt}" + render_element_list.append(aov_name) + return render_element_list + + def get_batch_render_output(self, camera): + target_layer_no = rt.batchRenderMgr.FindView(camera) + target_layer = rt.batchRenderMgr.GetView(target_layer_no) + return target_layer.outputFilename + + def batch_render_elements(self, camera): + target_layer_no = rt.batchRenderMgr.FindView(camera) + target_layer = rt.batchRenderMgr.GetView(target_layer_no) + outputfilename = target_layer.outputFilename + directory = os.path.dirname(outputfilename) + render_elem = rt.maxOps.GetCurRenderElementMgr() + render_elem_num = render_elem.NumRenderElements() + if render_elem_num < 0: + return + ext = self._project_settings["max"]["RenderSettings"]["image_format"] # noqa + + for i in range(render_elem_num): + renderlayer_name = render_elem.GetRenderElement(i) + target, renderpass = str(renderlayer_name).split(":") + aov_name = f"{directory}_{camera}_{renderpass}..{ext}" + render_elem.SetRenderElementFileName(i, aov_name) + + def batch_render_layer(self, container, + output_dir, cameras): + outputs = list() + output = os.path.join(output_dir, container) + img_fmt = self._project_settings["max"]["RenderSettings"]["image_format"] # noqa + for cam in cameras: + camera = rt.getNodeByName(cam) + layer_no = rt.batchRenderMgr.FindView(cam) + renderlayer = None + if layer_no == 0: + renderlayer = rt.batchRenderMgr.CreateView(camera) + else: + renderlayer = rt.batchRenderMgr.GetView(layer_no) + # use camera name as renderlayer name + renderlayer.name = cam + renderlayer.outputFilename = f"{output}_{cam}..{img_fmt}" + outputs.append(renderlayer.outputFilename) + return outputs diff --git a/openpype/hosts/max/plugins/create/create_render.py b/openpype/hosts/max/plugins/create/create_render.py index 9cc3c8da8a..617334753a 100644 --- a/openpype/hosts/max/plugins/create/create_render.py +++ b/openpype/hosts/max/plugins/create/create_render.py @@ -2,6 +2,7 @@ """Creator plugin for creating camera.""" import os from openpype.hosts.max.api import plugin +from openpype.lib import BoolDef from openpype.hosts.max.api.lib_rendersettings import RenderSettings @@ -17,15 +18,33 @@ class CreateRender(plugin.MaxCreator): file = rt.maxFileName filename, _ = os.path.splitext(file) instance_data["AssetName"] = filename + instance_data["multiCamera"] = pre_create_data.get("multi_cam") + num_of_renderlayer = rt.batchRenderMgr.numViews + if num_of_renderlayer > 0: + rt.batchRenderMgr.DeleteView(num_of_renderlayer) instance = super(CreateRender, self).create( subset_name, instance_data, pre_create_data) + container_name = instance.data.get("instance_node") - sel_obj = self.selected_nodes - if sel_obj: - # set viewport camera for rendering(mandatory for deadline) - RenderSettings(self.project_settings).set_render_camera(sel_obj) # set output paths for rendering(mandatory for deadline) RenderSettings().render_output(container_name) + # TODO: create multiple camera options + if self.selected_nodes: + selected_nodes_name = [] + for sel in self.selected_nodes: + name = sel.name + selected_nodes_name.append(name) + RenderSettings().batch_render_layer( + container_name, filename, + selected_nodes_name) + + def get_pre_create_attr_defs(self): + attrs = super(CreateRender, self).get_pre_create_attr_defs() + return attrs + [ + BoolDef("multi_cam", + label="Multiple Cameras Submission", + default=False), + ] diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index 38194a0735..8abffa5ab6 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -4,8 +4,10 @@ import os import pyblish.api from pymxs import runtime as rt +from openpype.pipeline.publish import KnownPublishError from openpype.hosts.max.api import colorspace from openpype.hosts.max.api.lib import get_max_version, get_current_renderer +from openpype.hosts.max.api.lib_rendersettings import RenderSettings from openpype.hosts.max.api.lib_renderproducts import RenderProducts @@ -23,7 +25,6 @@ class CollectRender(pyblish.api.InstancePlugin): file = rt.maxFileName current_file = os.path.join(folder, file) filepath = current_file.replace("\\", "/") - context.data['currentFile'] = current_file files_by_aov = RenderProducts().get_beauty(instance.name) @@ -39,6 +40,28 @@ class CollectRender(pyblish.api.InstancePlugin): instance.data["cameras"] = [camera.name] if camera else None # noqa + if instance.data.get("multiCamera"): + cameras = instance.data.get("members") + if not cameras: + raise KnownPublishError("There should be at least" + " one renderable camera in container") + sel_cam = [ + c.name for c in cameras + if rt.classOf(c) in rt.Camera.classes] + container_name = instance.data.get("instance_node") + render_dir = os.path.dirname(rt.rendOutputFilename) + outputs = RenderSettings().batch_render_layer( + container_name, render_dir, sel_cam + ) + + instance.data["cameras"] = sel_cam + + files_by_aov = RenderProducts().get_multiple_beauty( + outputs, sel_cam) + aovs = RenderProducts().get_multiple_aovs( + outputs, sel_cam) + files_by_aov.update(aovs) + if "expectedFiles" not in instance.data: instance.data["expectedFiles"] = list() instance.data["files"] = list() diff --git a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py new file mode 100644 index 0000000000..c39109417b --- /dev/null +++ b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py @@ -0,0 +1,100 @@ +import pyblish.api +import os +import sys +import tempfile + +from pymxs import runtime as rt +from openpype.lib import run_subprocess +from openpype.hosts.max.api.lib_rendersettings import RenderSettings +from openpype.hosts.max.api.lib_renderproducts import RenderProducts + + +class SaveScenesForCamera(pyblish.api.InstancePlugin): + """Save scene files for multiple cameras without + editing the original scene before deadline submission + + """ + + label = "Save Scene files for cameras" + order = pyblish.api.ExtractorOrder - 0.48 + hosts = ["max"] + families = ["maxrender"] + + def process(self, instance): + current_folder = rt.maxFilePath + current_filename = rt.maxFileName + current_filepath = os.path.join(current_folder, current_filename) + camera_scene_files = [] + scripts = [] + filename, ext = os.path.splitext(current_filename) + fmt = RenderProducts().image_format() + cameras = instance.data.get("cameras") + if not cameras: + return + new_folder = f"{current_folder}_{filename}" + os.makedirs(new_folder, exist_ok=True) + for camera in cameras: + new_output = RenderSettings().get_batch_render_output(camera) # noqa + new_output = new_output.replace("\\", "/") + new_filename = f"{filename}_{camera}{ext}" + new_filepath = os.path.join(new_folder, new_filename) + new_filepath = new_filepath.replace("\\", "/") + camera_scene_files.append(new_filepath) + RenderSettings().batch_render_elements(camera) + rt.rendOutputFilename = new_output + rt.saveMaxFile(current_filepath) + script = (""" +from pymxs import runtime as rt +import os +filename = "{filename}" +new_filepath = "{new_filepath}" +new_output = "{new_output}" +camera = "{camera}" +rt.rendOutputFilename = new_output +directory = os.path.dirname(rt.rendOutputFilename) +directory = os.path.join(directory, filename) +render_elem = rt.maxOps.GetCurRenderElementMgr() +render_elem_num = render_elem.NumRenderElements() +if render_elem_num > 0: + ext = "{ext}" + for i in range(render_elem_num): + renderlayer_name = render_elem.GetRenderElement(i) + target, renderpass = str(renderlayer_name).split(":") + aov_name = f"{{directory}}_{camera}_{{renderpass}}..{ext}" + render_elem.SetRenderElementFileName(i, aov_name) +rt.saveMaxFile(new_filepath) + """).format(filename=instance.name, + new_filepath=new_filepath, + new_output=new_output, + camera=camera, + ext=fmt) + scripts.append(script) + + maxbatch_exe = os.path.join( + os.path.dirname(sys.executable), "3dsmaxbatch") + maxbatch_exe = maxbatch_exe.replace("\\", "/") + if sys.platform == "windows": + maxbatch_exe += ".exe" + maxbatch_exe = os.path.normpath(maxbatch_exe) + with tempfile.TemporaryDirectory() as tmp_dir_name: + tmp_script_path = os.path.join( + tmp_dir_name, "extract_scene_files.py") + self.log.info("Using script file: {}".format(tmp_script_path)) + + with open(tmp_script_path, "wt") as tmp: + for script in scripts: + tmp.write(script + "\n") + + try: + current_filepath = current_filepath.replace("\\", "/") + tmp_script_path = tmp_script_path.replace("\\", "/") + run_subprocess([maxbatch_exe, tmp_script_path, + "-sceneFile", current_filepath]) + except RuntimeError: + self.log.debug("Checking the scene files existing") + + for camera_scene in camera_scene_files: + if not os.path.exists(camera_scene): + self.log.error("Camera scene files not existed yet!") + raise RuntimeError("MaxBatch.exe doesn't run as expected") + self.log.debug(f"Found Camera scene:{camera_scene}") diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index 23d4183132..f06bd4dbe6 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -15,6 +15,12 @@ from openpype.pipeline import ( from openpype.pipeline.publish.lib import ( replace_with_published_scene_path ) +from openpype.pipeline.publish import KnownPublishError +from openpype.hosts.max.api.lib import ( + get_current_renderer, + get_multipass_setting +) +from openpype.hosts.max.api.lib_rendersettings import RenderSettings from openpype_modules.deadline import abstract_submit_deadline from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo from openpype.lib import is_running_from_build @@ -54,7 +60,7 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, cls.priority) cls.chuck_size = settings.get("chunk_size", cls.chunk_size) cls.group = settings.get("group", cls.group) - + # TODO: multiple camera instance, separate job infos def get_job_info(self): job_info = DeadlineJobInfo(Plugin="3dsmax") @@ -71,7 +77,6 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, src_filepath = context.data["currentFile"] src_filename = os.path.basename(src_filepath) - job_info.Name = "%s - %s" % (src_filename, instance.name) job_info.BatchName = src_filename job_info.Plugin = instance.data["plugin"] @@ -134,11 +139,11 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, # Add list of expected files to job # --------------------------------- - exp = instance.data.get("expectedFiles") - - for filepath in self._iter_expected_files(exp): - job_info.OutputDirectory += os.path.dirname(filepath) - job_info.OutputFilename += os.path.basename(filepath) + if not instance.data.get("multiCamera"): + exp = instance.data.get("expectedFiles") + for filepath in self._iter_expected_files(exp): + job_info.OutputDirectory += os.path.dirname(filepath) + job_info.OutputFilename += os.path.basename(filepath) return job_info @@ -163,11 +168,11 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, def process_submission(self): instance = self._instance - filepath = self.scene_path + filepath = instance.context.data["currentFile"] files = instance.data["expectedFiles"] if not files: - raise RuntimeError("No Render Elements found!") + raise KnownPublishError("No Render Elements found!") first_file = next(self._iter_expected_files(files)) output_dir = os.path.dirname(first_file) instance.data["outputDir"] = output_dir @@ -181,9 +186,17 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, self.log.debug("Submitting 3dsMax render..") project_settings = instance.context.data["project_settings"] - payload = self._use_published_name(payload_data, project_settings) - job_info, plugin_info = payload - self.submit(self.assemble_payload(job_info, plugin_info)) + if instance.data.get("multiCamera"): + self.log.debug("Submitting jobs for multiple cameras..") + payload = self._use_published_name_for_multiples( + payload_data, project_settings) + job_infos, plugin_infos = payload + for job_info, plugin_info in zip(job_infos, plugin_infos): + self.submit(self.assemble_payload(job_info, plugin_info)) + else: + payload = self._use_published_name(payload_data, project_settings) + job_info, plugin_info = payload + self.submit(self.assemble_payload(job_info, plugin_info)) def _use_published_name(self, data, project_settings): # Not all hosts can import these modules. @@ -206,7 +219,7 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, files = instance.data.get("expectedFiles") if not files: - raise RuntimeError("No render elements found") + raise KnownPublishError("No render elements found") first_file = next(self._iter_expected_files(files)) old_output_dir = os.path.dirname(first_file) output_beauty = RenderSettings().get_render_output(instance.name, @@ -218,6 +231,7 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, plugin_data["RenderOutput"] = beauty_name # as 3dsmax has version with different languages plugin_data["Language"] = "ENU" + renderer_class = get_current_renderer() renderer = str(renderer_class).split(":")[0] @@ -249,6 +263,120 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, return job_info, plugin_info + def get_job_info_through_camera(self, camera): + """Get the job parameters for deadline submission when + multi-camera is enabled. + Args: + infos(dict): a dictionary with job info. + """ + instance = self._instance + context = instance.context + job_info = copy.deepcopy(self.job_info) + exp = instance.data.get("expectedFiles") + + src_filepath = context.data["currentFile"] + src_filename = os.path.basename(src_filepath) + job_info.Name = "%s - %s - %s" % ( + src_filename, instance.name, camera) + for filepath in self._iter_expected_files(exp): + if camera not in filepath: + continue + job_info.OutputDirectory += os.path.dirname(filepath) + job_info.OutputFilename += os.path.basename(filepath) + + return job_info + # set the output filepath with the relative camera + + def get_plugin_info_through_camera(self, camera): + """Get the plugin parameters for deadline submission when + multi-camera is enabled. + Args: + infos(dict): a dictionary with plugin info. + """ + instance = self._instance + # set the target camera + plugin_info = copy.deepcopy(self.plugin_info) + + plugin_data = {} + # set the output filepath with the relative camera + if instance.data.get("multiCamera"): + scene_filepath = instance.context.data["currentFile"] + scene_filename = os.path.basename(scene_filepath) + scene_directory = os.path.dirname(scene_filepath) + current_filename, ext = os.path.splitext(scene_filename) + camera_scene_name = f"{current_filename}_{camera}{ext}" + camera_scene_filepath = os.path.join( + scene_directory, f"_{current_filename}", camera_scene_name) + plugin_data["SceneFile"] = camera_scene_filepath + + files = instance.data.get("expectedFiles") + if not files: + raise KnownPublishError("No render elements found") + first_file = next(self._iter_expected_files(files)) + old_output_dir = os.path.dirname(first_file) + rgb_output = RenderSettings().get_batch_render_output(camera) # noqa + rgb_bname = os.path.basename(rgb_output) + dir = os.path.dirname(first_file) + beauty_name = f"{dir}/{rgb_bname}" + beauty_name = beauty_name.replace("\\", "/") + plugin_info["RenderOutput"] = beauty_name + renderer_class = get_current_renderer() + + renderer = str(renderer_class).split(":")[0] + if renderer in [ + "ART_Renderer", + "Redshift_Renderer", + "V_Ray_6_Hotfix_3", + "V_Ray_GPU_6_Hotfix_3", + "Default_Scanline_Renderer", + "Quicksilver_Hardware_Renderer", + ]: + render_elem_list = RenderSettings().get_batch_render_elements( + instance.name, old_output_dir, camera + ) + for i, element in enumerate(render_elem_list): + if camera in element: + elem_bname = os.path.basename(element) + new_elem = f"{dir}/{elem_bname}" + new_elem = new_elem.replace("/", "\\") + plugin_info["RenderElementOutputFilename%d" % i] = new_elem # noqa + + if camera: + # set the default camera and target camera + # (weird parameters from max) + plugin_data["Camera"] = camera + plugin_data["Camera1"] = camera + plugin_data["Camera0"] = None + + plugin_info.update(plugin_data) + return plugin_info + + def _use_published_name_for_multiples(self, data, project_settings): + """Process the parameters submission for deadline when + user enables multi-cameras option. + Args: + job_info_list (list): A list of multiple job infos + plugin_info_list (list): A list of multiple plugin infos + """ + job_info_list = [] + plugin_info_list = [] + instance = self._instance + cameras = instance.data.get("cameras", []) + plugin_data = {} + multipass = get_multipass_setting(project_settings) + if multipass: + plugin_data["DisableMultipass"] = 0 + else: + plugin_data["DisableMultipass"] = 1 + for cam in cameras: + job_info = self.get_job_info_through_camera(cam) + plugin_info = self.get_plugin_info_through_camera(cam) + plugin_info.update(plugin_data) + job_info_list.append(job_info) + plugin_info_list.append(plugin_info) + + return job_info_list, plugin_info_list + def from_published_scene(self, replace_in_path=True): instance = self._instance if instance.data["renderer"] == "Redshift_Renderer": diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py index 54ff2627e1..975fdd31cc 100644 --- a/openpype/pipeline/farm/pyblish_functions.py +++ b/openpype/pipeline/farm/pyblish_functions.py @@ -582,16 +582,17 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, group_name = subset # if there are multiple cameras, we need to add camera name - if isinstance(col, (list, tuple)): - cam = [c for c in cameras if c in col[0]] - else: - # in case of single frame - cam = [c for c in cameras if c in col] - if cam: - if aov: - subset_name = '{}_{}_{}'.format(group_name, cam, aov) - else: - subset_name = '{}_{}'.format(group_name, cam) + expected_filepath = col[0] if isinstance(col, (list, tuple)) else col + cams = [cam for cam in cameras if cam in expected_filepath] + if cams: + for cam in cams: + if aov: + if not aov.startswith(cam): + subset_name = '{}_{}_{}'.format(group_name, cam, aov) + else: + subset_name = "{}_{}".format(group_name, aov) + else: + subset_name = '{}_{}'.format(group_name, cam) else: if aov: subset_name = '{}_{}'.format(group_name, aov)