From 058fcb97a358a972d34a4cabd45773e249e15cf0 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 9 Jun 2023 17:51:54 +0800 Subject: [PATCH 01/61] multiple render camera supports for 3dsmax --- openpype/hosts/max/api/lib_rendersettings.py | 11 ++-------- .../hosts/max/plugins/create/create_render.py | 22 ++++++++++++++----- .../max/plugins/publish/collect_render.py | 5 +++++ .../plugins/publish/submit_max_deadline.py | 11 ++++++++++ 4 files changed, 34 insertions(+), 15 deletions(-) diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index 91e4a5bf9b..db8dee3340 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -35,15 +35,8 @@ class RenderSettings(object): ) def set_render_camera(self, selection): - for sel in selection: - # to avoid Attribute Error from pymxs wrapper - found = False - if rt.classOf(sel) in rt.Camera.classes: - found = True - rt.viewport.setCamera(sel) - break - if not found: - raise RuntimeError("Camera not found") + # to avoid Attribute Error from pymxs wrapper + return rt.viewport.setCamera(selection[0]) def render_output(self, container): folder = rt.maxFilePath diff --git a/openpype/hosts/max/plugins/create/create_render.py b/openpype/hosts/max/plugins/create/create_render.py index 5ad895b86e..ec5635b81b 100644 --- a/openpype/hosts/max/plugins/create/create_render.py +++ b/openpype/hosts/max/plugins/create/create_render.py @@ -14,7 +14,10 @@ class CreateRender(plugin.MaxCreator): def create(self, subset_name, instance_data, pre_create_data): from pymxs import runtime as rt - sel_obj = list(rt.selection) + sel_obj = [ + c for c in rt.Objects + if rt.classOf(c) in rt.Camera.classes] + file = rt.maxFileName filename, _ = os.path.splitext(file) instance_data["AssetName"] = filename @@ -24,11 +27,20 @@ class CreateRender(plugin.MaxCreator): instance_data, pre_create_data) # type: CreatedInstance container_name = instance.data.get("instance_node") - container = rt.getNodeByName(container_name) # TODO: Disable "Add to Containers?" Panel # parent the selected cameras into the container - for obj in sel_obj: - obj.parent = container + if self.selected_nodes: + # set viewport camera for + # rendering(mandatory for deadline) + sel_obj = [ + c for c in rt.getCurrentSelection() + if rt.classOf(c) in rt.Camera.classes] + + if not sel_obj: + raise RuntimeError("Please add at least one camera to the scene " + "before creating the render instance") + + RenderSettings().set_render_camera(sel_obj) # for additional work on the node: # instance_node = rt.getNodeByName(instance.get("instance_node")) @@ -37,7 +49,5 @@ class CreateRender(plugin.MaxCreator): # Changing the Render Setup dialog settings should be done # with the actual Render Setup dialog in a closed state. - # set viewport camera for rendering(mandatory for deadline) - RenderSettings().set_render_camera(sel_obj) # set output paths for rendering(mandatory for deadline) RenderSettings().render_output(container_name) diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index db5c84fad9..cbb3a7b4d6 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -48,6 +48,10 @@ class CollectRender(pyblish.api.InstancePlugin): instance.name, asset_id) self.log.debug("version_doc: {0}".format(version_doc)) + sel_obj = [ + c for c in rt.Objects + if rt.classOf(c) in rt.Camera.classes] + version_int = 1 if version_doc: version_int += int(version_doc["name"]) @@ -78,6 +82,7 @@ class CollectRender(pyblish.api.InstancePlugin): "renderer": renderer, "source": filepath, "plugin": "3dsmax", + "cameras": sel_obj, "frameStart": int(rt.rendStart), "frameEnd": int(rt.rendEnd), "version": version_int, diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index b6a30e36b7..ff5adc39ad 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -212,6 +212,17 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, plugin_data["RenderOutput"] = beauty_name # as 3dsmax has version with different languages plugin_data["Language"] = "ENU" + render_cameras = instance.data["cameras"] + if render_cameras: + for i, camera in enumerate(render_cameras): + cam_name = "Camera%s" % (i + 1) + plugin_data[cam_name] = camera.name + # set the default camera + plugin_data["Camera"] = render_cameras[0].name + # set empty camera of Camera 0 for the ' + # correct parameter submission + plugin_data["Camera0"] = None + renderer_class = get_current_renderer() renderer = str(renderer_class).split(":")[0] From 5fc037880fa7ddee5c65d63679d1db2810469411 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 19 Jun 2023 17:33:16 +0800 Subject: [PATCH 02/61] refactor the collector and deadline for multiple camera --- openpype/hosts/max/plugins/publish/collect_render.py | 2 +- .../modules/deadline/plugins/publish/submit_max_deadline.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index cbb3a7b4d6..8e39da0fbb 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -49,7 +49,7 @@ class CollectRender(pyblish.api.InstancePlugin): asset_id) self.log.debug("version_doc: {0}".format(version_doc)) sel_obj = [ - c for c in rt.Objects + c.name for c in rt.Objects if rt.classOf(c) in rt.Camera.classes] version_int = 1 diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index ff5adc39ad..365be7b07b 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -216,9 +216,9 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, if render_cameras: for i, camera in enumerate(render_cameras): cam_name = "Camera%s" % (i + 1) - plugin_data[cam_name] = camera.name - # set the default camera - plugin_data["Camera"] = render_cameras[0].name + plugin_data[cam_name] = camera + # set the default camera + plugin_data["Camera"] = camera # set empty camera of Camera 0 for the ' # correct parameter submission plugin_data["Camera0"] = None From 6b716b8ce92d5e0466ecb92c03aad86e920b8217 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 12 Jul 2023 19:16:52 +0800 Subject: [PATCH 03/61] hound fix --- openpype/hosts/max/api/lib_renderproducts.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index a1039b9309..45d7d7134d 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -155,8 +155,8 @@ class RenderProducts(object): for name in render_name: render_dict.update({ name: self.get_expected_render_elements( - output_file, name, start_frame, - end_frame, img_fmt) + output_file, name, start_frame, + end_frame, img_fmt) }) elif renderer == "Redshift_Renderer": render_name = self.get_render_elements_name() @@ -169,15 +169,15 @@ class RenderProducts(object): if name == "RsCryptomatte": render_dict.update({ name: self.get_expected_render_elements( - output_file, name, start_frame, - end_frame, img_fmt) + output_file, name, start_frame, + end_frame, img_fmt) }) else: for name in render_name: render_dict.update({ name: self.get_expected_render_elements( - output_file, name, start_frame, - end_frame, img_fmt) + output_file, name, start_frame, + end_frame, img_fmt) }) elif renderer == "Arnold": @@ -186,7 +186,7 @@ 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", @@ -198,8 +198,8 @@ class RenderProducts(object): for name in render_name: render_dict.update({ name: self.get_expected_render_elements( - output_file, name, start_frame, - end_frame, img_fmt) # noqa + output_file, name, start_frame, + end_frame, img_fmt) # noqa }) return render_dict From e96bada178f11d8f24d1036746a3f3e519e95b84 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 12 Jul 2023 19:23:03 +0800 Subject: [PATCH 04/61] hound fix --- openpype/hosts/max/api/lib_renderproducts.py | 44 ++++++++++--------- openpype/hosts/max/api/lib_rendersettings.py | 2 +- .../hosts/max/plugins/create/create_render.py | 2 +- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 45d7d7134d..433214935d 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -33,6 +33,7 @@ class RenderProducts(object): output_file, start_frame, end_frame, img_fmt ) } + def get_multiple_beauty(self, outputs, cameras): beauty_output_frames = dict() for output, camera in zip(outputs, cameras): @@ -68,9 +69,9 @@ class RenderProducts(object): for name in render_name: aovs_frames.update({ f"{camera}_{name}": ( - self.get_expected_render_elements( - filename, name, start_frame, - end_frame, ext) + self.get_expected_render_elements( + filename, name, start_frame, + end_frame, ext) ) }) elif renderer == "Redshift_Renderer": @@ -84,9 +85,9 @@ class RenderProducts(object): if name == "RsCryptomatte": aovs_frames.update({ f"{camera}_{name}": ( - self.get_expected_render_elements( - filename, name, start_frame, - end_frame, ext) + self.get_expected_render_elements( + filename, name, start_frame, + end_frame, ext) ) }) else: @@ -105,9 +106,9 @@ class RenderProducts(object): for name in render_name: aovs_frames.update({ f"{camera}_{name}": ( - self.get_expected_arnold_product( - filename, name, start_frame, - end_frame, ext) + self.get_expected_arnold_product( + filename, name, start_frame, + end_frame, ext) ) }) elif renderer in [ @@ -120,9 +121,9 @@ class RenderProducts(object): for name in render_name: aovs_frames.update({ f"{camera}_{name}": ( - self.get_expected_render_elements( - filename, name, start_frame, - end_frame, ext) + self.get_expected_render_elements( + filename, name, start_frame, + end_frame, ext) ) }) @@ -155,8 +156,8 @@ class RenderProducts(object): for name in render_name: render_dict.update({ name: self.get_expected_render_elements( - output_file, name, start_frame, - end_frame, img_fmt) + output_file, name, start_frame, + end_frame, img_fmt) }) elif renderer == "Redshift_Renderer": render_name = self.get_render_elements_name() @@ -169,15 +170,15 @@ class RenderProducts(object): if name == "RsCryptomatte": render_dict.update({ name: self.get_expected_render_elements( - output_file, name, start_frame, - end_frame, img_fmt) + output_file, name, start_frame, + end_frame, img_fmt) }) else: for name in render_name: render_dict.update({ name: self.get_expected_render_elements( - output_file, name, start_frame, - end_frame, img_fmt) + output_file, name, start_frame, + end_frame, img_fmt) }) elif renderer == "Arnold": @@ -186,7 +187,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", @@ -198,8 +200,8 @@ class RenderProducts(object): for name in render_name: render_dict.update({ name: self.get_expected_render_elements( - output_file, name, start_frame, - end_frame, img_fmt) # noqa + output_file, name, start_frame, + end_frame, img_fmt) # noqa }) return render_dict diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index 663f610e45..33cfc6dc4a 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -192,7 +192,7 @@ class RenderSettings(object): renderlayer = rt.batchRenderMgr.GetView(layer_no) # use camera name as renderlayer name renderlayer.name = cam - renderlayer.outputFilename ="{0}_{1}..{2}".format( + renderlayer.outputFilename = "{0}_{1}..{2}".format( 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 64e97fb941..23397e1a98 100644 --- a/openpype/hosts/max/plugins/create/create_render.py +++ b/openpype/hosts/max/plugins/create/create_render.py @@ -17,7 +17,7 @@ class CreateRender(plugin.MaxCreator): file = rt.maxFileName filename, _ = os.path.splitext(file) instance_data["AssetName"] = filename - num_of_renderlayer = rt.batchRenderMgr.numViews + num_of_renderlayer = rt.batchRenderMgr.numViews if num_of_renderlayer > 0: rt.batchRenderMgr.DeleteView(num_of_renderlayer) From 9ce0d97e48d4313b3d50d6edab36ab3608a98799 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 12 Jul 2023 19:28:08 +0800 Subject: [PATCH 05/61] hound fix --- openpype/hosts/max/api/lib_renderproducts.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 433214935d..d2d5fb08da 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -77,7 +77,7 @@ class RenderProducts(object): elif renderer == "Redshift_Renderer": render_name = self.get_render_elements_name() if render_name: - rs_aov_files = rt.Execute("renderers.current.separateAovFiles") + 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: @@ -97,9 +97,8 @@ class RenderProducts(object): self.get_expected_render_elements( filename, name, start_frame, end_frame, ext) - ) - }) - + ) + }) elif renderer == "Arnold": render_name = self.get_arnold_product_name() if render_name: From b182f29c90dd8810ab130331ad370e58fc21ab38 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 12 Jul 2023 19:32:25 +0800 Subject: [PATCH 06/61] hound fix --- openpype/hosts/max/api/lib_renderproducts.py | 24 ++++++-------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index d2d5fb08da..03dfcf3345 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -68,11 +68,9 @@ class RenderProducts(object): if render_name: for name in render_name: aovs_frames.update({ - f"{camera}_{name}": ( - self.get_expected_render_elements( + f"{camera}_{name}": self.get_expected_render_elements( filename, name, start_frame, end_frame, ext) - ) }) elif renderer == "Redshift_Renderer": render_name = self.get_render_elements_name() @@ -84,31 +82,25 @@ class RenderProducts(object): for name in render_name: if name == "RsCryptomatte": aovs_frames.update({ - f"{camera}_{name}": ( - self.get_expected_render_elements( + f"{camera}_{name}": self.get_expected_render_elements( filename, name, start_frame, end_frame, ext) - ) }) else: for name in render_name: aovs_frames.update({ - f"{camera}_{name}": ( - self.get_expected_render_elements( + f"{camera}_{name}": self.get_expected_render_elements( 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( + f"{camera}_{name}": self.get_expected_arnold_product( filename, name, start_frame, end_frame, ext) - ) }) elif renderer in [ "V_Ray_6_Hotfix_3", @@ -119,11 +111,9 @@ class RenderProducts(object): if render_name: for name in render_name: aovs_frames.update({ - f"{camera}_{name}": ( - self.get_expected_render_elements( - filename, name, start_frame, - end_frame, ext) - ) + f"{camera}_{name}": self.get_expected_render_elements( + filename, name, start_frame, + end_frame, ext) }) return aovs_frames From 4c01e09ef9d3ae8f4fb67b724e9c227e9afaa7f1 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 12 Jul 2023 19:35:01 +0800 Subject: [PATCH 07/61] hound fix --- openpype/hosts/max/api/lib_renderproducts.py | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 03dfcf3345..59417a39fa 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -68,7 +68,7 @@ class RenderProducts(object): if render_name: for name in render_name: aovs_frames.update({ - f"{camera}_{name}": self.get_expected_render_elements( + f"{camera}_{name}": self.get_expected_aovs( filename, name, start_frame, end_frame, ext) }) @@ -82,14 +82,14 @@ class RenderProducts(object): for name in render_name: if name == "RsCryptomatte": aovs_frames.update({ - f"{camera}_{name}": self.get_expected_render_elements( + 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_render_elements( + f"{camera}_{name}": self.get_expected_aovs( filename, name, start_frame, end_frame, ext) }) @@ -111,9 +111,9 @@ class RenderProducts(object): if render_name: for name in render_name: aovs_frames.update({ - f"{camera}_{name}": self.get_expected_render_elements( - filename, name, start_frame, - end_frame, ext) + f"{camera}_{name}": self.get_expected_aovs( + filename, name, start_frame, + end_frame, ext) }) return aovs_frames @@ -144,7 +144,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) }) @@ -158,14 +158,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) }) @@ -188,7 +188,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 }) @@ -251,8 +251,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): From 9f90e3a1f15bba7e26303d3fafee5ef5d9bd238e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 12 Jul 2023 19:35:57 +0800 Subject: [PATCH 08/61] hound fix --- openpype/hosts/max/api/lib_renderproducts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 59417a39fa..0b8c53dfa0 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -98,7 +98,7 @@ class RenderProducts(object): if render_name: for name in render_name: aovs_frames.update({ - f"{camera}_{name}": self.get_expected_arnold_product( + f"{camera}_{name}": self.get_expected_arnold_product( # noqa filename, name, start_frame, end_frame, ext) }) From c81818d09509b6b85e3259e6bac362717aa1ca1c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 14 Jul 2023 20:25:49 +0800 Subject: [PATCH 09/61] add multi camera options for render creator --- openpype/hosts/max/api/lib_rendersettings.py | 6 +-- .../hosts/max/plugins/create/create_render.py | 19 ++++++++ .../max/plugins/publish/collect_render.py | 46 ++++++++++--------- 3 files changed, 47 insertions(+), 24 deletions(-) diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index 33cfc6dc4a..18160c66a0 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -177,8 +177,8 @@ class RenderSettings(object): render_element_list.append(aov_name) return render_element_list - def create_batch_render_layer(self, container, - output_dir, cameras): + 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 @@ -186,7 +186,7 @@ class RenderSettings(object): camera = rt.getNodeByName(cam) layer_no = rt.batchRenderMgr.FindView(cam) renderlayer = None - if layer_no is None: + if layer_no == 0: renderlayer = rt.batchRenderMgr.CreateView(camera) else: renderlayer = rt.batchRenderMgr.GetView(layer_no) diff --git a/openpype/hosts/max/plugins/create/create_render.py b/openpype/hosts/max/plugins/create/create_render.py index 23397e1a98..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,6 +18,7 @@ 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) @@ -29,3 +31,20 @@ class CreateRender(plugin.MaxCreator): container_name = instance.data.get("instance_node") # 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 736ffa5865..ca2f2f444f 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -26,22 +26,7 @@ class CollectRender(pyblish.api.InstancePlugin): file = rt.maxFileName current_file = os.path.join(folder, file) filepath = current_file.replace("\\", "/") - container_name = instance.data.get("instance_node") context.data['currentFile'] = current_file - cameras = instance.data.get("members") - sel_cam = [ - c.name for c in cameras - if rt.classOf(c) in rt.Camera.classes] - render_dir = os.path.dirname(rt.rendOutputFilename) - outputs = RenderSettings().create_batch_render_layer( - container_name, render_dir, sel_cam - ) - aov_outputs = RenderSettings().get_batch_render_elements( - container_name, render_dir, sel_cam - ) - files_aov = RenderProducts().get_multiple_beauty(outputs, cameras) - aovs = RenderProducts().get_multiple_aovs(outputs, cameras) - files_aov.update(aovs) asset = get_current_asset_name() files_by_aov = RenderProducts().get_beauty(instance.name) @@ -49,11 +34,33 @@ class CollectRender(pyblish.api.InstancePlugin): aovs = RenderProducts().get_aovs(instance.name) files_by_aov.update(aovs) + if instance.data.get("multiCamera"): + cameras = instance.data.get("members") + if not cameras: + raise RuntimeError("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() - instance.data["expectedFiles"].append(files_aov) - instance.data["files"].append(files_aov) + instance.data["expectedFiles"].append(files_by_aov) + instance.data["files"].append(files_by_aov) img_format = RenderProducts().image_format() project_name = context.data["projectName"] @@ -94,13 +101,10 @@ class CollectRender(pyblish.api.InstancePlugin): "renderer": renderer, "source": filepath, "plugin": "3dsmax", - "cameras": sel_cam, "frameStart": int(rt.rendStart), "frameEnd": int(rt.rendEnd), "version": version_int, - "farm": True, - "renderoutput": outputs, - "aovoutput": aov_outputs + "farm": True } instance.data.update(data) From 22524c1d8473226b7687626b30b34ab237708fb5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 17 Jul 2023 17:22:09 +0800 Subject: [PATCH 10/61] add option for multiple job and plugin infos --- openpype/hosts/max/api/lib_rendersettings.py | 21 ++-- .../deadline/abstract_submit_deadline.py | 25 ++++ .../plugins/publish/submit_max_deadline.py | 113 +++++++++++++++--- 3 files changed, 136 insertions(+), 23 deletions(-) diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index 18160c66a0..f2294fbf95 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -160,7 +160,7 @@ class RenderSettings(object): return orig_render_elem def get_batch_render_elements(self, container, - output_dir, cameras): + output_dir, camera): render_element_list = list() output = os.path.join(output_dir, container) render_elem = rt.maxOps.GetCurRenderElementMgr() @@ -168,15 +168,20 @@ class RenderSettings(object): if render_elem_num < 0: return img_fmt = self._project_settings["max"]["RenderSettings"]["image_format"] # noqa - for cam in cameras: - for i in range(render_elem_num): - renderlayer_name = render_elem.GetRenderElement(i) - target, renderpass = str(renderlayer_name).split(":") - aov_name = "{0}_{1}_{2}..{3}".format( - output, cam, renderpass, img_fmt) - render_element_list.append(aov_name) + + for i in range(render_elem_num): + renderlayer_name = render_elem.GetRenderElement(i) + target, renderpass = str(renderlayer_name).split(":") + aov_name = "{0}_{1}_{2}..{3}".format( + 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_layer(self, container, output_dir, cameras): outputs = list() diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index 551a2f7373..de6babf555 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -592,6 +592,31 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin, return file_path + def get_job_info_through_camera(self, camera=None): + """Get the job parameters for deadline submission when + multi-camera is enabled. + Args: + infos(dict): a dictionary with job info. + """ + pass + + def get_plugin_info_through_camera(self, camera=None): + """Get the plugin parameters for deadline submission when + multi-camera is enabled. + Args: + infos(dict): a dictionary with plugin info. + """ + pass + + def _use_published_name_for_multiples(self, data): + """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 + """ + pass + def assemble_payload( self, job_info=None, plugin_info=None, aux_files=None): """Assemble payload data from its various parts. diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index 365be7b07b..6bbc956f55 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -56,7 +56,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") @@ -73,7 +73,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"] @@ -179,9 +178,19 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, } self.log.debug("Submitting 3dsMax render..") - payload = self._use_published_name(payload_data) - job_info, plugin_info = payload - self.submit(self.assemble_payload(job_info, plugin_info)) + #TODO: multiple camera options + if instance.data.get("multiCamera"): + payload = self._use_published_name_for_multiples(payload_data) + job_infos, plugin_infos = payload + for job_info, plugin_info in zip(job_infos, plugin_infos): + self.log.debug(f"job_info: {job_info}") + self.log.debug(f"plugin_info: {plugin_info}") + submission = self.assemble_payload(job_info, plugin_info) + self.submit(submission) + else: + payload = self._use_published_name(payload_data) + job_info, plugin_info = payload + self.submit(self.assemble_payload(job_info, plugin_info)) def _use_published_name(self, data): instance = self._instance @@ -212,16 +221,6 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, plugin_data["RenderOutput"] = beauty_name # as 3dsmax has version with different languages plugin_data["Language"] = "ENU" - render_cameras = instance.data["cameras"] - if render_cameras: - for i, camera in enumerate(render_cameras): - cam_name = "Camera%s" % (i + 1) - plugin_data[cam_name] = camera - # set the default camera - plugin_data["Camera"] = camera - # set empty camera of Camera 0 for the ' - # correct parameter submission - plugin_data["Camera0"] = None renderer_class = get_current_renderer() @@ -250,6 +249,90 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, return job_info, plugin_info + def get_job_info_through_camera(self, camera): + 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): + instance = self._instance + # set the target camera + plugin_info = copy.deepcopy(self.plugin_info) + plugin_data = {} + # set the output filepath with the relative camera + files = instance.data.get("expectedFiles") + if not files: + raise RuntimeError("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): + 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 + plugin_data["Camera"] = camera + + plugin_data["Camera0"] = None + + plugin_info.update(plugin_data) + return plugin_info + + def _use_published_name_for_multiples(self, data): + """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", []) + for cam in cameras: + job_info = self.get_job_info_through_camera(cam) + plugin_info = self.get_plugin_info_through_camera(cam) + 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": From 5ad81ea6bff0544cffdf7abe04a29eb64d8ec58c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 17 Jul 2023 17:44:24 +0800 Subject: [PATCH 11/61] make sure camera parameters are being collected accurately --- .../plugins/publish/submit_max_deadline.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index 6bbc956f55..23a2b4c679 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -131,11 +131,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 @@ -178,7 +178,7 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, } self.log.debug("Submitting 3dsMax render..") - #TODO: multiple camera options + if instance.data.get("multiCamera"): payload = self._use_published_name_for_multiples(payload_data) job_infos, plugin_infos = payload @@ -306,9 +306,10 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, plugin_info["RenderElementOutputFilename%d" % i] = new_elem # noqa if camera: - # set the default 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) From f5c8994fa31039b0549eddaa7bbe5ec8e5c3cee8 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 17 Jul 2023 20:21:34 +0800 Subject: [PATCH 12/61] get the correct naming for all render outputs --- openpype/hosts/max/api/lib_renderproducts.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 5446e4fca3..863dccd99e 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -41,6 +41,8 @@ class RenderProducts(object): 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( @@ -57,6 +59,8 @@ class RenderProducts(object): 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 From 523ad0519dcbe52d4821d0981ddc4cc6962aaf60 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 17 Jul 2023 20:44:29 +0800 Subject: [PATCH 13/61] get all beauty as expected files in a correct manner --- openpype/hosts/max/api/lib_renderproducts.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 863dccd99e..eaf5015ba8 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -48,9 +48,10 @@ class RenderProducts(object): new_beauty = self.get_expected_beauty( filename, start_frame, end_frame, ext ) - beauty_output_frames = ({ + beauty_output = ({ f"{camera}_beauty": new_beauty }) + beauty_output_frames.update(beauty_output) return beauty_output_frames def get_multiple_aovs(self, outputs, cameras): From 3bb7f871d452fdef0e46db3a92518e8dd8d94afe Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 18 Jul 2023 17:57:04 +0800 Subject: [PATCH 14/61] bug fix camera instance doesn't include anything --- .../deadline/plugins/publish/submit_publish_job.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 01a5c55286..1665a05f1e 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -361,7 +361,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, self.log.info("Submitting Deadline job ...") url = "{}/api/jobs".format(self.deadline_url) - response = requests.post(url, json=payload, timeout=10) + response = requests.post(url, json=payload, timeout=10, verify=False) if not response.ok: raise Exception(response.text) @@ -472,6 +472,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, host_name = self.context.data["hostName"] subset = instance_data["subset"] cameras = instance_data.get("cameras", []) + self.log.info(f"camera: {cameras}") instances = [] # go through aovs in expected files for aov, files in exp_files[0].items(): @@ -497,7 +498,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, task[0].upper(), task[1:], subset[0].upper(), subset[1:]) - cam = [c for c in cameras if c in col.head] + cam = [c for c in cameras if c in cols[0].head] + self.log.debug(f"cam: {cam}") if cam: if aov: subset_name = '{}_{}_{}'.format(group_name, cam, aov) @@ -898,6 +900,12 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, for v in values: instance_skeleton_data[v] = instance.data.get(v) + # if there are cameras, get the camera + # ]data for instance_skeleton_data + cameras = instance.data.get("cameras") + if cameras: + instance_skeleton_data["cameras"] = cameras + # look into instance data if representations are not having any # which are having tag `publish_on_farm` and include them for repre in instance.data.get("representations", []): From f349e720b09c78e6541eb7b10d6eb31f9020b425 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 20 Jul 2023 23:45:08 +0800 Subject: [PATCH 15/61] exporting camera scene through instanceplugin by subprocess --- openpype/hosts/max/api/lib_rendersettings.py | 18 +++ .../publish/save_scenes_for_cameras.py | 110 ++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index 0a6f6569bf..2d453b9712 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -182,6 +182,24 @@ class RenderSettings(object): 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 = "{0}_{1}_{2}..{3}".format( + directory, camera, renderpass, ext) + render_elem.SetRenderElementFileName(i, aov_name) + def batch_render_layer(self, container, output_dir, cameras): outputs = 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..d3357ba478 --- /dev/null +++ b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py @@ -0,0 +1,110 @@ +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 import get_max_version +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 before + deadline submission + + """ + + label = "Save Scene files for cameras" + order = pyblish.api.ExtractorOrder - 0.48 + hosts = ["max"] + families = ["maxrender", "workfile"] + + def process(self, instance): + if not instance.data.get("multiCamera"): + self.log.debug("Skipping instance...") + return + current_folder = rt.maxFilePath + current_filename = rt.maxFileName + current_filepath = os.path.join(current_folder, current_filename) + camera_scene_files = [] + repres_list = [] + scripts = [] + filename, ext = os.path.splitext(current_filename) + fmt = RenderProducts().image_format() + cameras = instance.data.get("cameras") + if not cameras: + return + new_folder = "{}_{}".format(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 = "{}_{}{}".format( + 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 +new_filepath = "{new_filepath}" +new_output = "{new_output}" +camera = "{camera}" +rt.rendOutputFilename = new_output +directory = os.path.dirname(new_output) +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 = directory + "_" + camera + "_" + renderpass + "." + "." + ext + render_elem.SetRenderElementFileName(i, aov_name) +rt.saveMaxFile(new_filepath) + """).format(new_filepath=new_filepath, + new_output=new_output, + camera=camera, + ext=fmt) + scripts.append(script) + + max_version = get_max_version() + maxBatch_exe = os.path.join(os.getenv(f"ADSK_3DSMAX_x64_{max_version}"), "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") + log_file =os.path.join(tmp_dir_name, "fatal.log") + 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") + tmp.write("rt.quitMax(quiet=True)"+"\n") + tmp.write("import time"+"\n") + tmp.write("time.sleep(3)") + + try: + current_filepath = current_filepath.replace("\\","/") + log_file = log_file.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 or not") + + 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}") + + if "sceneFiles" not in instance.data: + instance.data["sceneFiles"] = camera_scene_files From f4a864c0d2b0141f5cd81e40edb25b77374b22d7 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 20 Jul 2023 23:50:29 +0800 Subject: [PATCH 16/61] cosmetic fix --- .../plugins/publish/save_scenes_for_cameras.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py index d3357ba478..2abdb5dba1 100644 --- a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py +++ b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py @@ -74,27 +74,27 @@ rt.saveMaxFile(new_filepath) scripts.append(script) max_version = get_max_version() - maxBatch_exe = os.path.join(os.getenv(f"ADSK_3DSMAX_x64_{max_version}"), "3dsmaxbatch") + maxBatch_exe = os.path.join( + os.getenv(f"ADSK_3DSMAX_x64_{max_version}"), "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") - log_file =os.path.join(tmp_dir_name, "fatal.log") + 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") - tmp.write("rt.quitMax(quiet=True)"+"\n") - tmp.write("import time"+"\n") + tmp.write(script + "\n") + tmp.write("rt.quitMax(quiet=True)" + "\n") + tmp.write("import time" + "\n") tmp.write("time.sleep(3)") try: - current_filepath = current_filepath.replace("\\","/") - log_file = log_file.replace("\\", "/") - tmp_script_path = tmp_script_path.replace("\\","/") + current_filepath = current_filepath.replace("\\", "/") + tmp_script_path = tmp_script_path.replace("\\", "/") run_subprocess([maxBatch_exe, tmp_script_path, "-sceneFile", current_filepath]) except RuntimeError: From 7ae8b86c58ab061daa8b9acefab478adc14651fb Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 20 Jul 2023 23:51:21 +0800 Subject: [PATCH 17/61] hound fix --- openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py index 2abdb5dba1..af47959f8f 100644 --- a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py +++ b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py @@ -64,7 +64,7 @@ if render_elem_num > 0: for i in range(render_elem_num): renderlayer_name = render_elem.GetRenderElement(i) target, renderpass = str(renderlayer_name).split(":") - aov_name = directory + "_" + camera + "_" + renderpass + "." + "." + ext + aov_name = directory + "_" + camera + "_" + renderpass + "." + "." + ext # noqa render_elem.SetRenderElementFileName(i, aov_name) rt.saveMaxFile(new_filepath) """).format(new_filepath=new_filepath, From 3377150fe65342a421107fe71d08f72303cd0151 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 21 Jul 2023 16:14:36 +0800 Subject: [PATCH 18/61] clean up the save_scene_camera code --- .../plugins/publish/save_scenes_for_cameras.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py index af47959f8f..c6d264de32 100644 --- a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py +++ b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py @@ -29,7 +29,6 @@ class SaveScenesForCamera(pyblish.api.InstancePlugin): current_filename = rt.maxFileName current_filepath = os.path.join(current_folder, current_filename) camera_scene_files = [] - repres_list = [] scripts = [] filename, ext = os.path.splitext(current_filename) fmt = RenderProducts().image_format() @@ -88,9 +87,6 @@ rt.saveMaxFile(new_filepath) with open(tmp_script_path, "wt") as tmp: for script in scripts: tmp.write(script + "\n") - tmp.write("rt.quitMax(quiet=True)" + "\n") - tmp.write("import time" + "\n") - tmp.write("time.sleep(3)") try: current_filepath = current_filepath.replace("\\", "/") @@ -100,11 +96,23 @@ rt.saveMaxFile(new_filepath) except RuntimeError: self.log.debug("Checking the scene files existing or not") - for camera_scene in camera_scene_files: + for camera_scene, camera in zip(camera_scene_files, cameras): 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}") + instance.context.data["currentFile"] = camera_scene + representation = { + "name": "max", + "ext": "max", + "files": os.path.basename(camera_scene), + "stagingDir": new_folder, + "outputName": camera + } + self.log.debug(f"representation: {representation}") + if instance.data.get("representations") is None: + instance.data["representations"] = [] + instance.data["representations"].append(representation) if "sceneFiles" not in instance.data: instance.data["sceneFiles"] = camera_scene_files From 4144ca8e18fc2c5cf1e3b4971748cffa57ff3a9a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 21 Jul 2023 16:45:48 +0800 Subject: [PATCH 19/61] clean up the save_scene_camera code --- .../publish/save_scenes_for_cameras.py | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py index c6d264de32..2369cf78a3 100644 --- a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py +++ b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py @@ -22,13 +22,11 @@ class SaveScenesForCamera(pyblish.api.InstancePlugin): families = ["maxrender", "workfile"] def process(self, instance): - if not instance.data.get("multiCamera"): - self.log.debug("Skipping instance...") - return current_folder = rt.maxFilePath current_filename = rt.maxFileName current_filepath = os.path.join(current_folder, current_filename) camera_scene_files = [] + repres_list = [] scripts = [] filename, ext = os.path.splitext(current_filename) fmt = RenderProducts().image_format() @@ -94,25 +92,25 @@ rt.saveMaxFile(new_filepath) run_subprocess([maxBatch_exe, tmp_script_path, "-sceneFile", current_filepath]) except RuntimeError: - self.log.debug("Checking the scene files existing or not") + self.log.debug("Checking the scene files existing") for camera_scene, camera in zip(camera_scene_files, cameras): 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}") - instance.context.data["currentFile"] = camera_scene representation = { - "name": "max", - "ext": "max", - "files": os.path.basename(camera_scene), - "stagingDir": new_folder, - "outputName": camera + "name": camera, + "ext": "max", + "files": os.path.basename(camera_scene), + "stagingDir": os.path.dirname(camera_scene), + "outputName": camera } - self.log.debug(f"representation: {representation}") - if instance.data.get("representations") is None: - instance.data["representations"] = [] - instance.data["representations"].append(representation) + repres_list.append(representation) if "sceneFiles" not in instance.data: instance.data["sceneFiles"] = camera_scene_files + + if instance.data.get("representations") is None: + instance.data["representations"] = [] + instance.data["representations"] = (repres_list) From d62b410efbc3776598d7705c0ee223f672e4a994 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 21 Jul 2023 16:57:51 +0800 Subject: [PATCH 20/61] clean up the save_scene_camera code --- openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py index 2369cf78a3..3031c0d4cf 100644 --- a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py +++ b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py @@ -113,4 +113,4 @@ rt.saveMaxFile(new_filepath) if instance.data.get("representations") is None: instance.data["representations"] = [] - instance.data["representations"] = (repres_list) + instance.data["representations"]= repres_list From 855143d217400b813f128b0804e599332dbe95d6 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 21 Jul 2023 17:11:26 +0800 Subject: [PATCH 21/61] clean up --- .../plugins/publish/save_scenes_for_cameras.py | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py index 3031c0d4cf..8aff9b58dd 100644 --- a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py +++ b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py @@ -11,8 +11,8 @@ from openpype.hosts.max.api.lib_renderproducts import RenderProducts class SaveScenesForCamera(pyblish.api.InstancePlugin): - """Save scene files for multiple cameras before - deadline submission + """Save scene files for multiple cameras without + editing the original scene before deadline submission """ @@ -26,7 +26,6 @@ class SaveScenesForCamera(pyblish.api.InstancePlugin): current_filename = rt.maxFileName current_filepath = os.path.join(current_folder, current_filename) camera_scene_files = [] - repres_list = [] scripts = [] filename, ext = os.path.splitext(current_filename) fmt = RenderProducts().image_format() @@ -99,18 +98,6 @@ rt.saveMaxFile(new_filepath) 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}") - representation = { - "name": camera, - "ext": "max", - "files": os.path.basename(camera_scene), - "stagingDir": os.path.dirname(camera_scene), - "outputName": camera - } - repres_list.append(representation) if "sceneFiles" not in instance.data: instance.data["sceneFiles"] = camera_scene_files - - if instance.data.get("representations") is None: - instance.data["representations"] = [] - instance.data["representations"]= repres_list From 7ecb5ba056f2e5adf5f6ebbc6af19db16b207d56 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 21 Jul 2023 17:14:23 +0800 Subject: [PATCH 22/61] hound fix --- openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py index 8aff9b58dd..918b6cda43 100644 --- a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py +++ b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py @@ -93,7 +93,7 @@ rt.saveMaxFile(new_filepath) except RuntimeError: self.log.debug("Checking the scene files existing") - for camera_scene, camera in zip(camera_scene_files, cameras): + 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") From 0a7d24ba5f2f3b8dfaaeb7b0fbf8309c27ce5c38 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 21 Jul 2023 18:27:00 +0800 Subject: [PATCH 23/61] use sys.executable to find the directory of maxBatch.exe --- openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py index 918b6cda43..0ccc69591a 100644 --- a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py +++ b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py @@ -5,7 +5,6 @@ import tempfile from pymxs import runtime as rt from openpype.lib import run_subprocess -from openpype.hosts.max.api.lib import get_max_version from openpype.hosts.max.api.lib_rendersettings import RenderSettings from openpype.hosts.max.api.lib_renderproducts import RenderProducts @@ -69,9 +68,8 @@ rt.saveMaxFile(new_filepath) ext=fmt) scripts.append(script) - max_version = get_max_version() maxBatch_exe = os.path.join( - os.getenv(f"ADSK_3DSMAX_x64_{max_version}"), "3dsmaxbatch") + os.path.dirname(sys.executable), "3dsmaxbatch") maxBatch_exe = maxBatch_exe.replace("\\", "/") if sys.platform == "windows": maxBatch_exe += ".exe" From 7692b95f1c113c8f2da8c0ae335398c0f7740a17 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 21 Jul 2023 19:37:00 +0800 Subject: [PATCH 24/61] change the scene_path command in submit 3dsmax renders and make sure render elements have the correct path --- .../publish/save_scenes_for_cameras.py | 19 +++++++++++-------- .../plugins/publish/submit_max_deadline.py | 5 ++++- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py index 0ccc69591a..9561f53996 100644 --- a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py +++ b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py @@ -18,7 +18,7 @@ class SaveScenesForCamera(pyblish.api.InstancePlugin): label = "Save Scene files for cameras" order = pyblish.api.ExtractorOrder - 0.48 hosts = ["max"] - families = ["maxrender", "workfile"] + families = ["maxrender"] def process(self, instance): current_folder = rt.maxFilePath @@ -47,11 +47,13 @@ class SaveScenesForCamera(pyblish.api.InstancePlugin): 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(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: @@ -62,18 +64,19 @@ if render_elem_num > 0: aov_name = directory + "_" + camera + "_" + renderpass + "." + "." + ext # noqa render_elem.SetRenderElementFileName(i, aov_name) rt.saveMaxFile(new_filepath) - """).format(new_filepath=new_filepath, + """).format(filename=filename, + new_filepath=new_filepath, new_output=new_output, camera=camera, ext=fmt) scripts.append(script) - maxBatch_exe = os.path.join( + maxbatch_exe = os.path.join( os.path.dirname(sys.executable), "3dsmaxbatch") - maxBatch_exe = maxBatch_exe.replace("\\", "/") + maxbatch_exe = maxbatch_exe.replace("\\", "/") if sys.platform == "windows": - maxBatch_exe += ".exe" - maxBatch_exe = os.path.normpath(maxBatch_exe) + 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") @@ -86,7 +89,7 @@ rt.saveMaxFile(new_filepath) try: current_filepath = current_filepath.replace("\\", "/") tmp_script_path = tmp_script_path.replace("\\", "/") - run_subprocess([maxBatch_exe, tmp_script_path, + run_subprocess([maxbatch_exe, tmp_script_path, "-sceneFile", current_filepath]) except RuntimeError: self.log.debug("Checking the scene files existing") diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index 5fed10a7c5..57f4353dcc 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -12,7 +12,6 @@ from openpype.pipeline import ( legacy_io, OpenPypePyblishPluginMixin ) -from openpype.settings import get_project_settings from openpype.hosts.max.api.lib import ( get_current_renderer, get_multipass_setting @@ -272,8 +271,12 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, instance = self._instance # set the target camera plugin_info = copy.deepcopy(self.plugin_info) + plugin_data = {} # set the output filepath with the relative camera + for camera_scene_path in instance.data.get("sceneFiles"): + if camera in camera_scene_path: + plugin_data["SceneFile"] = camera_scene_path files = instance.data.get("expectedFiles") if not files: raise RuntimeError("No render elements found") From b8952629705de15b9fbcc2ca18bba16bd83b7014 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 21 Jul 2023 20:51:16 +0800 Subject: [PATCH 25/61] if multiCamera enabled, the deadline submission wont use the published workfiles to render --- .../modules/deadline/plugins/publish/submit_max_deadline.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index 57f4353dcc..f3d873a1db 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -349,7 +349,11 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, if instance.data["renderer"] == "Redshift_Renderer": self.log.debug("Using Redshift...published scene wont be used..") replace_in_path = False - return replace_in_path + + if instance.data["multiCamera"] == True: + self.log.debug("Using Redshift...published scene wont be used..") + replace_in_path = False + return replace_in_path @staticmethod def _iter_expected_files(exp): From 5893675dc0e96dbcef99c5dc89fabc5b9831a783 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 21 Jul 2023 20:53:35 +0800 Subject: [PATCH 26/61] some cosmetic fix --- .../modules/deadline/plugins/publish/submit_max_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index f3d873a1db..904732c4b4 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -350,7 +350,7 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, self.log.debug("Using Redshift...published scene wont be used..") replace_in_path = False - if instance.data["multiCamera"] == True: + if instance.data.get("multiCamera"): self.log.debug("Using Redshift...published scene wont be used..") replace_in_path = False return replace_in_path From 9801ad52a0e42fbc2a69e0b5c514453d0a6d4954 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 24 Jul 2023 18:30:58 +0800 Subject: [PATCH 27/61] make sure the renderpass subset name is correct when saving scenes for camera in subprocess --- .../max/plugins/publish/save_scenes_for_cameras.py | 2 +- .../deadline/plugins/publish/submit_max_deadline.py | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py index 9561f53996..9382a8f4b3 100644 --- a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py +++ b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py @@ -64,7 +64,7 @@ if render_elem_num > 0: aov_name = directory + "_" + camera + "_" + renderpass + "." + "." + ext # noqa render_elem.SetRenderElementFileName(i, aov_name) rt.saveMaxFile(new_filepath) - """).format(filename=filename, + """).format(filename=instance.name, new_filepath=new_filepath, new_output=new_output, camera=camera, diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index 904732c4b4..822962b23e 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -303,10 +303,11 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, instance.name, old_output_dir, camera ) for i, element in enumerate(render_elem_list): - 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 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 @@ -349,10 +350,6 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, if instance.data["renderer"] == "Redshift_Renderer": self.log.debug("Using Redshift...published scene wont be used..") replace_in_path = False - - if instance.data.get("multiCamera"): - self.log.debug("Using Redshift...published scene wont be used..") - replace_in_path = False return replace_in_path @staticmethod From ed55100dd19d2f99d572a78999278b73a78b1c76 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 31 Jul 2023 17:37:21 +0800 Subject: [PATCH 28/61] ondrej's comment --- .../publish/save_scenes_for_cameras.py | 3 --- .../deadline/abstract_submit_deadline.py | 25 ------------------ .../plugins/publish/submit_max_deadline.py | 26 ++++++++++++++++--- 3 files changed, 22 insertions(+), 32 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py index 9382a8f4b3..79e9088ac7 100644 --- a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py +++ b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py @@ -99,6 +99,3 @@ rt.saveMaxFile(new_filepath) 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}") - - if "sceneFiles" not in instance.data: - instance.data["sceneFiles"] = camera_scene_files diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index ae568c43e3..3fa427204b 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -531,31 +531,6 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin, return replace_with_published_scene_path( self._instance, replace_in_path=replace_in_path) - def get_job_info_through_camera(self, camera=None): - """Get the job parameters for deadline submission when - multi-camera is enabled. - Args: - infos(dict): a dictionary with job info. - """ - pass - - def get_plugin_info_through_camera(self, camera=None): - """Get the plugin parameters for deadline submission when - multi-camera is enabled. - Args: - infos(dict): a dictionary with plugin info. - """ - pass - - def _use_published_name_for_multiples(self, data): - """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 - """ - pass - def assemble_payload( self, job_info=None, plugin_info=None, aux_files=None): """Assemble payload data from its various parts. diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index 822962b23e..78e570a780 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -164,7 +164,7 @@ 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: @@ -184,6 +184,7 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, self.log.debug("Submitting 3dsMax render..") project_settings = instance.context.data["project_settings"] 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 @@ -249,6 +250,11 @@ 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) @@ -268,15 +274,27 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, # 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 - for camera_scene_path in instance.data.get("sceneFiles"): - if camera in camera_scene_path: - plugin_data["SceneFile"] = camera_scene_path + 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 RuntimeError("No render elements found") From 0d9873dd41dc71d603b100469a1faa419688a85c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 31 Jul 2023 22:32:19 +0800 Subject: [PATCH 29/61] ondrej's comment --- .../modules/deadline/plugins/publish/submit_max_deadline.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index 78e570a780..15019c2647 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -12,6 +12,7 @@ from openpype.pipeline import ( legacy_io, OpenPypePyblishPluginMixin ) +from openpype.pipeline.publish import KnownPublishError from openpype.hosts.max.api.lib import ( get_current_renderer, get_multipass_setting @@ -168,7 +169,7 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, 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 From 3e49e6c642b1813d90111bd4007b039f46fded27 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 31 Jul 2023 23:19:31 +0800 Subject: [PATCH 30/61] use KnownPublishError instead of RuntimeError --- .../modules/deadline/plugins/publish/submit_max_deadline.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index 15019c2647..29ce315bdc 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -210,7 +210,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, @@ -298,7 +298,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) rgb_output = RenderSettings().get_batch_render_output(camera) # noqa From 1e19bfa6949c7340a7ea146d87d66d1f2bb80aa4 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 30 Aug 2023 16:42:58 +0800 Subject: [PATCH 31/61] fix the weird naming of publish render folder --- openpype/pipeline/farm/pyblish_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py index fe3ab97de8..61cceb80ad 100644 --- a/openpype/pipeline/farm/pyblish_functions.py +++ b/openpype/pipeline/farm/pyblish_functions.py @@ -583,7 +583,7 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, else: # in case of single frame cam = [c for c in cameras if c in col] - if cam: + if cam or subset != "maxrenderMain": if aov: subset_name = '{}_{}_{}'.format(group_name, cam, aov) else: From 960e7a24bc68cb913ef4a6b6857d01b0006f04cd Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 30 Aug 2023 17:32:31 +0800 Subject: [PATCH 32/61] fixing bigroy's comment on camera subset interruption --- openpype/pipeline/farm/pyblish_functions.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py index 61cceb80ad..fb73e46508 100644 --- a/openpype/pipeline/farm/pyblish_functions.py +++ b/openpype/pipeline/farm/pyblish_functions.py @@ -583,11 +583,15 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, else: # in case of single frame cam = [c for c in cameras if c in col] - if cam or subset != "maxrenderMain": + if cam: if aov: subset_name = '{}_{}_{}'.format(group_name, cam, aov) + if subset == "maxrenderMain": + subset_name = '{}_{}'.format(group_name, aov) else: subset_name = '{}_{}'.format(group_name, cam) + if subset == "maxrenderMain": + subset_name = '{}'.format(group_name) else: if aov: subset_name = '{}_{}'.format(group_name, aov) From 749ca64e58caa9b95df2edc0347c72d44e15c151 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 30 Aug 2023 18:54:14 +0800 Subject: [PATCH 33/61] roy's comment on the subset naming based on cameras in multiple camera rendering --- openpype/pipeline/farm/pyblish_functions.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py index fb73e46508..0c5d6a2712 100644 --- a/openpype/pipeline/farm/pyblish_functions.py +++ b/openpype/pipeline/farm/pyblish_functions.py @@ -579,18 +579,24 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, # 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]] + cam = next((c for c in cameras if c in col[0]), None) else: # in case of single frame - cam = [c for c in cameras if c in col] + cam = next((cam for cam in cameras if cam in col), None) if cam: if aov: subset_name = '{}_{}_{}'.format(group_name, cam, aov) if subset == "maxrenderMain": + # Max submit scenes by cameras for multiple camera + # submission, it results to include the camera name inside + # the original subset and i.e group_name subset_name = '{}_{}'.format(group_name, aov) else: subset_name = '{}_{}'.format(group_name, cam) if subset == "maxrenderMain": + # Max submit scenes by cameras for multiple camera + # submission, it results to include the camera name inside + # the original subset and i.e group_name subset_name = '{}'.format(group_name) else: if aov: From 1f2e32311f71448d2cc16348871d36ebf0093f04 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 30 Aug 2023 20:34:44 +0800 Subject: [PATCH 34/61] remove camera_name in aov if there is one --- openpype/pipeline/farm/pyblish_functions.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py index 0c5d6a2712..58ffeb937f 100644 --- a/openpype/pipeline/farm/pyblish_functions.py +++ b/openpype/pipeline/farm/pyblish_functions.py @@ -585,19 +585,13 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, cam = next((cam for cam in cameras if cam in col), None) if cam: if aov: + # if there is duplicatd camera name found in aov, + # it would be removed + if aov.startswith(cam): + aov = aov.replace(f"{cam}_", "") subset_name = '{}_{}_{}'.format(group_name, cam, aov) - if subset == "maxrenderMain": - # Max submit scenes by cameras for multiple camera - # submission, it results to include the camera name inside - # the original subset and i.e group_name - subset_name = '{}_{}'.format(group_name, aov) else: subset_name = '{}_{}'.format(group_name, cam) - if subset == "maxrenderMain": - # Max submit scenes by cameras for multiple camera - # submission, it results to include the camera name inside - # the original subset and i.e group_name - subset_name = '{}'.format(group_name) else: if aov: subset_name = '{}_{}'.format(group_name, aov) From 30ea0213b72237cdc9bd2920c691fb22cf184338 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 5 Sep 2023 18:40:07 +0800 Subject: [PATCH 35/61] add pattern for clique.assemble function to just iterate through frame ranges --- openpype/pipeline/farm/pyblish_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py index 58ffeb937f..3f19c3cc95 100644 --- a/openpype/pipeline/farm/pyblish_functions.py +++ b/openpype/pipeline/farm/pyblish_functions.py @@ -548,7 +548,7 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, instances = [] # go through AOVs in expected files for aov, files in exp_files[0].items(): - cols, rem = clique.assemble(files) + cols, rem = clique.assemble(files, patterns=[clique.PATTERNS['frames']]) # we shouldn't have any reminders. And if we do, it should # be just one item for single frame renders. if not cols and rem: From 44391391df926316b8c7585e526ebbaffc5ab3d6 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 5 Sep 2023 18:41:00 +0800 Subject: [PATCH 36/61] hound --- openpype/pipeline/farm/pyblish_functions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py index 3f19c3cc95..5713c13c4e 100644 --- a/openpype/pipeline/farm/pyblish_functions.py +++ b/openpype/pipeline/farm/pyblish_functions.py @@ -548,7 +548,8 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, instances = [] # go through AOVs in expected files for aov, files in exp_files[0].items(): - cols, rem = clique.assemble(files, patterns=[clique.PATTERNS['frames']]) + cols, rem = clique.assemble( + files, patterns=[clique.PATTERNS['frames']]) # we shouldn't have any reminders. And if we do, it should # be just one item for single frame renders. if not cols and rem: From 60e5eb74aeee01ad6a621554eac950a5852cdb90 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 5 Sep 2023 18:43:30 +0800 Subject: [PATCH 37/61] big roy's comments on the line 582-583 --- openpype/pipeline/farm/pyblish_functions.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py index 5713c13c4e..0bdc2b1339 100644 --- a/openpype/pipeline/farm/pyblish_functions.py +++ b/openpype/pipeline/farm/pyblish_functions.py @@ -579,11 +579,8 @@ 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 = next((c for c in cameras if c in col[0]), None) - else: - # in case of single frame - cam = next((cam for cam in cameras if cam in col), None) + expected_filepath = col[0] if isinstance(col, (list, tuple)) else col + cam = next((cam for cam in cameras if cam in expected_filepath), None) if cam: if aov: # if there is duplicatd camera name found in aov, From 75d17fcdffb6e10b049110be6defa023b6a5d68e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 5 Sep 2023 18:56:45 +0800 Subject: [PATCH 38/61] restore the pattern for not breaking the submit publish job --- openpype/pipeline/farm/pyblish_functions.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py index 0bdc2b1339..181aad6cb5 100644 --- a/openpype/pipeline/farm/pyblish_functions.py +++ b/openpype/pipeline/farm/pyblish_functions.py @@ -548,8 +548,7 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, instances = [] # go through AOVs in expected files for aov, files in exp_files[0].items(): - cols, rem = clique.assemble( - files, patterns=[clique.PATTERNS['frames']]) + cols, rem = clique.assemble(files) # we shouldn't have any reminders. And if we do, it should # be just one item for single frame renders. if not cols and rem: From dadc6fa85747cb4790b226e2279fdacd1744064c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 26 Sep 2023 15:12:21 +0800 Subject: [PATCH 39/61] make sure render outputs with all cameras should be in the render publish folder --- openpype/pipeline/farm/pyblish_functions.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py index 181aad6cb5..5019d01be3 100644 --- a/openpype/pipeline/farm/pyblish_functions.py +++ b/openpype/pipeline/farm/pyblish_functions.py @@ -582,11 +582,13 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, cam = next((cam for cam in cameras if cam in expected_filepath), None) if cam: if aov: - # if there is duplicatd camera name found in aov, - # it would be removed - if aov.startswith(cam): - aov = aov.replace(f"{cam}_", "") - subset_name = '{}_{}_{}'.format(group_name, cam, aov) + # Multiple cameras publishing in some hosts such as 3dsMax + # have aov data set to "Camera001_beauty" to differentiate + # the render output files + 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: @@ -594,7 +596,6 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, subset_name = '{}_{}'.format(group_name, aov) else: subset_name = '{}'.format(group_name) - if isinstance(col, (list, tuple)): staging = os.path.dirname(col[0]) else: From 55555e3078e3289e663f13709334e0ed4e038e72 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 27 Sep 2023 22:00:38 +0800 Subject: [PATCH 40/61] iterate the camera list --- openpype/pipeline/farm/pyblish_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py index 5019d01be3..1a5cbbf3e2 100644 --- a/openpype/pipeline/farm/pyblish_functions.py +++ b/openpype/pipeline/farm/pyblish_functions.py @@ -579,7 +579,7 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, # if there are multiple cameras, we need to add camera name expected_filepath = col[0] if isinstance(col, (list, tuple)) else col - cam = next((cam for cam in cameras if cam in expected_filepath), None) + cam = next(iter(cam for cam in cameras if cam in expected_filepath), None) if cam: if aov: # Multiple cameras publishing in some hosts such as 3dsMax From 810b3259d1e7441bda9a147bdf4942a6a9fd101c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 27 Sep 2023 22:29:41 +0800 Subject: [PATCH 41/61] hound --- openpype/pipeline/farm/pyblish_functions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py index 1a5cbbf3e2..e5530481d2 100644 --- a/openpype/pipeline/farm/pyblish_functions.py +++ b/openpype/pipeline/farm/pyblish_functions.py @@ -579,7 +579,8 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, # if there are multiple cameras, we need to add camera name expected_filepath = col[0] if isinstance(col, (list, tuple)) else col - cam = next(iter(cam for cam in cameras if cam in expected_filepath), None) + cam = next( + iter(cam for cam in cameras if cam in expected_filepath), None) if cam: if aov: # Multiple cameras publishing in some hosts such as 3dsMax From 824912dba1e1d6d793d872d93188312bb6e43309 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 28 Sep 2023 19:08:09 +0800 Subject: [PATCH 42/61] update camera subset name condition --- openpype/pipeline/farm/pyblish_functions.py | 22 ++++++++++----------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py index e5530481d2..f0d330da71 100644 --- a/openpype/pipeline/farm/pyblish_functions.py +++ b/openpype/pipeline/farm/pyblish_functions.py @@ -579,24 +579,22 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, # if there are multiple cameras, we need to add camera name expected_filepath = col[0] if isinstance(col, (list, tuple)) else col - cam = next( - iter(cam for cam in cameras if cam in expected_filepath), None) - if cam: - if aov: - # Multiple cameras publishing in some hosts such as 3dsMax - # have aov data set to "Camera001_beauty" to differentiate - # the render output files - if not aov.startswith(cam): - subset_name = '{}_{}_{}'.format(group_name, cam, aov) + 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, aov) - else: - subset_name = '{}_{}'.format(group_name, cam) + subset_name = '{}_{}'.format(group_name, cam) else: if aov: subset_name = '{}_{}'.format(group_name, aov) else: subset_name = '{}'.format(group_name) + if isinstance(col, (list, tuple)): staging = os.path.dirname(col[0]) else: From 5b2e5faccdbd0e425e0d9f438f0e82f96fe15c24 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 19 Oct 2023 18:30:03 +0800 Subject: [PATCH 43/61] remove if condition of cameras --- openpype/pipeline/farm/pyblish_functions.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py index f0d330da71..8ae46ae1a1 100644 --- a/openpype/pipeline/farm/pyblish_functions.py +++ b/openpype/pipeline/farm/pyblish_functions.py @@ -580,20 +580,17 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, # if there are multiple cameras, we need to add camera name 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: + for cam in cams: if aov: - subset_name = '{}_{}'.format(group_name, aov) + if aov.startswith(cam): + subset_name = '{}_{}_{}'.format(group_name, cam, aov) + else: + subset_name = "{}_{}".format(group_name, aov) else: - subset_name = '{}'.format(group_name) + if aov.startswith(cam): + subset_name = '{}_{}'.format(group_name, cam) + else: + subset_name = '{}'.format(group_name) if isinstance(col, (list, tuple)): staging = os.path.dirname(col[0]) From 5bb6f304d381938d1effa7cc089971ab85f46352 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 19 Oct 2023 19:26:42 +0800 Subject: [PATCH 44/61] use known publish error instead of runtime error --- openpype/hosts/max/plugins/publish/collect_render.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index 78ab02ef5e..01d7cff32d 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -5,6 +5,7 @@ import pyblish.api from pymxs import runtime as rt from openpype.pipeline import get_current_asset_name +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 @@ -45,8 +46,8 @@ class CollectRender(pyblish.api.InstancePlugin): if instance.data.get("multiCamera"): cameras = instance.data.get("members") if not cameras: - raise RuntimeError("There should be at least" - " one renderable camera in container") + 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] From e3e8770ffefb25a2b89388f70f51d385a10b0cb5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 19 Oct 2023 20:31:06 +0800 Subject: [PATCH 45/61] restore the function code for test --- openpype/pipeline/farm/pyblish_functions.py | 25 +++++++++++---------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py index 3254fbdfda..c9e013a09a 100644 --- a/openpype/pipeline/farm/pyblish_functions.py +++ b/openpype/pipeline/farm/pyblish_functions.py @@ -578,20 +578,21 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, else: group_name = subset - # if there are multiple cameras, we need to add camera name - expected_filepath = col[0] if isinstance(col, (list, tuple)) else col - cams = [cam for cam in cameras if cam in expected_filepath] - for cam in cams: + 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: - if aov.startswith(cam): - subset_name = '{}_{}_{}'.format(group_name, cam, aov) - else: - subset_name = "{}_{}".format(group_name, aov) + subset_name = '{}_{}_{}'.format(group_name, cam, aov) else: - if aov.startswith(cam): - subset_name = '{}_{}'.format(group_name, cam) - else: - subset_name = '{}'.format(group_name) + subset_name = '{}_{}'.format(group_name, cam) + else: + if aov: + subset_name = '{}_{}'.format(group_name, aov) + else: + subset_name = '{}'.format(group_name) if isinstance(col, (list, tuple)): staging = os.path.dirname(col[0]) From e9005c66184c5b6145a342e5258256c1929b111a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 31 Oct 2023 12:59:24 +0800 Subject: [PATCH 46/61] make sure subset name conditions are applicable for other hosts such as maya --- openpype/pipeline/farm/pyblish_functions.py | 22 +++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py index c9e013a09a..6265449515 100644 --- a/openpype/pipeline/farm/pyblish_functions.py +++ b/openpype/pipeline/farm/pyblish_functions.py @@ -578,16 +578,18 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, else: group_name = subset - 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) + # if there are multiple cameras, we need to add camera name + 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) From 333433057ea2728f1646a20ee602b72c40e5c1ba Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 3 Jan 2024 21:48:38 +0800 Subject: [PATCH 47/61] cosmetic tweaks regarding to Ondrej's comment --- openpype/hosts/max/api/lib_rendersettings.py | 15 ++++++--------- .../plugins/publish/save_scenes_for_cameras.py | 7 +++---- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index 166076f008..be50e296eb 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -80,7 +80,7 @@ class RenderSettings(object): )] 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): @@ -181,8 +181,7 @@ 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}..{3}".format( - output, camera, renderpass, img_fmt) + aov_name = f"{output}_{camera}_{renderpass}..{img_fmt}" render_element_list.append(aov_name) return render_element_list @@ -205,8 +204,7 @@ 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}..{3}".format( - directory, camera, renderpass, ext) + aov_name = f"{directory}_{camera}_{renderpass}..{ext}" render_elem.SetRenderElementFileName(i, aov_name) def batch_render_layer(self, container, @@ -224,7 +222,6 @@ class RenderSettings(object): renderlayer = rt.batchRenderMgr.GetView(layer_no) # use camera name as renderlayer name renderlayer.name = cam - renderlayer.outputFilename = "{0}_{1}..{2}".format( - output, cam, img_fmt) + renderlayer.outputFilename = f"{output}_{cam}..{img_fmt}" outputs.append(renderlayer.outputFilename) return outputs diff --git a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py index 79e9088ac7..c39109417b 100644 --- a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py +++ b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py @@ -31,13 +31,12 @@ class SaveScenesForCamera(pyblish.api.InstancePlugin): cameras = instance.data.get("cameras") if not cameras: return - new_folder = "{}_{}".format(current_folder, filename) + 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 = "{}_{}{}".format( - filename, camera, ext) + 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) @@ -61,7 +60,7 @@ if render_elem_num > 0: for i in range(render_elem_num): renderlayer_name = render_elem.GetRenderElement(i) target, renderpass = str(renderlayer_name).split(":") - aov_name = directory + "_" + camera + "_" + renderpass + "." + "." + ext # noqa + aov_name = f"{{directory}}_{camera}_{{renderpass}}..{ext}" render_elem.SetRenderElementFileName(i, aov_name) rt.saveMaxFile(new_filepath) """).format(filename=instance.name, From df7b8683716ff5bb2ab6b648ab22a62e2a555fd3 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 12 Jan 2024 18:32:42 +0800 Subject: [PATCH 48/61] bugfix the thumbnail error when publishing with emissive map & maps without RGB channel --- openpype/hosts/substancepainter/api/lib.py | 50 +++++++++++++++++++ .../publish/collect_textureset_images.py | 20 +++++--- 2 files changed, 62 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/substancepainter/api/lib.py b/openpype/hosts/substancepainter/api/lib.py index 1cb480b552..f46426388b 100644 --- a/openpype/hosts/substancepainter/api/lib.py +++ b/openpype/hosts/substancepainter/api/lib.py @@ -643,3 +643,53 @@ def prompt_new_file_with_mesh(mesh_filepath): return return project_mesh + + +def has_rgb_channel_in_texture_set(texture_set_name, map_identifier): + """Function to check whether the texture has RGB channel. + + Args: + texture_set_name (str): Name of Texture Set + map_identifier (str): Map identifier + + Returns: + colorspace_dict: A dictionary which stores the boolean + value of textures having RGB channels + """ + texture_stack = substance_painter.textureset.Stack.from_name(texture_set_name) + # 2D_View is always True as it exports all texture maps + colorspace_dict = {"2D_View": True} + colorspace_dict["BaseColor"] = texture_stack.get_channel( + substance_painter.textureset.ChannelType.BaseColor).is_color() + colorspace_dict["Roughness"] = texture_stack.get_channel( + substance_painter.textureset.ChannelType.Roughness).is_color() + colorspace_dict["Metallic"] = texture_stack.get_channel( + substance_painter.textureset.ChannelType.Metallic).is_color() + colorspace_dict["Height"] = texture_stack.get_channel( + substance_painter.textureset.ChannelType.Height).is_color() + colorspace_dict["Normal"] = texture_stack.get_channel( + substance_painter.textureset.ChannelType.Normal).is_color() + return colorspace_dict.get(map_identifier, False) + + +def texture_set_filtering(texture_set_same, template): + """Function to check whether some specific textures(e.g. Emissive) + are parts of the texture stack in Substance Painter + + Args: + texture_set_same (str): Name of Texture Set + template (str): texture template name + + Returns: + texture_filter: A dictionary which stores the boolean + value of whether the texture exist in the channel. + """ + texture_filter = {} + channel_stack = substance_painter.textureset.Stack.from_name( + texture_set_same) + has_emissive = channel_stack.has_channel( + substance_painter.textureset.ChannelType.Emissive) + map_identifier = strip_template(template) + if map_identifier == "Emissive": + texture_filter[map_identifier] = has_emissive + return texture_filter.get(map_identifier, True) diff --git a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py index 316f72509e..4c3398d5b4 100644 --- a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py +++ b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py @@ -7,7 +7,9 @@ from openpype.pipeline import publish import substance_painter.textureset from openpype.hosts.substancepainter.api.lib import ( get_parsed_export_maps, - strip_template + strip_template, + has_rgb_channel_in_texture_set, + texture_set_filtering ) from openpype.pipeline.create import get_subset_name from openpype.client import get_asset_by_name @@ -39,11 +41,12 @@ class CollectTextureSet(pyblish.api.InstancePlugin): for (texture_set_name, stack_name), template_maps in maps.items(): self.log.info(f"Processing {texture_set_name}/{stack_name}") for template, outputs in template_maps.items(): - self.log.info(f"Processing {template}") - self.create_image_instance(instance, template, outputs, - asset_doc=asset_doc, - texture_set_name=texture_set_name, - stack_name=stack_name) + if texture_set_filtering(texture_set_name, template): + self.log.info(f"Processing {template}") + self.create_image_instance(instance, template, outputs, + asset_doc=asset_doc, + texture_set_name=texture_set_name, + stack_name=stack_name) def create_image_instance(self, instance, template, outputs, asset_doc, texture_set_name, stack_name): @@ -78,7 +81,6 @@ class CollectTextureSet(pyblish.api.InstancePlugin): # Always include the map identifier map_identifier = strip_template(template) suffix += f".{map_identifier}" - image_subset = get_subset_name( # TODO: The family actually isn't 'texture' currently but for now # this is only done so the subset name starts with 'texture' @@ -132,7 +134,9 @@ class CollectTextureSet(pyblish.api.InstancePlugin): # Store color space with the instance # Note: The extractor will assign it to the representation colorspace = outputs[0].get("colorSpace") - if colorspace: + has_rgb_channel = has_rgb_channel_in_texture_set( + texture_set_name, map_identifier) + if colorspace and has_rgb_channel: self.log.debug(f"{image_subset} colorspace: {colorspace}") image_instance.data["colorspace"] = colorspace From 72848657af4e52aa6d037fbf1f35c2b4548efeee Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 12 Jan 2024 18:44:18 +0800 Subject: [PATCH 49/61] hound shut --- openpype/hosts/substancepainter/api/lib.py | 6 ++++-- .../plugins/publish/collect_textureset_images.py | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/substancepainter/api/lib.py b/openpype/hosts/substancepainter/api/lib.py index f46426388b..67229a75bf 100644 --- a/openpype/hosts/substancepainter/api/lib.py +++ b/openpype/hosts/substancepainter/api/lib.py @@ -656,7 +656,9 @@ def has_rgb_channel_in_texture_set(texture_set_name, map_identifier): colorspace_dict: A dictionary which stores the boolean value of textures having RGB channels """ - texture_stack = substance_painter.textureset.Stack.from_name(texture_set_name) + texture_stack = ( + substance_painter.textureset.Stack.from_name(texture_set_name) + ) # 2D_View is always True as it exports all texture maps colorspace_dict = {"2D_View": True} colorspace_dict["BaseColor"] = texture_stack.get_channel( @@ -686,7 +688,7 @@ def texture_set_filtering(texture_set_same, template): """ texture_filter = {} channel_stack = substance_painter.textureset.Stack.from_name( - texture_set_same) + texture_set_same) has_emissive = channel_stack.has_channel( substance_painter.textureset.ChannelType.Emissive) map_identifier = strip_template(template) diff --git a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py index 4c3398d5b4..f535bfa2a6 100644 --- a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py +++ b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py @@ -44,9 +44,9 @@ class CollectTextureSet(pyblish.api.InstancePlugin): if texture_set_filtering(texture_set_name, template): self.log.info(f"Processing {template}") self.create_image_instance(instance, template, outputs, - asset_doc=asset_doc, - texture_set_name=texture_set_name, - stack_name=stack_name) + asset_doc=asset_doc, + texture_set_name=texture_set_name, + stack_name=stack_name) def create_image_instance(self, instance, template, outputs, asset_doc, texture_set_name, stack_name): From 6410f381f34625defa91250a8e529e47cb40a8a6 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 12 Jan 2024 18:45:40 +0800 Subject: [PATCH 50/61] hound shut --- .../plugins/publish/collect_textureset_images.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py index f535bfa2a6..9d6aa06872 100644 --- a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py +++ b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py @@ -43,10 +43,11 @@ class CollectTextureSet(pyblish.api.InstancePlugin): for template, outputs in template_maps.items(): if texture_set_filtering(texture_set_name, template): self.log.info(f"Processing {template}") - self.create_image_instance(instance, template, outputs, - asset_doc=asset_doc, - texture_set_name=texture_set_name, - stack_name=stack_name) + self.create_image_instance( + instance, template, outputs, + asset_doc=asset_doc, + texture_set_name=texture_set_name, + stack_name=stack_name) def create_image_instance(self, instance, template, outputs, asset_doc, texture_set_name, stack_name): From 00eb748b4b64339b00799b5fa4c41ad42426f73c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Sat, 13 Jan 2024 00:15:46 +0800 Subject: [PATCH 51/61] code tweaks on has_rgb_channel_in_texture_set function & add publish data into the imageinstance --- openpype/hosts/substancepainter/api/lib.py | 48 +++++-------------- .../publish/collect_textureset_images.py | 12 ++--- .../plugins/publish/validate_ouput_maps.py | 1 + 3 files changed, 19 insertions(+), 42 deletions(-) diff --git a/openpype/hosts/substancepainter/api/lib.py b/openpype/hosts/substancepainter/api/lib.py index 67229a75bf..896cca79b0 100644 --- a/openpype/hosts/substancepainter/api/lib.py +++ b/openpype/hosts/substancepainter/api/lib.py @@ -653,45 +653,23 @@ def has_rgb_channel_in_texture_set(texture_set_name, map_identifier): map_identifier (str): Map identifier Returns: - colorspace_dict: A dictionary which stores the boolean - value of textures having RGB channels + bool: Whether the channel type identifier has RGB channel or not + in the texture stack. """ + + # 2D_View is always True as it exports all texture maps texture_stack = ( substance_painter.textureset.Stack.from_name(texture_set_name) ) # 2D_View is always True as it exports all texture maps - colorspace_dict = {"2D_View": True} - colorspace_dict["BaseColor"] = texture_stack.get_channel( - substance_painter.textureset.ChannelType.BaseColor).is_color() - colorspace_dict["Roughness"] = texture_stack.get_channel( - substance_painter.textureset.ChannelType.Roughness).is_color() - colorspace_dict["Metallic"] = texture_stack.get_channel( - substance_painter.textureset.ChannelType.Metallic).is_color() - colorspace_dict["Height"] = texture_stack.get_channel( - substance_painter.textureset.ChannelType.Height).is_color() - colorspace_dict["Normal"] = texture_stack.get_channel( - substance_painter.textureset.ChannelType.Normal).is_color() - return colorspace_dict.get(map_identifier, False) + if map_identifier == "2D_View": + return True + channel_type = getattr( + substance_painter.textureset.ChannelType, map_identifier, None) + if channel_type is None: + return False + if not texture_stack.has_channel(channel_type): + return False -def texture_set_filtering(texture_set_same, template): - """Function to check whether some specific textures(e.g. Emissive) - are parts of the texture stack in Substance Painter - - Args: - texture_set_same (str): Name of Texture Set - template (str): texture template name - - Returns: - texture_filter: A dictionary which stores the boolean - value of whether the texture exist in the channel. - """ - texture_filter = {} - channel_stack = substance_painter.textureset.Stack.from_name( - texture_set_same) - has_emissive = channel_stack.has_channel( - substance_painter.textureset.ChannelType.Emissive) - map_identifier = strip_template(template) - if map_identifier == "Emissive": - texture_filter[map_identifier] = has_emissive - return texture_filter.get(map_identifier, True) + return texture_stack.get_channel(channel_type).is_color() diff --git a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py index 9d6aa06872..4468602392 100644 --- a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py +++ b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py @@ -41,13 +41,11 @@ class CollectTextureSet(pyblish.api.InstancePlugin): for (texture_set_name, stack_name), template_maps in maps.items(): self.log.info(f"Processing {texture_set_name}/{stack_name}") for template, outputs in template_maps.items(): - if texture_set_filtering(texture_set_name, template): - self.log.info(f"Processing {template}") - self.create_image_instance( - instance, template, outputs, - asset_doc=asset_doc, - texture_set_name=texture_set_name, - stack_name=stack_name) + self.log.info(f"Processing {template}") + self.create_image_instance(instance, template, outputs, + asset_doc=asset_doc, + texture_set_name=texture_set_name, + stack_name=stack_name) def create_image_instance(self, instance, template, outputs, asset_doc, texture_set_name, stack_name): diff --git a/openpype/hosts/substancepainter/plugins/publish/validate_ouput_maps.py b/openpype/hosts/substancepainter/plugins/publish/validate_ouput_maps.py index b57cf4c5a2..252683b6c8 100644 --- a/openpype/hosts/substancepainter/plugins/publish/validate_ouput_maps.py +++ b/openpype/hosts/substancepainter/plugins/publish/validate_ouput_maps.py @@ -80,6 +80,7 @@ class ValidateOutputMaps(pyblish.api.InstancePlugin): self.log.warning(f"Disabling texture instance: " f"{image_instance}") image_instance.data["active"] = False + image_instance.data["publish"] = False image_instance.data["integrate"] = False representation.setdefault("tags", []).append("delete") continue From 00ab2bc9f69916ab19ddd15d018dc38cba624991 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Sat, 13 Jan 2024 00:19:03 +0800 Subject: [PATCH 52/61] remove unused function --- .../plugins/publish/collect_textureset_images.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py index 4468602392..370e72f34b 100644 --- a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py +++ b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py @@ -8,8 +8,7 @@ import substance_painter.textureset from openpype.hosts.substancepainter.api.lib import ( get_parsed_export_maps, strip_template, - has_rgb_channel_in_texture_set, - texture_set_filtering + has_rgb_channel_in_texture_set ) from openpype.pipeline.create import get_subset_name from openpype.client import get_asset_by_name From 95d2d45e0ffe10186714bf8136f6ac085ef6c202 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 12 Jan 2024 17:07:28 +0000 Subject: [PATCH 53/61] Fix reading image sequences through oiiotool --- openpype/lib/transcoding.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 316dedbd3d..79d24e737a 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -120,6 +120,10 @@ def get_oiio_info_for_input(filepath, logger=None, subimages=False): output = [] for subimage_lines in subimages_lines: + # First line of oiiotool for xml format is "Reading path/to/file.ext". + if not subimage_lines[0].startswith(" Date: Sat, 13 Jan 2024 03:26:00 +0000 Subject: [PATCH 54/61] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 3f762bd2d8..e9b68a54f1 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.18.4-nightly.1 - 3.18.3 - 3.18.3-nightly.2 - 3.18.3-nightly.1 @@ -134,7 +135,6 @@ body: - 3.15.7-nightly.2 - 3.15.7-nightly.1 - 3.15.6 - - 3.15.6-nightly.3 validations: required: true - type: dropdown From 7d94fb92c23d7ef055329f71d0333ab490a0055c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 15 Jan 2024 10:32:39 +0100 Subject: [PATCH 55/61] Fusion: new creator for image product type (#6057) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Introduced image product type 'image' product type should result in single frame output, 'render' should be more focused on multiple frames. * Updated logging * Refactor moved generic creaor class to better location * Update openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json Co-authored-by: Jakub Ježek * Change label It might be movie type not only image sequence. * OP-7470 - fix name * OP-7470 - update docstring There were objections for setting up this creator as it seems unnecessary. There is currently no other way how to implement customer requirement but this, but in the future 'alias' product types implementation might solve this. * Implementing changes from #6060 https://github.com/ynput/OpenPype/pull/6060 * Update openpype/settings/defaults/project_settings/fusion.json Co-authored-by: Jakub Ježek * Update server_addon/fusion/server/settings.py Co-authored-by: Jakub Ježek * Update openpype/hosts/fusion/api/plugin.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> * OP-7470 - added explicit frame field Artist can insert specific frame from which `image` instance should be created. * OP-7470 - fix name and logging Prints better message even in debug mode. * OP-7470 - update instance label It contained original frames which was confusing. * Update openpype/hosts/fusion/plugins/create/create_image_saver.py Co-authored-by: Roy Nieterau * OP-7470 - fix documentation * OP-7470 - moved frame range resolution earlier This approach is safer, as frame range is resolved sooner. * OP-7470 - added new validator for single frame * OP-7470 - Hound * OP-7470 - removed unnecessary as label * OP-7470 - use internal class anatomy * OP-7470 - add explicit settings_category to propagete values from Setting correctly apply_settings is replaced by correct value in `settings_category` * OP-7470 - typo * OP-7470 - update docstring * OP-7470 - update formatting data This probably fixes issue with missing product key in intermediate product name. * OP-7470 - moved around only proper fields Some fields (frame and frame_range) are making sense only in specific creator. * OP-7470 - added defaults to Settings * OP-7470 - fixed typo * OP-7470 - bumped up version Settings changed, so addon version should change too. 0.1.2 is in develop * Update openpype/hosts/fusion/plugins/publish/collect_instances.py Co-authored-by: Roy Nieterau * OP-7470 - removed unnecessary variables There was logic intended to use those, deemed not necessary. * OP-7470 - update to error message * OP-7470 - removed unneded method --------- Co-authored-by: Jakub Ježek Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Co-authored-by: Roy Nieterau --- openpype/hosts/fusion/api/plugin.py | 221 +++++++++++++++ .../plugins/create/create_image_saver.py | 64 +++++ .../fusion/plugins/create/create_saver.py | 257 ++---------------- .../fusion/plugins/publish/collect_inputs.py | 2 +- .../plugins/publish/collect_instances.py | 12 + .../fusion/plugins/publish/collect_render.py | 4 +- .../fusion/plugins/publish/save_scene.py | 2 +- .../publish/validate_background_depth.py | 2 +- .../plugins/publish/validate_comp_saved.py | 2 +- .../publish/validate_create_folder_checked.py | 2 +- .../validate_filename_has_extension.py | 2 +- .../plugins/publish/validate_image_frame.py | 27 ++ .../publish/validate_instance_frame_range.py | 4 +- .../publish/validate_saver_has_input.py | 2 +- .../publish/validate_saver_passthrough.py | 2 +- .../publish/validate_saver_resolution.py | 2 +- .../publish/validate_unique_subsets.py | 2 +- .../defaults/project_settings/fusion.json | 12 + .../schema_project_fusion.json | 51 +++- server_addon/fusion/server/settings.py | 67 ++++- server_addon/fusion/server/version.py | 2 +- 21 files changed, 482 insertions(+), 259 deletions(-) create mode 100644 openpype/hosts/fusion/api/plugin.py create mode 100644 openpype/hosts/fusion/plugins/create/create_image_saver.py create mode 100644 openpype/hosts/fusion/plugins/publish/validate_image_frame.py diff --git a/openpype/hosts/fusion/api/plugin.py b/openpype/hosts/fusion/api/plugin.py new file mode 100644 index 0000000000..63a74fbdb5 --- /dev/null +++ b/openpype/hosts/fusion/api/plugin.py @@ -0,0 +1,221 @@ +from copy import deepcopy +import os + +from openpype.hosts.fusion.api import ( + get_current_comp, + comp_lock_and_undo_chunk, +) + +from openpype.lib import ( + BoolDef, + EnumDef, +) +from openpype.pipeline import ( + legacy_io, + Creator, + CreatedInstance +) + + +class GenericCreateSaver(Creator): + default_variants = ["Main", "Mask"] + description = "Fusion Saver to generate image sequence" + icon = "fa5.eye" + + instance_attributes = [ + "reviewable" + ] + + settings_category = "fusion" + + image_format = "exr" + + # TODO: This should be renamed together with Nuke so it is aligned + temp_rendering_path_template = ( + "{workdir}/renders/fusion/{subset}/{subset}.{frame}.{ext}") + + def create(self, subset_name, instance_data, pre_create_data): + self.pass_pre_attributes_to_instance(instance_data, pre_create_data) + + instance = CreatedInstance( + family=self.family, + subset_name=subset_name, + data=instance_data, + creator=self, + ) + data = instance.data_to_store() + comp = get_current_comp() + with comp_lock_and_undo_chunk(comp): + args = (-32768, -32768) # Magical position numbers + saver = comp.AddTool("Saver", *args) + + self._update_tool_with_data(saver, data=data) + + # Register the CreatedInstance + self._imprint(saver, data) + + # Insert the transient data + instance.transient_data["tool"] = saver + + self._add_instance_to_context(instance) + + return instance + + def collect_instances(self): + comp = get_current_comp() + tools = comp.GetToolList(False, "Saver").values() + for tool in tools: + data = self.get_managed_tool_data(tool) + if not data: + continue + + # Add instance + created_instance = CreatedInstance.from_existing(data, self) + + # Collect transient data + created_instance.transient_data["tool"] = tool + + self._add_instance_to_context(created_instance) + + def update_instances(self, update_list): + for created_inst, _changes in update_list: + new_data = created_inst.data_to_store() + tool = created_inst.transient_data["tool"] + self._update_tool_with_data(tool, new_data) + self._imprint(tool, new_data) + + def remove_instances(self, instances): + for instance in instances: + # Remove the tool from the scene + + tool = instance.transient_data["tool"] + if tool: + tool.Delete() + + # Remove the collected CreatedInstance to remove from UI directly + self._remove_instance_from_context(instance) + + def _imprint(self, tool, data): + # Save all data in a "openpype.{key}" = value data + + # Instance id is the tool's name so we don't need to imprint as data + data.pop("instance_id", None) + + active = data.pop("active", None) + if active is not None: + # Use active value to set the passthrough state + tool.SetAttrs({"TOOLB_PassThrough": not active}) + + for key, value in data.items(): + tool.SetData(f"openpype.{key}", value) + + def _update_tool_with_data(self, tool, data): + """Update tool node name and output path based on subset data""" + if "subset" not in data: + return + + original_subset = tool.GetData("openpype.subset") + original_format = tool.GetData( + "openpype.creator_attributes.image_format" + ) + + subset = data["subset"] + if ( + original_subset != subset + or original_format != data["creator_attributes"]["image_format"] + ): + self._configure_saver_tool(data, tool, subset) + + def _configure_saver_tool(self, data, tool, subset): + formatting_data = deepcopy(data) + + # get frame padding from anatomy templates + frame_padding = self.project_anatomy.templates["frame_padding"] + + # get output format + ext = data["creator_attributes"]["image_format"] + + # Subset change detected + workdir = os.path.normpath(legacy_io.Session["AVALON_WORKDIR"]) + formatting_data.update({ + "workdir": workdir, + "frame": "0" * frame_padding, + "ext": ext, + "product": { + "name": formatting_data["subset"], + "type": formatting_data["family"], + }, + }) + + # build file path to render + filepath = self.temp_rendering_path_template.format(**formatting_data) + + comp = get_current_comp() + tool["Clip"] = comp.ReverseMapPath(os.path.normpath(filepath)) + + # Rename tool + if tool.Name != subset: + print(f"Renaming {tool.Name} -> {subset}") + tool.SetAttrs({"TOOLS_Name": subset}) + + def get_managed_tool_data(self, tool): + """Return data of the tool if it matches creator identifier""" + data = tool.GetData("openpype") + if not isinstance(data, dict): + return + + required = { + "id": "pyblish.avalon.instance", + "creator_identifier": self.identifier, + } + for key, value in required.items(): + if key not in data or data[key] != value: + return + + # Get active state from the actual tool state + attrs = tool.GetAttrs() + passthrough = attrs["TOOLB_PassThrough"] + data["active"] = not passthrough + + # Override publisher's UUID generation because tool names are + # already unique in Fusion in a comp + data["instance_id"] = tool.Name + + return data + + def get_instance_attr_defs(self): + """Settings for publish page""" + return self.get_pre_create_attr_defs() + + def pass_pre_attributes_to_instance(self, instance_data, pre_create_data): + creator_attrs = instance_data["creator_attributes"] = {} + for pass_key in pre_create_data.keys(): + creator_attrs[pass_key] = pre_create_data[pass_key] + + def _get_render_target_enum(self): + rendering_targets = { + "local": "Local machine rendering", + "frames": "Use existing frames", + } + if "farm_rendering" in self.instance_attributes: + rendering_targets["farm"] = "Farm rendering" + + return EnumDef( + "render_target", items=rendering_targets, label="Render target" + ) + + def _get_reviewable_bool(self): + return BoolDef( + "review", + default=("reviewable" in self.instance_attributes), + label="Review", + ) + + def _get_image_format_enum(self): + image_format_options = ["exr", "tga", "tif", "png", "jpg"] + return EnumDef( + "image_format", + items=image_format_options, + default=self.image_format, + label="Output Image Format", + ) diff --git a/openpype/hosts/fusion/plugins/create/create_image_saver.py b/openpype/hosts/fusion/plugins/create/create_image_saver.py new file mode 100644 index 0000000000..490228d488 --- /dev/null +++ b/openpype/hosts/fusion/plugins/create/create_image_saver.py @@ -0,0 +1,64 @@ +from openpype.lib import NumberDef + +from openpype.hosts.fusion.api.plugin import GenericCreateSaver +from openpype.hosts.fusion.api import get_current_comp + + +class CreateImageSaver(GenericCreateSaver): + """Fusion Saver to generate single image. + + Created to explicitly separate single ('image') or + multi frame('render) outputs. + + This might be temporary creator until 'alias' functionality will be + implemented to limit creation of additional product types with similar, but + not the same workflows. + """ + identifier = "io.openpype.creators.fusion.imagesaver" + label = "Image (saver)" + name = "image" + family = "image" + description = "Fusion Saver to generate image" + + default_frame = 0 + + def get_detail_description(self): + return """Fusion Saver to generate single image. + + This creator is expected for publishing of single frame `image` product + type. + + Artist should provide frame number (integer) to specify which frame + should be published. It must be inside of global timeline frame range. + + Supports local and deadline rendering. + + Supports selection from predefined set of output file extensions: + - exr + - tga + - png + - tif + - jpg + + Created to explicitly separate single frame ('image') or + multi frame ('render') outputs. + """ + + def get_pre_create_attr_defs(self): + """Settings for create page""" + attr_defs = [ + self._get_render_target_enum(), + self._get_reviewable_bool(), + self._get_frame_int(), + self._get_image_format_enum(), + ] + return attr_defs + + def _get_frame_int(self): + return NumberDef( + "frame", + default=self.default_frame, + label="Frame", + tooltip="Set frame to be rendered, must be inside of global " + "timeline range" + ) diff --git a/openpype/hosts/fusion/plugins/create/create_saver.py b/openpype/hosts/fusion/plugins/create/create_saver.py index 5870828b41..3a8ffe890b 100644 --- a/openpype/hosts/fusion/plugins/create/create_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_saver.py @@ -1,187 +1,42 @@ -from copy import deepcopy -import os +from openpype.lib import EnumDef -from openpype.hosts.fusion.api import ( - get_current_comp, - comp_lock_and_undo_chunk, -) - -from openpype.lib import ( - BoolDef, - EnumDef, -) -from openpype.pipeline import ( - legacy_io, - Creator as NewCreator, - CreatedInstance, - Anatomy, -) +from openpype.hosts.fusion.api.plugin import GenericCreateSaver -class CreateSaver(NewCreator): +class CreateSaver(GenericCreateSaver): + """Fusion Saver to generate image sequence of 'render' product type. + + Original Saver creator targeted for 'render' product type. It uses + original not to descriptive name because of values in Settings. + """ identifier = "io.openpype.creators.fusion.saver" label = "Render (saver)" name = "render" family = "render" - default_variants = ["Main", "Mask"] description = "Fusion Saver to generate image sequence" - icon = "fa5.eye" - instance_attributes = ["reviewable"] - image_format = "exr" + default_frame_range_option = "asset_db" - # TODO: This should be renamed together with Nuke so it is aligned - temp_rendering_path_template = ( - "{workdir}/renders/fusion/{subset}/{subset}.{frame}.{ext}" - ) + def get_detail_description(self): + return """Fusion Saver to generate image sequence. - def create(self, subset_name, instance_data, pre_create_data): - self.pass_pre_attributes_to_instance(instance_data, pre_create_data) + This creator is expected for publishing of image sequences for 'render' + product type. (But can publish even single frame 'render'.) - instance_data.update( - {"id": "pyblish.avalon.instance", "subset": subset_name} - ) + Select what should be source of render range: + - "Current asset context" - values set on Asset in DB (Ftrack) + - "From render in/out" - from node itself + - "From composition timeline" - from timeline - comp = get_current_comp() - with comp_lock_and_undo_chunk(comp): - args = (-32768, -32768) # Magical position numbers - saver = comp.AddTool("Saver", *args) + Supports local and farm rendering. - self._update_tool_with_data(saver, data=instance_data) - - # Register the CreatedInstance - instance = CreatedInstance( - family=self.family, - subset_name=subset_name, - data=instance_data, - creator=self, - ) - data = instance.data_to_store() - self._imprint(saver, data) - - # Insert the transient data - instance.transient_data["tool"] = saver - - self._add_instance_to_context(instance) - - return instance - - def collect_instances(self): - comp = get_current_comp() - tools = comp.GetToolList(False, "Saver").values() - for tool in tools: - data = self.get_managed_tool_data(tool) - if not data: - continue - - # Add instance - created_instance = CreatedInstance.from_existing(data, self) - - # Collect transient data - created_instance.transient_data["tool"] = tool - - self._add_instance_to_context(created_instance) - - def update_instances(self, update_list): - for created_inst, _changes in update_list: - new_data = created_inst.data_to_store() - tool = created_inst.transient_data["tool"] - self._update_tool_with_data(tool, new_data) - self._imprint(tool, new_data) - - def remove_instances(self, instances): - for instance in instances: - # Remove the tool from the scene - - tool = instance.transient_data["tool"] - if tool: - tool.Delete() - - # Remove the collected CreatedInstance to remove from UI directly - self._remove_instance_from_context(instance) - - def _imprint(self, tool, data): - # Save all data in a "openpype.{key}" = value data - - # Instance id is the tool's name so we don't need to imprint as data - data.pop("instance_id", None) - - active = data.pop("active", None) - if active is not None: - # Use active value to set the passthrough state - tool.SetAttrs({"TOOLB_PassThrough": not active}) - - for key, value in data.items(): - tool.SetData(f"openpype.{key}", value) - - def _update_tool_with_data(self, tool, data): - """Update tool node name and output path based on subset data""" - if "subset" not in data: - return - - original_subset = tool.GetData("openpype.subset") - original_format = tool.GetData( - "openpype.creator_attributes.image_format" - ) - - subset = data["subset"] - if ( - original_subset != subset - or original_format != data["creator_attributes"]["image_format"] - ): - self._configure_saver_tool(data, tool, subset) - - def _configure_saver_tool(self, data, tool, subset): - formatting_data = deepcopy(data) - - # get frame padding from anatomy templates - anatomy = Anatomy() - frame_padding = anatomy.templates["frame_padding"] - - # get output format - ext = data["creator_attributes"]["image_format"] - - # Subset change detected - workdir = os.path.normpath(legacy_io.Session["AVALON_WORKDIR"]) - formatting_data.update( - {"workdir": workdir, "frame": "0" * frame_padding, "ext": ext} - ) - - # build file path to render - filepath = self.temp_rendering_path_template.format(**formatting_data) - - comp = get_current_comp() - tool["Clip"] = comp.ReverseMapPath(os.path.normpath(filepath)) - - # Rename tool - if tool.Name != subset: - print(f"Renaming {tool.Name} -> {subset}") - tool.SetAttrs({"TOOLS_Name": subset}) - - def get_managed_tool_data(self, tool): - """Return data of the tool if it matches creator identifier""" - data = tool.GetData("openpype") - if not isinstance(data, dict): - return - - required = { - "id": "pyblish.avalon.instance", - "creator_identifier": self.identifier, - } - for key, value in required.items(): - if key not in data or data[key] != value: - return - - # Get active state from the actual tool state - attrs = tool.GetAttrs() - passthrough = attrs["TOOLB_PassThrough"] - data["active"] = not passthrough - - # Override publisher's UUID generation because tool names are - # already unique in Fusion in a comp - data["instance_id"] = tool.Name - - return data + Supports selection from predefined set of output file extensions: + - exr + - tga + - png + - tif + - jpg + """ def get_pre_create_attr_defs(self): """Settings for create page""" @@ -193,29 +48,6 @@ class CreateSaver(NewCreator): ] return attr_defs - def get_instance_attr_defs(self): - """Settings for publish page""" - return self.get_pre_create_attr_defs() - - def pass_pre_attributes_to_instance(self, instance_data, pre_create_data): - creator_attrs = instance_data["creator_attributes"] = {} - for pass_key in pre_create_data.keys(): - creator_attrs[pass_key] = pre_create_data[pass_key] - - # These functions below should be moved to another file - # so it can be used by other plugins. plugin.py ? - def _get_render_target_enum(self): - rendering_targets = { - "local": "Local machine rendering", - "frames": "Use existing frames", - } - if "farm_rendering" in self.instance_attributes: - rendering_targets["farm"] = "Farm rendering" - - return EnumDef( - "render_target", items=rendering_targets, label="Render target" - ) - def _get_frame_range_enum(self): frame_range_options = { "asset_db": "Current asset context", @@ -227,42 +59,5 @@ class CreateSaver(NewCreator): "frame_range_source", items=frame_range_options, label="Frame range source", - ) - - def _get_reviewable_bool(self): - return BoolDef( - "review", - default=("reviewable" in self.instance_attributes), - label="Review", - ) - - def _get_image_format_enum(self): - image_format_options = ["exr", "tga", "tif", "png", "jpg"] - return EnumDef( - "image_format", - items=image_format_options, - default=self.image_format, - label="Output Image Format", - ) - - def apply_settings(self, project_settings): - """Method called on initialization of plugin to apply settings.""" - - # plugin settings - plugin_settings = project_settings["fusion"]["create"][ - self.__class__.__name__ - ] - - # individual attributes - self.instance_attributes = plugin_settings.get( - "instance_attributes", self.instance_attributes - ) - self.default_variants = plugin_settings.get( - "default_variants", self.default_variants - ) - self.temp_rendering_path_template = plugin_settings.get( - "temp_rendering_path_template", self.temp_rendering_path_template - ) - self.image_format = plugin_settings.get( - "image_format", self.image_format + default=self.default_frame_range_option ) diff --git a/openpype/hosts/fusion/plugins/publish/collect_inputs.py b/openpype/hosts/fusion/plugins/publish/collect_inputs.py index a6628300db..f23e4d0268 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_inputs.py +++ b/openpype/hosts/fusion/plugins/publish/collect_inputs.py @@ -95,7 +95,7 @@ class CollectUpstreamInputs(pyblish.api.InstancePlugin): label = "Collect Inputs" order = pyblish.api.CollectorOrder + 0.2 hosts = ["fusion"] - families = ["render"] + families = ["render", "image"] def process(self, instance): diff --git a/openpype/hosts/fusion/plugins/publish/collect_instances.py b/openpype/hosts/fusion/plugins/publish/collect_instances.py index 4d6da79b77..a0131248e8 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_instances.py +++ b/openpype/hosts/fusion/plugins/publish/collect_instances.py @@ -57,6 +57,18 @@ class CollectInstanceData(pyblish.api.InstancePlugin): start_with_handle = comp_start end_with_handle = comp_end + frame = instance.data["creator_attributes"].get("frame") + # explicitly publishing only single frame + if frame is not None: + frame = int(frame) + + start = frame + end = frame + handle_start = 0 + handle_end = 0 + start_with_handle = frame + end_with_handle = frame + # Include start and end render frame in label subset = instance.data["subset"] label = ( diff --git a/openpype/hosts/fusion/plugins/publish/collect_render.py b/openpype/hosts/fusion/plugins/publish/collect_render.py index a7daa0b64c..366eaa905c 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_render.py +++ b/openpype/hosts/fusion/plugins/publish/collect_render.py @@ -50,7 +50,7 @@ class CollectFusionRender( continue family = inst.data["family"] - if family != "render": + if family not in ["render", "image"]: continue task_name = context.data["task"] @@ -59,7 +59,7 @@ class CollectFusionRender( instance_families = inst.data.get("families", []) subset_name = inst.data["subset"] instance = FusionRenderInstance( - family="render", + family=family, tool=tool, workfileComp=comp, families=instance_families, diff --git a/openpype/hosts/fusion/plugins/publish/save_scene.py b/openpype/hosts/fusion/plugins/publish/save_scene.py index 0798e7c8b7..da9b6ce41f 100644 --- a/openpype/hosts/fusion/plugins/publish/save_scene.py +++ b/openpype/hosts/fusion/plugins/publish/save_scene.py @@ -7,7 +7,7 @@ class FusionSaveComp(pyblish.api.ContextPlugin): label = "Save current file" order = pyblish.api.ExtractorOrder - 0.49 hosts = ["fusion"] - families = ["render", "workfile"] + families = ["render", "image", "workfile"] def process(self, context): diff --git a/openpype/hosts/fusion/plugins/publish/validate_background_depth.py b/openpype/hosts/fusion/plugins/publish/validate_background_depth.py index 6908889eb4..e268f8adec 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_background_depth.py +++ b/openpype/hosts/fusion/plugins/publish/validate_background_depth.py @@ -17,7 +17,7 @@ class ValidateBackgroundDepth( order = pyblish.api.ValidatorOrder label = "Validate Background Depth 32 bit" hosts = ["fusion"] - families = ["render"] + families = ["render", "image"] optional = True actions = [SelectInvalidAction, publish.RepairAction] diff --git a/openpype/hosts/fusion/plugins/publish/validate_comp_saved.py b/openpype/hosts/fusion/plugins/publish/validate_comp_saved.py index 748047e8cf..6e6d10e09a 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_comp_saved.py +++ b/openpype/hosts/fusion/plugins/publish/validate_comp_saved.py @@ -9,7 +9,7 @@ class ValidateFusionCompSaved(pyblish.api.ContextPlugin): order = pyblish.api.ValidatorOrder label = "Validate Comp Saved" - families = ["render"] + families = ["render", "image"] hosts = ["fusion"] def process(self, context): diff --git a/openpype/hosts/fusion/plugins/publish/validate_create_folder_checked.py b/openpype/hosts/fusion/plugins/publish/validate_create_folder_checked.py index 35c92163eb..d5c618af58 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_create_folder_checked.py +++ b/openpype/hosts/fusion/plugins/publish/validate_create_folder_checked.py @@ -15,7 +15,7 @@ class ValidateCreateFolderChecked(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Validate Create Folder Checked" - families = ["render"] + families = ["render", "image"] hosts = ["fusion"] actions = [RepairAction, SelectInvalidAction] diff --git a/openpype/hosts/fusion/plugins/publish/validate_filename_has_extension.py b/openpype/hosts/fusion/plugins/publish/validate_filename_has_extension.py index 537e43c875..38cd578ff2 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_filename_has_extension.py +++ b/openpype/hosts/fusion/plugins/publish/validate_filename_has_extension.py @@ -17,7 +17,7 @@ class ValidateFilenameHasExtension(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Validate Filename Has Extension" - families = ["render"] + families = ["render", "image"] hosts = ["fusion"] actions = [SelectInvalidAction] diff --git a/openpype/hosts/fusion/plugins/publish/validate_image_frame.py b/openpype/hosts/fusion/plugins/publish/validate_image_frame.py new file mode 100644 index 0000000000..734203f31c --- /dev/null +++ b/openpype/hosts/fusion/plugins/publish/validate_image_frame.py @@ -0,0 +1,27 @@ +import pyblish.api + +from openpype.pipeline import PublishValidationError + + +class ValidateImageFrame(pyblish.api.InstancePlugin): + """Validates that `image` product type contains only single frame.""" + + order = pyblish.api.ValidatorOrder + label = "Validate Image Frame" + families = ["image"] + hosts = ["fusion"] + + def process(self, instance): + render_start = instance.data["frameStartHandle"] + render_end = instance.data["frameEndHandle"] + too_many_frames = (isinstance(instance.data["expectedFiles"], list) + and len(instance.data["expectedFiles"]) > 1) + + if render_end - render_start > 0 or too_many_frames: + desc = ("Trying to render multiple frames. 'image' product type " + "is meant for single frame. Please use 'render' creator.") + raise PublishValidationError( + title="Frame range outside of comp range", + message=desc, + description=desc + ) diff --git a/openpype/hosts/fusion/plugins/publish/validate_instance_frame_range.py b/openpype/hosts/fusion/plugins/publish/validate_instance_frame_range.py index 06cd0ca186..edf219e752 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_instance_frame_range.py +++ b/openpype/hosts/fusion/plugins/publish/validate_instance_frame_range.py @@ -7,8 +7,8 @@ class ValidateInstanceFrameRange(pyblish.api.InstancePlugin): """Validate instance frame range is within comp's global render range.""" order = pyblish.api.ValidatorOrder - label = "Validate Filename Has Extension" - families = ["render"] + label = "Validate Frame Range" + families = ["render", "image"] hosts = ["fusion"] def process(self, instance): diff --git a/openpype/hosts/fusion/plugins/publish/validate_saver_has_input.py b/openpype/hosts/fusion/plugins/publish/validate_saver_has_input.py index faf2102a8b..0103e990fb 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_saver_has_input.py +++ b/openpype/hosts/fusion/plugins/publish/validate_saver_has_input.py @@ -13,7 +13,7 @@ class ValidateSaverHasInput(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Validate Saver Has Input" - families = ["render"] + families = ["render", "image"] hosts = ["fusion"] actions = [SelectInvalidAction] diff --git a/openpype/hosts/fusion/plugins/publish/validate_saver_passthrough.py b/openpype/hosts/fusion/plugins/publish/validate_saver_passthrough.py index 9004976dc5..6019bee93a 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_saver_passthrough.py +++ b/openpype/hosts/fusion/plugins/publish/validate_saver_passthrough.py @@ -9,7 +9,7 @@ class ValidateSaverPassthrough(pyblish.api.ContextPlugin): order = pyblish.api.ValidatorOrder label = "Validate Saver Passthrough" - families = ["render"] + families = ["render", "image"] hosts = ["fusion"] actions = [SelectInvalidAction] diff --git a/openpype/hosts/fusion/plugins/publish/validate_saver_resolution.py b/openpype/hosts/fusion/plugins/publish/validate_saver_resolution.py index efa7295d11..f6aba170c0 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_saver_resolution.py +++ b/openpype/hosts/fusion/plugins/publish/validate_saver_resolution.py @@ -64,7 +64,7 @@ class ValidateSaverResolution( order = pyblish.api.ValidatorOrder label = "Validate Asset Resolution" - families = ["render"] + families = ["render", "image"] hosts = ["fusion"] optional = True actions = [SelectInvalidAction] diff --git a/openpype/hosts/fusion/plugins/publish/validate_unique_subsets.py b/openpype/hosts/fusion/plugins/publish/validate_unique_subsets.py index 5b6ceb2fdb..d1693ef3dc 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_unique_subsets.py +++ b/openpype/hosts/fusion/plugins/publish/validate_unique_subsets.py @@ -11,7 +11,7 @@ class ValidateUniqueSubsets(pyblish.api.ContextPlugin): order = pyblish.api.ValidatorOrder label = "Validate Unique Subsets" - families = ["render"] + families = ["render", "image"] hosts = ["fusion"] actions = [SelectInvalidAction] diff --git a/openpype/settings/defaults/project_settings/fusion.json b/openpype/settings/defaults/project_settings/fusion.json index 8579442625..15b6bfc09b 100644 --- a/openpype/settings/defaults/project_settings/fusion.json +++ b/openpype/settings/defaults/project_settings/fusion.json @@ -32,6 +32,18 @@ "farm_rendering" ], "image_format": "exr" + }, + "CreateImageSaver": { + "temp_rendering_path_template": "{workdir}/renders/fusion/{subset}/{subset}.{ext}", + "default_variants": [ + "Main", + "Mask" + ], + "instance_attributes": [ + "reviewable", + "farm_rendering" + ], + "image_format": "exr" } }, "publish": { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json index fbd856b895..8669842087 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json @@ -74,7 +74,56 @@ "type": "dict", "collapsible": true, "key": "CreateSaver", - "label": "Create Saver", + "label": "Create Render Saver", + "is_group": true, + "children": [ + { + "type": "text", + "key": "temp_rendering_path_template", + "label": "Temporary rendering path template" + }, + { + "type": "list", + "key": "default_variants", + "label": "Default variants", + "object_type": { + "type": "text" + } + }, + { + "key": "instance_attributes", + "label": "Instance attributes", + "type": "enum", + "multiselection": true, + "enum_items": [ + { + "reviewable": "Reviewable" + }, + { + "farm_rendering": "Farm rendering" + } + ] + }, + { + "key": "image_format", + "label": "Output Image Format", + "type": "enum", + "multiselect": false, + "enum_items": [ + {"exr": "exr"}, + {"tga": "tga"}, + {"png": "png"}, + {"tif": "tif"}, + {"jpg": "jpg"} + ] + } + ] + }, + { + "type": "dict", + "collapsible": true, + "key": "CreateImageSaver", + "label": "Create Image Saver", "is_group": true, "children": [ { diff --git a/server_addon/fusion/server/settings.py b/server_addon/fusion/server/settings.py index 21189b390e..bf295f3064 100644 --- a/server_addon/fusion/server/settings.py +++ b/server_addon/fusion/server/settings.py @@ -25,6 +25,24 @@ def _create_saver_instance_attributes_enum(): ] +def _image_format_enum(): + return [ + {"value": "exr", "label": "exr"}, + {"value": "tga", "label": "tga"}, + {"value": "png", "label": "png"}, + {"value": "tif", "label": "tif"}, + {"value": "jpg", "label": "jpg"}, + ] + + +def _frame_range_options_enum(): + return [ + {"value": "asset_db", "label": "Current asset context"}, + {"value": "render_range", "label": "From render in/out"}, + {"value": "comp_range", "label": "From composition timeline"}, + ] + + class CreateSaverPluginModel(BaseSettingsModel): _isGroup = True temp_rendering_path_template: str = Field( @@ -59,10 +77,29 @@ class HooksModel(BaseSettingsModel): ) +class CreateSaverModel(CreateSaverPluginModel): + default_frame_range_option: str = Field( + default="asset_db", + enum_resolver=_frame_range_options_enum, + title="Default frame range source" + ) + + +class CreateImageSaverModel(CreateSaverPluginModel): + default_frame: int = Field( + 0, + title="Default rendered frame" + ) class CreatPluginsModel(BaseSettingsModel): - CreateSaver: CreateSaverPluginModel = Field( - default_factory=CreateSaverPluginModel, - title="Create Saver" + CreateSaver: CreateSaverModel = Field( + default_factory=CreateSaverModel, + title="Create Saver", + description="Creator for render product type (eg. sequence)" + ) + CreateImageSaver: CreateImageSaverModel = Field( + default_factory=CreateImageSaverModel, + title="Create Image Saver", + description="Creator for image product type (eg. single)" ) @@ -117,15 +154,21 @@ DEFAULT_VALUES = { "reviewable", "farm_rendering" ], - "output_formats": [ - "exr", - "jpg", - "jpeg", - "jpg", - "tiff", - "png", - "tga" - ] + "image_format": "exr", + "default_frame_range_option": "asset_db" + }, + "CreateImageSaver": { + "temp_rendering_path_template": "{workdir}/renders/fusion/{product[name]}/{product[name]}.{ext}", + "default_variants": [ + "Main", + "Mask" + ], + "instance_attributes": [ + "reviewable", + "farm_rendering" + ], + "image_format": "exr", + "default_frame": 0 } } } diff --git a/server_addon/fusion/server/version.py b/server_addon/fusion/server/version.py index b3f4756216..ae7362549b 100644 --- a/server_addon/fusion/server/version.py +++ b/server_addon/fusion/server/version.py @@ -1 +1 @@ -__version__ = "0.1.2" +__version__ = "0.1.3" From 1ac89d2efa55792e11023419e3ac5914e7b57480 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 15 Jan 2024 19:17:12 +0800 Subject: [PATCH 56/61] restore some of the codes on lib and collect images --- openpype/hosts/substancepainter/api/lib.py | 30 ------------------- .../publish/collect_textureset_images.py | 8 ++--- 2 files changed, 3 insertions(+), 35 deletions(-) diff --git a/openpype/hosts/substancepainter/api/lib.py b/openpype/hosts/substancepainter/api/lib.py index 896cca79b0..1cb480b552 100644 --- a/openpype/hosts/substancepainter/api/lib.py +++ b/openpype/hosts/substancepainter/api/lib.py @@ -643,33 +643,3 @@ def prompt_new_file_with_mesh(mesh_filepath): return return project_mesh - - -def has_rgb_channel_in_texture_set(texture_set_name, map_identifier): - """Function to check whether the texture has RGB channel. - - Args: - texture_set_name (str): Name of Texture Set - map_identifier (str): Map identifier - - Returns: - bool: Whether the channel type identifier has RGB channel or not - in the texture stack. - """ - - # 2D_View is always True as it exports all texture maps - texture_stack = ( - substance_painter.textureset.Stack.from_name(texture_set_name) - ) - # 2D_View is always True as it exports all texture maps - if map_identifier == "2D_View": - return True - - channel_type = getattr( - substance_painter.textureset.ChannelType, map_identifier, None) - if channel_type is None: - return False - if not texture_stack.has_channel(channel_type): - return False - - return texture_stack.get_channel(channel_type).is_color() diff --git a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py index 370e72f34b..316f72509e 100644 --- a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py +++ b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py @@ -7,8 +7,7 @@ from openpype.pipeline import publish import substance_painter.textureset from openpype.hosts.substancepainter.api.lib import ( get_parsed_export_maps, - strip_template, - has_rgb_channel_in_texture_set + strip_template ) from openpype.pipeline.create import get_subset_name from openpype.client import get_asset_by_name @@ -79,6 +78,7 @@ class CollectTextureSet(pyblish.api.InstancePlugin): # Always include the map identifier map_identifier = strip_template(template) suffix += f".{map_identifier}" + image_subset = get_subset_name( # TODO: The family actually isn't 'texture' currently but for now # this is only done so the subset name starts with 'texture' @@ -132,9 +132,7 @@ class CollectTextureSet(pyblish.api.InstancePlugin): # Store color space with the instance # Note: The extractor will assign it to the representation colorspace = outputs[0].get("colorSpace") - has_rgb_channel = has_rgb_channel_in_texture_set( - texture_set_name, map_identifier) - if colorspace and has_rgb_channel: + if colorspace: self.log.debug(f"{image_subset} colorspace: {colorspace}") image_instance.data["colorspace"] = colorspace From 7dcdd1b3b0d1799d786054da896af57f13244657 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 15 Jan 2024 17:21:40 +0100 Subject: [PATCH 57/61] remove 'template_name_profiles' for 'IntegrateHeroVersion' (#6130) --- openpype/settings/ayon_settings.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/openpype/settings/ayon_settings.py b/openpype/settings/ayon_settings.py index a6d90d1cf0..36f96c9dc7 100644 --- a/openpype/settings/ayon_settings.py +++ b/openpype/settings/ayon_settings.py @@ -1291,12 +1291,6 @@ def _convert_global_project_settings(ayon_settings, output, default_settings): for extract_burnin_def in extract_burnin_defs } - ayon_integrate_hero = ayon_publish["IntegrateHeroVersion"] - for profile in ayon_integrate_hero["template_name_profiles"]: - if "product_types" not in profile: - break - profile["families"] = profile.pop("product_types") - if "IntegrateProductGroup" in ayon_publish: subset_group = ayon_publish.pop("IntegrateProductGroup") subset_group_profiles = subset_group.pop("product_grouping_profiles") From 10167c86a7c25044280df8012ec3193677749677 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 15 Jan 2024 17:33:26 +0100 Subject: [PATCH 58/61] use instance version if context version is not set (#6117) --- openpype/plugins/publish/collect_anatomy_instance_data.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/collect_anatomy_instance_data.py b/openpype/plugins/publish/collect_anatomy_instance_data.py index 0a34848166..34df49ea5b 100644 --- a/openpype/plugins/publish/collect_anatomy_instance_data.py +++ b/openpype/plugins/publish/collect_anatomy_instance_data.py @@ -200,9 +200,15 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): self._fill_task_data(instance, project_task_types, anatomy_data) # Define version + version_number = None if self.follow_workfile_version: version_number = context.data("version") - else: + + # Even if 'follow_workfile_version' is enabled, it may not be set + # because workfile version was not collected to 'context.data' + # - that can happen e.g. in 'traypublisher' or other hosts without + # a workfile + if version_number is None: version_number = instance.data.get("version") # use latest version (+1) if already any exist From 17e861f532bbdd6b3e4e9f16f2c69639483c16b2 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 15 Jan 2024 17:42:47 +0000 Subject: [PATCH 59/61] Illicit suggestion --- openpype/lib/transcoding.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 79d24e737a..03fd91bec2 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -110,8 +110,9 @@ def get_oiio_info_for_input(filepath, logger=None, subimages=False): if line == "": subimages_lines.append(lines) lines = [] + xml_started = False - if not xml_started: + if not subimages_lines: raise ValueError( "Failed to read input file \"{}\".\nOutput:\n{}".format( filepath, output @@ -120,10 +121,6 @@ def get_oiio_info_for_input(filepath, logger=None, subimages=False): output = [] for subimage_lines in subimages_lines: - # First line of oiiotool for xml format is "Reading path/to/file.ext". - if not subimage_lines[0].startswith(" Date: Tue, 16 Jan 2024 10:53:40 +0100 Subject: [PATCH 60/61] make sure style object is not garbage collected --- openpype/tools/utils/lib.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/tools/utils/lib.py b/openpype/tools/utils/lib.py index 723e71e7aa..365caaafd9 100644 --- a/openpype/tools/utils/lib.py +++ b/openpype/tools/utils/lib.py @@ -91,7 +91,8 @@ def set_style_property(widget, property_name, property_value): if cur_value == property_value: return widget.setProperty(property_name, property_value) - widget.style().polish(widget) + style = widget.style() + style.polish(widget) def paint_image_with_color(image, color): From a6cc0b511e4468be960e14f47aa2300a865e9035 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Tue, 16 Jan 2024 10:15:48 +0000 Subject: [PATCH 61/61] Maya: Account and ignore free image planes. (#5993) * Account and ignore for free image planes. * Incorporating BigRoy changes --------- Co-authored-by: Petr Kalis --- .../maya/plugins/publish/extract_camera_mayaScene.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py b/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py index 38cf00bbdd..f67e9db14f 100644 --- a/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py +++ b/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py @@ -265,13 +265,16 @@ def transfer_image_planes(source_cameras, target_cameras, try: for source_camera, target_camera in zip(source_cameras, target_cameras): - image_planes = cmds.listConnections(source_camera, + image_plane_plug = "{}.imagePlane".format(source_camera) + image_planes = cmds.listConnections(image_plane_plug, + source=True, + destination=False, type="imagePlane") or [] # Split of the parent path they are attached - we want - # the image plane node name. + # the image plane node name if attached to a camera. # TODO: Does this still mean the image plane name is unique? - image_planes = [x.split("->", 1)[1] for x in image_planes] + image_planes = [x.split("->", 1)[-1] for x in image_planes] if not image_planes: continue @@ -282,7 +285,7 @@ def transfer_image_planes(source_cameras, target_cameras, if source_camera == target_camera: continue _attach_image_plane(target_camera, image_plane) - else: # explicitly dettaching image planes + else: # explicitly detach image planes cmds.imagePlane(image_plane, edit=True, detach=True) originals[source_camera].append(image_plane) yield