From a644949a29b017f2d38c6697fd73c67e0d11f1c5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 29 Mar 2023 15:21:03 +0800 Subject: [PATCH 01/52] make sure the render dialog is close for the update of resolution and other render settings --- openpype/hosts/max/api/lib.py | 8 +++++++- openpype/hosts/max/plugins/create/create_render.py | 7 +++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index ac7d75db08..519eeffd7f 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -173,10 +173,16 @@ def set_scene_resolution(width: int, height: int): None """ + # make sure the render dialog is closed + # for the update of resolution + # Changing the Render Setup dialog settingsshould be done + # with the actual Render Setup dialog in a closed state. + if rt.renderSceneDialog.isOpen(): + rt.renderSceneDialog.close() + rt.renderWidth = width rt.renderHeight = height - def reset_scene_resolution(): """Apply the scene resolution from the project definition diff --git a/openpype/hosts/max/plugins/create/create_render.py b/openpype/hosts/max/plugins/create/create_render.py index 269fff2e32..a8720f464d 100644 --- a/openpype/hosts/max/plugins/create/create_render.py +++ b/openpype/hosts/max/plugins/create/create_render.py @@ -27,6 +27,13 @@ class CreateRender(plugin.MaxCreator): # for additional work on the node: # instance_node = rt.getNodeByName(instance.get("instance_node")) + # make sure the render dialog is closed + # for the update of resolution + # Changing the Render Setup dialog settings should be done + # with the actual Render Setup dialog in a closed state. + if rt.renderSceneDialog.isOpen(): + rt.renderSceneDialog.close() + # set viewport camera for rendering(mandatory for deadline) RenderSettings().set_render_camera(sel_obj) # set output paths for rendering(mandatory for deadline) From 0079322a6de30b70108dd5103bb04403a0730e56 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 31 Mar 2023 16:25:51 +0800 Subject: [PATCH 02/52] close the render setup dialog before the render settings --- openpype/hosts/max/api/lib_rendersettings.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index 4940265a23..e7f4ee1e6b 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -105,6 +105,9 @@ class RenderSettings(object): rt.rendSaveFile = True + if rt.renderSceneDialog.isOpen(): + rt.renderSceneDialog.close() + def arnold_setup(self): # get Arnold RenderView run in the background # for setting up renderable camera From ee8b5e770013b42553b3c3e32aec44d37479728f Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 31 Mar 2023 16:28:41 +0800 Subject: [PATCH 03/52] cleanup --- openpype/hosts/max/plugins/create/create_render.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/max/plugins/create/create_render.py b/openpype/hosts/max/plugins/create/create_render.py index a8720f464d..68ae5eac72 100644 --- a/openpype/hosts/max/plugins/create/create_render.py +++ b/openpype/hosts/max/plugins/create/create_render.py @@ -31,8 +31,6 @@ class CreateRender(plugin.MaxCreator): # for the update of resolution # Changing the Render Setup dialog settings should be done # with the actual Render Setup dialog in a closed state. - if rt.renderSceneDialog.isOpen(): - rt.renderSceneDialog.close() # set viewport camera for rendering(mandatory for deadline) RenderSettings().set_render_camera(sel_obj) From db72ed2e634389e4f57ee555585efb1b934c3e32 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 18 Apr 2023 15:46:47 +0800 Subject: [PATCH 04/52] the resolution and the frame range set correctly before saving the scene --- openpype/hosts/max/api/pipeline.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/max/api/pipeline.py b/openpype/hosts/max/api/pipeline.py index dacc402318..957c674518 100644 --- a/openpype/hosts/max/api/pipeline.py +++ b/openpype/hosts/max/api/pipeline.py @@ -52,8 +52,11 @@ class MaxHost(HostBase, IWorkfileHost, ILoadHost, INewPublisher): def context_setting(): return lib.set_context_setting() + rt.callbacks.addScript(rt.Name('systemPostNew'), context_setting) + rt.callbacks.addScript(rt.Name('filePreSave'), + context_setting) def has_unsaved_changes(self): # TODO: how to get it from 3dsmax? From 973caf2d6de18b1f63384aef8089a0908878b4ba Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 18 Apr 2023 15:56:13 +0800 Subject: [PATCH 05/52] not adding the before save callback --- openpype/hosts/max/api/pipeline.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/max/api/pipeline.py b/openpype/hosts/max/api/pipeline.py index 957c674518..50fe30b299 100644 --- a/openpype/hosts/max/api/pipeline.py +++ b/openpype/hosts/max/api/pipeline.py @@ -55,8 +55,6 @@ class MaxHost(HostBase, IWorkfileHost, ILoadHost, INewPublisher): rt.callbacks.addScript(rt.Name('systemPostNew'), context_setting) - rt.callbacks.addScript(rt.Name('filePreSave'), - context_setting) def has_unsaved_changes(self): # TODO: how to get it from 3dsmax? From 5f78ba9bb8a4c5747618b6f669d9c0ddd287a363 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 18 Apr 2023 16:06:20 +0800 Subject: [PATCH 06/52] adding callback to reset the resolution when opening the file --- openpype/hosts/max/api/pipeline.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/max/api/pipeline.py b/openpype/hosts/max/api/pipeline.py index 50fe30b299..ac841d395f 100644 --- a/openpype/hosts/max/api/pipeline.py +++ b/openpype/hosts/max/api/pipeline.py @@ -56,6 +56,9 @@ class MaxHost(HostBase, IWorkfileHost, ILoadHost, INewPublisher): rt.callbacks.addScript(rt.Name('systemPostNew'), context_setting) + rt.callbacks.addScript(rt.Name('filePostOpen'), + context_setting) + def has_unsaved_changes(self): # TODO: how to get it from 3dsmax? return True From 5e012d6be254dfa68721d5f08a981cafecbc8842 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 21 Apr 2023 17:29:47 +0800 Subject: [PATCH 07/52] add validator for resolution setting --- openpype/hosts/max/api/pipeline.py | 3 - .../publish/validate_resolution_setting.py | 59 +++++++++++++++++++ 2 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 openpype/hosts/max/plugins/publish/validate_resolution_setting.py diff --git a/openpype/hosts/max/api/pipeline.py b/openpype/hosts/max/api/pipeline.py index ac841d395f..50fe30b299 100644 --- a/openpype/hosts/max/api/pipeline.py +++ b/openpype/hosts/max/api/pipeline.py @@ -56,9 +56,6 @@ class MaxHost(HostBase, IWorkfileHost, ILoadHost, INewPublisher): rt.callbacks.addScript(rt.Name('systemPostNew'), context_setting) - rt.callbacks.addScript(rt.Name('filePostOpen'), - context_setting) - def has_unsaved_changes(self): # TODO: how to get it from 3dsmax? return True diff --git a/openpype/hosts/max/plugins/publish/validate_resolution_setting.py b/openpype/hosts/max/plugins/publish/validate_resolution_setting.py new file mode 100644 index 0000000000..43a3a3a278 --- /dev/null +++ b/openpype/hosts/max/plugins/publish/validate_resolution_setting.py @@ -0,0 +1,59 @@ +import pyblish.api +from openpype.pipeline import ( + PublishValidationError, + OptionalPyblishPluginMixin +) +from pymxs import runtime as rt +from openpype.hosts.max.api.lib import reset_scene_resolution + +from openpype.pipeline.context_tools import ( + get_current_project_asset, + get_current_project +) + + +class ValidateResolutionSetting(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): + """Validate the resolution setting aligned with DB""" + + order = pyblish.api.ValidatorOrder- 0.01 + families = ["maxrender"] + hosts = ["max"] + label = "Validate Resolution Setting" + optional = True + + def process(self, instance): + if not self.is_active(instance.data): + return + width, height = self.get_db_resolution(instance) + current_width = rt.renderwidth + current_height = rt.renderHeight + if current_width != width and current_height != height: + raise PublishValidationError("Resolution Setting" + " not aligned with DB") + if current_width != width: + raise PublishValidationError("Width in Resolution Setting " + "not aligned with DB") + + if current_height != height: + raise PublishValidationError("Height in Resolution Setting " + "not aligned with DB") + + + def get_db_resolution(self, instance): + data = ["data.resolutionWidth", "data.resolutionHeight"] + project_resolution = get_current_project(fields=data) + project_resolution_data = project_resolution["data"] + asset_resolution = get_current_project_asset(fields=data) + asset_resolution_data = asset_resolution["data"] + # Set project resolution + project_width = int(project_resolution_data.get("resolutionWidth", 1920)) + project_height = int(project_resolution_data.get("resolutionHeight", 1080)) + width = int(asset_resolution_data.get("resolutionWidth", project_width)) + height = int(asset_resolution_data.get("resolutionHeight", project_height)) + + return width, height + + @classmethod + def repair(cls, instance): + reset_scene_resolution() From dac51f41ec5d97384698faf0f19e4ba398d96be6 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 21 Apr 2023 17:32:11 +0800 Subject: [PATCH 08/52] hound fix --- .../publish/validate_resolution_setting.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_resolution_setting.py b/openpype/hosts/max/plugins/publish/validate_resolution_setting.py index 43a3a3a278..ce1f8a975d 100644 --- a/openpype/hosts/max/plugins/publish/validate_resolution_setting.py +++ b/openpype/hosts/max/plugins/publish/validate_resolution_setting.py @@ -16,7 +16,7 @@ class ValidateResolutionSetting(pyblish.api.InstancePlugin, OptionalPyblishPluginMixin): """Validate the resolution setting aligned with DB""" - order = pyblish.api.ValidatorOrder- 0.01 + order = pyblish.api.ValidatorOrder - 0.01 families = ["maxrender"] hosts = ["max"] label = "Validate Resolution Setting" @@ -39,7 +39,6 @@ class ValidateResolutionSetting(pyblish.api.InstancePlugin, raise PublishValidationError("Height in Resolution Setting " "not aligned with DB") - def get_db_resolution(self, instance): data = ["data.resolutionWidth", "data.resolutionHeight"] project_resolution = get_current_project(fields=data) @@ -47,10 +46,17 @@ class ValidateResolutionSetting(pyblish.api.InstancePlugin, asset_resolution = get_current_project_asset(fields=data) asset_resolution_data = asset_resolution["data"] # Set project resolution - project_width = int(project_resolution_data.get("resolutionWidth", 1920)) - project_height = int(project_resolution_data.get("resolutionHeight", 1080)) - width = int(asset_resolution_data.get("resolutionWidth", project_width)) - height = int(asset_resolution_data.get("resolutionHeight", project_height)) + project_width = int( + project_resolution_data.get("resolutionWidth", 1920) + ) + project_height = int( + project_resolution_data.get("resolutionHeight", 1080)) + width = int( + asset_resolution_data.get("resolutionWidth", project_width) + ) + height = int( + asset_resolution_data.get("resolutionHeight", project_height) + ) return width, height From 90f12d15e050e0136eaec50a53d2049385e3fcf6 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 21 Apr 2023 17:33:08 +0800 Subject: [PATCH 09/52] hound fix --- .../max/plugins/publish/validate_resolution_setting.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_resolution_setting.py b/openpype/hosts/max/plugins/publish/validate_resolution_setting.py index ce1f8a975d..9424b24380 100644 --- a/openpype/hosts/max/plugins/publish/validate_resolution_setting.py +++ b/openpype/hosts/max/plugins/publish/validate_resolution_setting.py @@ -47,16 +47,13 @@ class ValidateResolutionSetting(pyblish.api.InstancePlugin, asset_resolution_data = asset_resolution["data"] # Set project resolution project_width = int( - project_resolution_data.get("resolutionWidth", 1920) - ) + project_resolution_data.get("resolutionWidth", 1920)) project_height = int( project_resolution_data.get("resolutionHeight", 1080)) width = int( - asset_resolution_data.get("resolutionWidth", project_width) - ) + asset_resolution_data.get("resolutionWidth", project_width)) height = int( - asset_resolution_data.get("resolutionHeight", project_height) - ) + asset_resolution_data.get("resolutionHeight", project_height)) return width, height From eb78b8359b31ded4a4f3339b7d983599df667aff Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 28 Apr 2023 17:47:09 +0800 Subject: [PATCH 10/52] add validators and change to take the frame range from the render setting --- openpype/hosts/max/api/lib.py | 8 ++- openpype/hosts/max/api/lib_renderproducts.py | 5 +- .../max/plugins/publish/collect_render.py | 4 +- .../plugins/publish/validate_frame_range.py | 61 +++++++++++++++++++ .../publish/validate_frame_range_type.py | 32 ++++++++++ 5 files changed, 103 insertions(+), 7 deletions(-) create mode 100644 openpype/hosts/max/plugins/publish/validate_frame_range.py create mode 100644 openpype/hosts/max/plugins/publish/validate_frame_range_type.py diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index ad9a450cad..7d629922fc 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -150,10 +150,10 @@ def set_framerange(start_frame, end_frame): Todo: Current type is hard-coded, there should be a custom setting for this. """ - rt.rendTimeType = 4 + rt.rendTimeType = 3 if start_frame is not None and end_frame is not None: - frame_range = "{0}-{1}".format(start_frame, end_frame) - rt.rendPickupFrames = frame_range + rt.rendStart = int(start_frame) + rt.rendEnd = int(end_frame) def get_multipass_setting(project_setting=None): @@ -243,6 +243,7 @@ def reset_frame_range(fps: bool = True): frame_end = frame_range["frameEnd"] + int(frame_range["handleEnd"]) frange_cmd = f"animationRange = interval {frame_start} {frame_end}" rt.execute(frange_cmd) + set_framerange(frame_start, frame_end) def set_context_setting(): @@ -259,6 +260,7 @@ def set_context_setting(): None """ reset_scene_resolution() + reset_frame_range() def get_max_version(): diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 350eb97661..8224d589ad 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -36,8 +36,9 @@ class RenderProducts(object): container) context = get_current_project_asset() - startFrame = context["data"].get("frameStart") - endFrame = context["data"].get("frameEnd") + 1 + # TODO: change the frame range follows the current render setting + startFrame = int(rt.rendStart) + endFrame = int(rt.rendEnd) + 1 img_fmt = self._project_settings["max"]["RenderSettings"]["image_format"] # noqa full_render_list = self.beauty_render_product(output_file, diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index b040467522..9d93a40021 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -59,8 +59,8 @@ class CollectRender(pyblish.api.InstancePlugin): "source": filepath, "expectedFiles": render_layer_files, "plugin": "3dsmax", - "frameStart": context.data['frameStart'], - "frameEnd": context.data['frameEnd'], + "frameStart": int(rt.rendStart), + "frameEnd": int(rt.rendEnd), "version": version_int, "farm": True } diff --git a/openpype/hosts/max/plugins/publish/validate_frame_range.py b/openpype/hosts/max/plugins/publish/validate_frame_range.py new file mode 100644 index 0000000000..2e1d7c9177 --- /dev/null +++ b/openpype/hosts/max/plugins/publish/validate_frame_range.py @@ -0,0 +1,61 @@ +import pyblish.api + +from pymxs import runtime as rt +from openpype.pipeline import ( + OptionalPyblishPluginMixin +) +from openpype.pipeline.publish import ( + RepairAction, + ValidateContentsOrder, + PublishValidationError +) + + +class ValidateFrameRange(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): + """Validates the frame ranges. + + This is an optional validator checking if the frame range on instance + matches the frame range specified for the asset. + + It also validates render frame ranges of render layers. + + Repair action will change everything to match the asset frame range. + + This can be turned off by the artist to allow custom ranges. + """ + + label = "Validate Frame Range" + order = ValidateContentsOrder + families = ["maxrender"] + hosts = ["max"] + optional = True + actions = [RepairAction] + + def process(self, instance): + if not self.is_active(instance.data): + self.log.info("Skipping validation...") + return + context = instance.context + + frame_start = int(context.data.get("frameStart")) + frame_end = int(context.data.get("frameEnd")) + + inst_frame_start = int(instance.data.get("frameStart")) + inst_frame_end = int(instance.data.get("frameEnd")) + + + if frame_start != inst_frame_start: + raise PublishValidationError( + "startFrame on instance does not match" + " with startFrame from the context data") + + if frame_end != inst_frame_end: + raise PublishValidationError( + "endFrame on instance does not match" + " with endFrame from the context data") + + @classmethod + def repair(cls, instance): + rt.rendStart = instance.context.data.get("frameStart") + rt.rendEnd = instance.context.data.get("frameEnd") diff --git a/openpype/hosts/max/plugins/publish/validate_frame_range_type.py b/openpype/hosts/max/plugins/publish/validate_frame_range_type.py new file mode 100644 index 0000000000..2403595b3b --- /dev/null +++ b/openpype/hosts/max/plugins/publish/validate_frame_range_type.py @@ -0,0 +1,32 @@ +import pyblish.api + +from pymxs import runtime as rt +from openpype.pipeline.publish import ( + RepairAction, + ValidateContentsOrder, + PublishValidationError +) + + +class ValidateFrameRangeType(pyblish.api.InstancePlugin): + """ + Validates whether the User + specified Frame Range(Type 3) is used in render setting + + """ + + label = "Validate Render Frame Range Type" + order = ValidateContentsOrder + families = ["maxrender"] + hosts = ["max"] + actions = [RepairAction] + + def process(self, instance): + if rt.rendTimeType != 3: + raise PublishValidationError("Incorrect type of frame range" + " used in render setting..") + + @classmethod + def repair(cls, instance): + rt.renderTimeType = 3 + return instance From c54447372308ac7fda9d91aa16a0ced50480ad17 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 28 Apr 2023 17:59:36 +0800 Subject: [PATCH 11/52] set frame range validator to switch off by default --- openpype/hosts/max/plugins/publish/validate_frame_range.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/validate_frame_range.py b/openpype/hosts/max/plugins/publish/validate_frame_range.py index 2e1d7c9177..8d5d99197e 100644 --- a/openpype/hosts/max/plugins/publish/validate_frame_range.py +++ b/openpype/hosts/max/plugins/publish/validate_frame_range.py @@ -29,7 +29,7 @@ class ValidateFrameRange(pyblish.api.InstancePlugin, order = ValidateContentsOrder families = ["maxrender"] hosts = ["max"] - optional = True + optional = False actions = [RepairAction] def process(self, instance): From d4422d7ec57fc894bac76b4bf974945d1db3413d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 28 Apr 2023 18:00:12 +0800 Subject: [PATCH 12/52] cosmetic fix --- openpype/hosts/max/plugins/publish/validate_frame_range.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/validate_frame_range.py b/openpype/hosts/max/plugins/publish/validate_frame_range.py index 8d5d99197e..1fafacb8b0 100644 --- a/openpype/hosts/max/plugins/publish/validate_frame_range.py +++ b/openpype/hosts/max/plugins/publish/validate_frame_range.py @@ -44,7 +44,6 @@ class ValidateFrameRange(pyblish.api.InstancePlugin, inst_frame_start = int(instance.data.get("frameStart")) inst_frame_end = int(instance.data.get("frameEnd")) - if frame_start != inst_frame_start: raise PublishValidationError( "startFrame on instance does not match" From 13264ea11c7985f8c5c117f4dcc84ecc925c009b Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 28 Apr 2023 18:41:05 +0800 Subject: [PATCH 13/52] roy's comment --- openpype/hosts/max/api/lib_rendersettings.py | 4 ++-- openpype/hosts/max/plugins/publish/validate_frame_range.py | 6 ++++-- .../hosts/max/plugins/publish/validate_frame_range_type.py | 3 ++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index 4940265a23..82a25dfa29 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -6,7 +6,7 @@ from openpype.pipeline import legacy_io from openpype.pipeline.context_tools import get_current_project_asset from openpype.hosts.max.api.lib import ( - set_framerange, + set_render_frame_range, get_current_renderer, get_default_render_folder ) @@ -68,7 +68,7 @@ class RenderSettings(object): # Set Frame Range frame_start = context["data"].get("frame_start") frame_end = context["data"].get("frame_end") - set_framerange(frame_start, frame_end) + set_render_frame_range(frame_start, frame_end) # get the production render renderer_class = get_current_renderer() renderer = str(renderer_class).split(":")[0] diff --git a/openpype/hosts/max/plugins/publish/validate_frame_range.py b/openpype/hosts/max/plugins/publish/validate_frame_range.py index 1fafacb8b0..fc2782ded6 100644 --- a/openpype/hosts/max/plugins/publish/validate_frame_range.py +++ b/openpype/hosts/max/plugins/publish/validate_frame_range.py @@ -47,12 +47,14 @@ class ValidateFrameRange(pyblish.api.InstancePlugin, if frame_start != inst_frame_start: raise PublishValidationError( "startFrame on instance does not match" - " with startFrame from the context data") + " with startFrame from the context data" + " You can use repair action to fix it") if frame_end != inst_frame_end: raise PublishValidationError( "endFrame on instance does not match" - " with endFrame from the context data") + " with endFrame from the context data" + " You can use repair action to fix it") @classmethod def repair(cls, instance): diff --git a/openpype/hosts/max/plugins/publish/validate_frame_range_type.py b/openpype/hosts/max/plugins/publish/validate_frame_range_type.py index 2403595b3b..7bc23e5a70 100644 --- a/openpype/hosts/max/plugins/publish/validate_frame_range_type.py +++ b/openpype/hosts/max/plugins/publish/validate_frame_range_type.py @@ -24,7 +24,8 @@ class ValidateFrameRangeType(pyblish.api.InstancePlugin): def process(self, instance): if rt.rendTimeType != 3: raise PublishValidationError("Incorrect type of frame range" - " used in render setting..") + " used in render setting.." + "Repair action can help to fix it.") @classmethod def repair(cls, instance): From f3b1002f2641bcc122e24debee6b042cb05dee1e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 28 Apr 2023 19:02:17 +0800 Subject: [PATCH 14/52] style fix --- openpype/hosts/max/api/lib.py | 4 ++-- openpype/hosts/max/plugins/publish/validate_frame_range.py | 4 ++-- .../hosts/max/plugins/publish/validate_frame_range_type.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 7d629922fc..1673fc5ab8 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -138,7 +138,7 @@ def get_default_render_folder(project_setting=None): ["default_render_image_folder"]) -def set_framerange(start_frame, end_frame): +def set_render_frame_range(start_frame, end_frame): """ Note: Frame range can be specified in different types. Possible values are: @@ -243,7 +243,7 @@ def reset_frame_range(fps: bool = True): frame_end = frame_range["frameEnd"] + int(frame_range["handleEnd"]) frange_cmd = f"animationRange = interval {frame_start} {frame_end}" rt.execute(frange_cmd) - set_framerange(frame_start, frame_end) + set_render_frame_range(frame_start, frame_end) def set_context_setting(): diff --git a/openpype/hosts/max/plugins/publish/validate_frame_range.py b/openpype/hosts/max/plugins/publish/validate_frame_range.py index fc2782ded6..dc12eece39 100644 --- a/openpype/hosts/max/plugins/publish/validate_frame_range.py +++ b/openpype/hosts/max/plugins/publish/validate_frame_range.py @@ -47,13 +47,13 @@ class ValidateFrameRange(pyblish.api.InstancePlugin, if frame_start != inst_frame_start: raise PublishValidationError( "startFrame on instance does not match" - " with startFrame from the context data" + " with startFrame from the context data." " You can use repair action to fix it") if frame_end != inst_frame_end: raise PublishValidationError( "endFrame on instance does not match" - " with endFrame from the context data" + " with endFrame from the context data." " You can use repair action to fix it") @classmethod diff --git a/openpype/hosts/max/plugins/publish/validate_frame_range_type.py b/openpype/hosts/max/plugins/publish/validate_frame_range_type.py index 7bc23e5a70..944780f6fa 100644 --- a/openpype/hosts/max/plugins/publish/validate_frame_range_type.py +++ b/openpype/hosts/max/plugins/publish/validate_frame_range_type.py @@ -24,7 +24,7 @@ class ValidateFrameRangeType(pyblish.api.InstancePlugin): def process(self, instance): if rt.rendTimeType != 3: raise PublishValidationError("Incorrect type of frame range" - " used in render setting.." + " used in render setting." "Repair action can help to fix it.") @classmethod From 88e7b3386b70756ff0b3a81bfd6a33bcd062830a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 2 May 2023 11:22:10 +0800 Subject: [PATCH 15/52] cosmetic fix --- openpype/hosts/max/plugins/publish/validate_frame_range_type.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/validate_frame_range_type.py b/openpype/hosts/max/plugins/publish/validate_frame_range_type.py index 944780f6fa..d77b1503e0 100644 --- a/openpype/hosts/max/plugins/publish/validate_frame_range_type.py +++ b/openpype/hosts/max/plugins/publish/validate_frame_range_type.py @@ -25,7 +25,7 @@ class ValidateFrameRangeType(pyblish.api.InstancePlugin): if rt.rendTimeType != 3: raise PublishValidationError("Incorrect type of frame range" " used in render setting." - "Repair action can help to fix it.") + " Repair action can help to fix it.") @classmethod def repair(cls, instance): From 0d2a0f87230a78fedbf3e68e54868c3a5269ff06 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Tue, 2 May 2023 14:36:18 +0200 Subject: [PATCH 16/52] Feature: Remove and load inv action --- openpype/plugins/inventory/remove_and_load.py | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 openpype/plugins/inventory/remove_and_load.py diff --git a/openpype/plugins/inventory/remove_and_load.py b/openpype/plugins/inventory/remove_and_load.py new file mode 100644 index 0000000000..27ae1d4139 --- /dev/null +++ b/openpype/plugins/inventory/remove_and_load.py @@ -0,0 +1,44 @@ +from openpype.pipeline import InventoryAction +from openpype.pipeline.legacy_io import Session +from openpype.pipeline.load.plugins import discover_loader_plugins +from openpype.pipeline.load.utils import ( + get_loader_identifier, + remove_container, + load_container, +) +from openpype.client import get_representation_by_id + + +class RemoveAndLoad(InventoryAction): + """Delete inventory item and reload it.""" + + label = "Remove and load" + icon = "refresh" + + def process(self, containers): + for container in containers: + project_name = Session.get("AVALON_PROJECT") + + # Get loader + loader_name = container["loader"] + for plugin in discover_loader_plugins(project_name=project_name): + if get_loader_identifier(plugin) == loader_name: + loader = plugin + break + + assert ( + loader, + "Failed to get loader, can't remove and load container", + ) + + # Get representation + representation = get_representation_by_id( + project_name, container["representation"] + ) + assert representation, "Represenatation not found" + + # Remove container + remove_container(container) + + # Load container + load_container(loader, representation) From cffe72f0010f0128efd257bf2d6ec749f56958a8 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Tue, 2 May 2023 16:46:01 +0200 Subject: [PATCH 17/52] register inventory actions --- openpype/pipeline/context_tools.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/pipeline/context_tools.py b/openpype/pipeline/context_tools.py index dede2b8fce..ada78b989d 100644 --- a/openpype/pipeline/context_tools.py +++ b/openpype/pipeline/context_tools.py @@ -35,6 +35,7 @@ from . import ( register_inventory_action_path, register_creator_plugin_path, deregister_loader_plugin_path, + deregister_inventory_action_path, ) @@ -54,6 +55,7 @@ PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins") # Global plugin paths PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "load") +INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") def _get_modules_manager(): @@ -158,6 +160,7 @@ def install_openpype_plugins(project_name=None, host_name=None): pyblish.api.register_plugin_path(PUBLISH_PATH) pyblish.api.register_discovery_filter(filter_pyblish_plugins) register_loader_plugin_path(LOAD_PATH) + register_inventory_action_path(INVENTORY_PATH) if host_name is None: host_name = os.environ.get("AVALON_APP") @@ -223,6 +226,7 @@ def uninstall_host(): pyblish.api.deregister_plugin_path(PUBLISH_PATH) pyblish.api.deregister_discovery_filter(filter_pyblish_plugins) deregister_loader_plugin_path(LOAD_PATH) + deregister_inventory_action_path(INVENTORY_PATH) log.info("Global plug-ins unregistred") deregister_host() From dc34bcc776c14aba094d7d2bde0f58cbf53002d5 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Tue, 2 May 2023 17:08:39 +0200 Subject: [PATCH 18/52] pre rebase From 865ba2c97539396ac4a871733d1088c2e25661bb Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Tue, 2 May 2023 17:12:36 +0200 Subject: [PATCH 19/52] edited assertion --- openpype/plugins/inventory/remove_and_load.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/inventory/remove_and_load.py b/openpype/plugins/inventory/remove_and_load.py index 27ae1d4139..981722c065 100644 --- a/openpype/plugins/inventory/remove_and_load.py +++ b/openpype/plugins/inventory/remove_and_load.py @@ -27,9 +27,8 @@ class RemoveAndLoad(InventoryAction): break assert ( - loader, - "Failed to get loader, can't remove and load container", - ) + loader + ), "Failed to get loader, can't remove and load container" # Get representation representation = get_representation_by_id( From 7c2a1542145ba75808d7927507bfda45f5166810 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 3 May 2023 15:32:22 +0800 Subject: [PATCH 20/52] add settings to switch on/off the frame range validator --- .../plugins/publish/validate_frame_range.py | 2 +- .../publish/validate_frame_range_type.py | 33 ------------------- .../defaults/project_settings/max.json | 7 ++++ .../projects_schema/schema_project_max.json | 4 +++ .../schemas/schema_max_publish.json | 33 +++++++++++++++++++ 5 files changed, 45 insertions(+), 34 deletions(-) delete mode 100644 openpype/hosts/max/plugins/publish/validate_frame_range_type.py create mode 100644 openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json diff --git a/openpype/hosts/max/plugins/publish/validate_frame_range.py b/openpype/hosts/max/plugins/publish/validate_frame_range.py index dc12eece39..e07c6390c1 100644 --- a/openpype/hosts/max/plugins/publish/validate_frame_range.py +++ b/openpype/hosts/max/plugins/publish/validate_frame_range.py @@ -29,7 +29,7 @@ class ValidateFrameRange(pyblish.api.InstancePlugin, order = ValidateContentsOrder families = ["maxrender"] hosts = ["max"] - optional = False + optional = True actions = [RepairAction] def process(self, instance): diff --git a/openpype/hosts/max/plugins/publish/validate_frame_range_type.py b/openpype/hosts/max/plugins/publish/validate_frame_range_type.py deleted file mode 100644 index d77b1503e0..0000000000 --- a/openpype/hosts/max/plugins/publish/validate_frame_range_type.py +++ /dev/null @@ -1,33 +0,0 @@ -import pyblish.api - -from pymxs import runtime as rt -from openpype.pipeline.publish import ( - RepairAction, - ValidateContentsOrder, - PublishValidationError -) - - -class ValidateFrameRangeType(pyblish.api.InstancePlugin): - """ - Validates whether the User - specified Frame Range(Type 3) is used in render setting - - """ - - label = "Validate Render Frame Range Type" - order = ValidateContentsOrder - families = ["maxrender"] - hosts = ["max"] - actions = [RepairAction] - - def process(self, instance): - if rt.rendTimeType != 3: - raise PublishValidationError("Incorrect type of frame range" - " used in render setting." - " Repair action can help to fix it.") - - @classmethod - def repair(cls, instance): - rt.renderTimeType = 3 - return instance diff --git a/openpype/settings/defaults/project_settings/max.json b/openpype/settings/defaults/project_settings/max.json index d59cdf8c4a..a757e08ef5 100644 --- a/openpype/settings/defaults/project_settings/max.json +++ b/openpype/settings/defaults/project_settings/max.json @@ -19,5 +19,12 @@ "custFloats": "custFloats", "custVecs": "custVecs" } + }, + "publish": { + "ValidateFrameRange": { + "enabled": true, + "optional": true, + "active": true + } } } diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json index 4fba9aff0a..42506559d0 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json @@ -73,6 +73,10 @@ } } ] + }, + { + "type": "schema", + "name": "schema_max_publish" } ] } diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json new file mode 100644 index 0000000000..ea08c735a6 --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json @@ -0,0 +1,33 @@ +{ + "type": "dict", + "collapsible": true, + "key": "publish", + "label": "Publish plugins", + "children": [ + { + "type": "dict", + "collapsible": true, + "checkbox_key": "enabled", + "key": "ValidateFrameRange", + "label": "Validate Frame Range", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "boolean", + "key": "active", + "label": "Active" + } + ] + } + ] + } From 273eb747915722c29e2c88d123cac43977b3b575 Mon Sep 17 00:00:00 2001 From: Sharkitty <81646000+Sharkitty@users.noreply.github.com> Date: Wed, 3 May 2023 08:13:37 +0000 Subject: [PATCH 21/52] Update openpype/plugins/inventory/remove_and_load.py Fix typo Co-authored-by: Roy Nieterau --- openpype/plugins/inventory/remove_and_load.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/inventory/remove_and_load.py b/openpype/plugins/inventory/remove_and_load.py index 981722c065..9befaf8729 100644 --- a/openpype/plugins/inventory/remove_and_load.py +++ b/openpype/plugins/inventory/remove_and_load.py @@ -34,7 +34,7 @@ class RemoveAndLoad(InventoryAction): representation = get_representation_by_id( project_name, container["representation"] ) - assert representation, "Represenatation not found" + assert representation, "Representation not found" # Remove container remove_container(container) From e2da9a0552356688f2fe2e091ca5de0ae6a5b84c Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Wed, 3 May 2023 10:20:50 +0200 Subject: [PATCH 22/52] Using for else to raise loader not found error --- openpype/plugins/inventory/remove_and_load.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/plugins/inventory/remove_and_load.py b/openpype/plugins/inventory/remove_and_load.py index 9befaf8729..998be119d5 100644 --- a/openpype/plugins/inventory/remove_and_load.py +++ b/openpype/plugins/inventory/remove_and_load.py @@ -25,10 +25,10 @@ class RemoveAndLoad(InventoryAction): if get_loader_identifier(plugin) == loader_name: loader = plugin break - - assert ( - loader - ), "Failed to get loader, can't remove and load container" + else: + raise RuntimeError( + "Failed to get loader, can't remove and load container" + ) # Get representation representation = get_representation_by_id( From 9d3e15378b0109f332c99a21c2468012ff7d60c4 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 3 May 2023 22:31:43 +0800 Subject: [PATCH 23/52] repharse the actual msg for the artists --- .../max/plugins/publish/validate_resolution_setting.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_resolution_setting.py b/openpype/hosts/max/plugins/publish/validate_resolution_setting.py index 9424b24380..94cd093569 100644 --- a/openpype/hosts/max/plugins/publish/validate_resolution_setting.py +++ b/openpype/hosts/max/plugins/publish/validate_resolution_setting.py @@ -29,15 +29,15 @@ class ValidateResolutionSetting(pyblish.api.InstancePlugin, current_width = rt.renderwidth current_height = rt.renderHeight if current_width != width and current_height != height: - raise PublishValidationError("Resolution Setting" - " not aligned with DB") + raise PublishValidationError("Resolution Setting " + "not matching resolution set on asset or shot.") if current_width != width: raise PublishValidationError("Width in Resolution Setting " - "not aligned with DB") + "not matching resolution set on asset or shot.") if current_height != height: raise PublishValidationError("Height in Resolution Setting " - "not aligned with DB") + "not matching resolution set on asset or shot.") def get_db_resolution(self, instance): data = ["data.resolutionWidth", "data.resolutionHeight"] From d8e62093acaf92ca21df28240c420a8d7895f808 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 3 May 2023 22:33:13 +0800 Subject: [PATCH 24/52] hound fix --- .../max/plugins/publish/validate_resolution_setting.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_resolution_setting.py b/openpype/hosts/max/plugins/publish/validate_resolution_setting.py index 94cd093569..5fcb843b20 100644 --- a/openpype/hosts/max/plugins/publish/validate_resolution_setting.py +++ b/openpype/hosts/max/plugins/publish/validate_resolution_setting.py @@ -30,14 +30,17 @@ class ValidateResolutionSetting(pyblish.api.InstancePlugin, current_height = rt.renderHeight if current_width != width and current_height != height: raise PublishValidationError("Resolution Setting " - "not matching resolution set on asset or shot.") + "not matching resolution " + "set on asset or shot.") if current_width != width: raise PublishValidationError("Width in Resolution Setting " - "not matching resolution set on asset or shot.") + "not matching resolution set " + "on asset or shot.") if current_height != height: raise PublishValidationError("Height in Resolution Setting " - "not matching resolution set on asset or shot.") + "not matching resolution set " + "on asset or shot.") def get_db_resolution(self, instance): data = ["data.resolutionWidth", "data.resolutionHeight"] From feaa01eff56d9fbae19adf97edb5ef3f620ac1f5 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 5 May 2023 11:49:47 +0200 Subject: [PATCH 25/52] :fire: remove obsolete validator --- .../publish/validate_sequence_frames.py | 66 ------------------- 1 file changed, 66 deletions(-) delete mode 100644 openpype/plugins/publish/validate_sequence_frames.py diff --git a/openpype/plugins/publish/validate_sequence_frames.py b/openpype/plugins/publish/validate_sequence_frames.py deleted file mode 100644 index 239008ee21..0000000000 --- a/openpype/plugins/publish/validate_sequence_frames.py +++ /dev/null @@ -1,66 +0,0 @@ -import os -import re - -import clique -import pyblish.api - - -class ValidateSequenceFrames(pyblish.api.InstancePlugin): - """Ensure the sequence of frames is complete - - The files found in the folder are checked against the startFrame and - endFrame of the instance. If the first or last file is not - corresponding with the first or last frame it is flagged as invalid. - - Used regular expression pattern handles numbers in the file names - (eg "Main_beauty.v001.1001.exr", "Main_beauty_v001.1001.exr", - "Main_beauty.1001.1001.exr") but not numbers behind frames (eg. - "Main_beauty.1001.v001.exr") - """ - - order = pyblish.api.ValidatorOrder - label = "Validate Sequence Frames" - families = ["imagesequence", "render"] - hosts = ["shell", "unreal"] - - def process(self, instance): - representations = instance.data.get("representations") - if not representations: - return - for repr in representations: - repr_files = repr["files"] - if isinstance(repr_files, str): - continue - - ext = repr.get("ext") - if not ext: - _, ext = os.path.splitext(repr_files[0]) - elif not ext.startswith("."): - ext = ".{}".format(ext) - pattern = r"\D?(?P(?P0*)\d+){}$".format( - re.escape(ext)) - patterns = [pattern] - - collections, remainder = clique.assemble( - repr_files, minimum_items=1, patterns=patterns) - - assert not remainder, "Must not have remainder" - assert len(collections) == 1, "Must detect single collection" - collection = collections[0] - frames = list(collection.indexes) - - if instance.data.get("slate"): - # Slate is not part of the frame range - frames = frames[1:] - - current_range = (frames[0], frames[-1]) - - required_range = (instance.data["frameStart"], - instance.data["frameEnd"]) - - if current_range != required_range: - raise ValueError(f"Invalid frame range: {current_range} - " - f"expected: {required_range}") - - missing = collection.holes().indexes - assert not missing, "Missing frames: %s" % (missing,) From c0499c46b852c3ffe9983d17f739cabeee596963 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Fri, 5 May 2023 14:02:40 +0200 Subject: [PATCH 26/52] Changes based on suggestions --- openpype/plugins/inventory/remove_and_load.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/inventory/remove_and_load.py b/openpype/plugins/inventory/remove_and_load.py index 998be119d5..be24220c56 100644 --- a/openpype/plugins/inventory/remove_and_load.py +++ b/openpype/plugins/inventory/remove_and_load.py @@ -1,5 +1,5 @@ from openpype.pipeline import InventoryAction -from openpype.pipeline.legacy_io import Session +from openpype.pipeline import get_current_project_name from openpype.pipeline.load.plugins import discover_loader_plugins from openpype.pipeline.load.utils import ( get_loader_identifier, @@ -16,12 +16,13 @@ class RemoveAndLoad(InventoryAction): icon = "refresh" def process(self, containers): + project_name = get_current_project_name() for container in containers: - project_name = Session.get("AVALON_PROJECT") # Get loader loader_name = container["loader"] - for plugin in discover_loader_plugins(project_name=project_name): + loaders = discover_loader_plugins(project_name=project_name) + for plugin in loader: if get_loader_identifier(plugin) == loader_name: loader = plugin break From 43b71e4f1e2f8387cb9fba4b20e401a15b140e51 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Fri, 5 May 2023 14:24:46 +0200 Subject: [PATCH 27/52] Fixed wrong variable --- openpype/plugins/inventory/remove_and_load.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/inventory/remove_and_load.py b/openpype/plugins/inventory/remove_and_load.py index be24220c56..d465154187 100644 --- a/openpype/plugins/inventory/remove_and_load.py +++ b/openpype/plugins/inventory/remove_and_load.py @@ -22,7 +22,7 @@ class RemoveAndLoad(InventoryAction): # Get loader loader_name = container["loader"] loaders = discover_loader_plugins(project_name=project_name) - for plugin in loader: + for plugin in loaders: if get_loader_identifier(plugin) == loader_name: loader = plugin break From e08ff46bfa4edb8b526501b88ff2dcdc36538885 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 5 May 2023 17:22:14 +0200 Subject: [PATCH 28/52] adding settings for creators --- .../defaults/project_settings/fusion.json | 13 ++++++ .../schema_project_fusion.json | 44 +++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/openpype/settings/defaults/project_settings/fusion.json b/openpype/settings/defaults/project_settings/fusion.json index f974eebaca..d76ed82942 100644 --- a/openpype/settings/defaults/project_settings/fusion.json +++ b/openpype/settings/defaults/project_settings/fusion.json @@ -21,5 +21,18 @@ "copy_path": "~/.openpype/hosts/fusion/profiles", "copy_status": false, "force_sync": false + }, + "create": { + "CreateSaver": { + "temp_rendering_path_template": "{workdir}/renders/fusion/{subset}/{subset}..{ext}", + "default_variants": [ + "Main", + "Mask" + ], + "instance_attributes": [ + "reviewable", + "farm_rendering" + ] + } } } 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 464cf2c06d..7971c62300 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json @@ -68,6 +68,50 @@ "label": "Resync profile on each launch" } ] + }, + { + "type": "dict", + "collapsible": true, + "key": "create", + "label": "Creator plugins", + "children": [ + { + "type": "dict", + "collapsible": true, + "key": "CreateSaver", + "label": "Create 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" + } + ] + } + ] + } + ] } ] } From d637fed33de88d4048c40b56ecf3f5cb86aab654 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 5 May 2023 17:23:27 +0200 Subject: [PATCH 29/52] implementing settings also adding temp rendering path attribute with with support for tempates --- .../fusion/plugins/create/create_saver.py | 78 +++++++++++++++---- 1 file changed, 65 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/fusion/plugins/create/create_saver.py b/openpype/hosts/fusion/plugins/create/create_saver.py index cedc4029fa..fb6767d2cd 100644 --- a/openpype/hosts/fusion/plugins/create/create_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_saver.py @@ -1,4 +1,6 @@ +from copy import deepcopy import os +from pprint import pformat from openpype.hosts.fusion.api import ( get_current_comp, @@ -11,7 +13,7 @@ from openpype.lib import ( ) from openpype.pipeline import ( legacy_io, - Creator, + Creator as NewCreator, CreatedInstance, ) from openpype.client import ( @@ -19,7 +21,7 @@ from openpype.client import ( ) -class CreateSaver(Creator): +class CreateSaver(NewCreator): identifier = "io.openpype.creators.fusion.saver" label = "Render (saver)" name = "render" @@ -28,7 +30,15 @@ class CreateSaver(Creator): description = "Fusion Saver to generate image sequence" icon = "fa5.eye" - instance_attributes = ["reviewable"] + instance_attributes = [ + "reviewable" + ] + default_variants = [ + "Main", + "Mask" + ] + temp_rendering_path_template = ( + "{workdir}/renders/fusion/{subset}/{subset}..{ext}") def create(self, subset_name, instance_data, pre_create_data): # TODO: Add pre_create attributes to choose file format? @@ -125,17 +135,34 @@ class CreateSaver(Creator): original_subset = tool.GetData("openpype.subset") subset = data["subset"] if original_subset != subset: - # Subset change detected - # Update output filepath - workdir = os.path.normpath(legacy_io.Session["AVALON_WORKDIR"]) - filename = f"{subset}..exr" - filepath = os.path.join(workdir, "render", subset, filename) - tool["Clip"] = filepath + self._configure_saver_tool(data, tool, subset) - # Rename tool - if tool.Name != subset: - print(f"Renaming {tool.Name} -> {subset}") - tool.SetAttrs({"TOOLS_Name": subset}) + def _configure_saver_tool(self, data, tool, subset): + formatting_data = deepcopy(data) + self.log.warning(pformat(formatting_data)) + + # Subset change detected + workdir = os.path.normpath(legacy_io.Session["AVALON_WORKDIR"]) + formatting_data.update({ + "workdir": workdir, + "ext": "exr" + }) + + # build file path to render + filepath = self.temp_rendering_path_template.format( + **formatting_data) + + # create directory + if not os.path.isdir(os.path.dirname(filepath)): + self.log.warning("Path does not exist! I am creating it.") + os.makedirs(os.path.dirname(filepath)) + + tool["Clip"] = filepath + + # Rename tool + if tool.Name != subset: + print(f"Renaming {tool.Name} -> {subset}") + tool.SetAttrs({"TOOLS_Name": subset}) def _collect_unmanaged_saver(self, tool): # TODO: this should not be done this way - this should actually @@ -238,3 +265,28 @@ class CreateSaver(Creator): default=("reviewable" in self.instance_attributes), label="Review", ) + + def apply_settings( + self, + project_settings, + system_settings + ): + """Method called on initialization of plugin to apply settings.""" + + # plugin settings + plugin_settings = self._get_creator_settings(project_settings) + + # individual attributes + self.instance_attributes = plugin_settings.get( + "instance_attributes") or self.instance_attributes + self.default_variants = plugin_settings.get( + "default_variants") or self.default_variants + self.temp_rendering_path_template = ( + plugin_settings.get("temp_rendering_path_template") + or self.temp_rendering_path_template + ) + + def _get_creator_settings(self, project_settings, settings_key=None): + if not settings_key: + settings_key = self.__class__.__name__ + return project_settings["fusion"]["create"][settings_key] From c73772919a32f349ecb16229140338b58e466445 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 5 May 2023 17:42:38 +0200 Subject: [PATCH 30/52] fixing path slashes --- openpype/hosts/fusion/plugins/create/create_saver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/plugins/create/create_saver.py b/openpype/hosts/fusion/plugins/create/create_saver.py index fb6767d2cd..8bf364cf20 100644 --- a/openpype/hosts/fusion/plugins/create/create_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_saver.py @@ -144,7 +144,7 @@ class CreateSaver(NewCreator): # Subset change detected workdir = os.path.normpath(legacy_io.Session["AVALON_WORKDIR"]) formatting_data.update({ - "workdir": workdir, + "workdir": workdir.replace("\\", "/"), "ext": "exr" }) From 291432d49bcd92120904ad868a27e9981d010865 Mon Sep 17 00:00:00 2001 From: Sharkitty <81646000+Sharkitty@users.noreply.github.com> Date: Mon, 8 May 2023 16:01:57 +0000 Subject: [PATCH 31/52] Update openpype/plugins/inventory/remove_and_load.py Changed representation assertion into a warning with more info Co-authored-by: Roy Nieterau --- openpype/plugins/inventory/remove_and_load.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/inventory/remove_and_load.py b/openpype/plugins/inventory/remove_and_load.py index d465154187..015f80cd4e 100644 --- a/openpype/plugins/inventory/remove_and_load.py +++ b/openpype/plugins/inventory/remove_and_load.py @@ -35,7 +35,10 @@ class RemoveAndLoad(InventoryAction): representation = get_representation_by_id( project_name, container["representation"] ) - assert representation, "Representation not found" + if not representation: + self.log.warning( + "Skipping remove and load because representation id is not" + " found in database: '{}'".format(container["representation"]) # Remove container remove_container(container) From 9a2a25a957cf25ed7fc5f1bcf10c3d15dc6b505c Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Mon, 8 May 2023 18:05:18 +0200 Subject: [PATCH 32/52] Added continue statement + linting --- openpype/plugins/inventory/remove_and_load.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/inventory/remove_and_load.py b/openpype/plugins/inventory/remove_and_load.py index 015f80cd4e..062a44354b 100644 --- a/openpype/plugins/inventory/remove_and_load.py +++ b/openpype/plugins/inventory/remove_and_load.py @@ -18,7 +18,6 @@ class RemoveAndLoad(InventoryAction): def process(self, containers): project_name = get_current_project_name() for container in containers: - # Get loader loader_name = container["loader"] loaders = discover_loader_plugins(project_name=project_name) @@ -38,7 +37,11 @@ class RemoveAndLoad(InventoryAction): if not representation: self.log.warning( "Skipping remove and load because representation id is not" - " found in database: '{}'".format(container["representation"]) + " found in database: '{}'".format( + container["representation"] + ) + ) + continue # Remove container remove_container(container) From 55bebf86426cf467f6f4268882ec00c7b44234e2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 10 May 2023 12:33:38 +0200 Subject: [PATCH 33/52] pr comments --- openpype/hosts/fusion/plugins/create/create_saver.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/fusion/plugins/create/create_saver.py b/openpype/hosts/fusion/plugins/create/create_saver.py index 8bf364cf20..7378dec2b4 100644 --- a/openpype/hosts/fusion/plugins/create/create_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_saver.py @@ -1,6 +1,5 @@ from copy import deepcopy import os -from pprint import pformat from openpype.hosts.fusion.api import ( get_current_comp, @@ -139,7 +138,6 @@ class CreateSaver(NewCreator): def _configure_saver_tool(self, data, tool, subset): formatting_data = deepcopy(data) - self.log.warning(pformat(formatting_data)) # Subset change detected workdir = os.path.normpath(legacy_io.Session["AVALON_WORKDIR"]) @@ -274,7 +272,9 @@ class CreateSaver(NewCreator): """Method called on initialization of plugin to apply settings.""" # plugin settings - plugin_settings = self._get_creator_settings(project_settings) + plugin_settings = ( + project_settings["fusion"]["create"][self.__class__.__name__] + ) # individual attributes self.instance_attributes = plugin_settings.get( @@ -285,8 +285,3 @@ class CreateSaver(NewCreator): plugin_settings.get("temp_rendering_path_template") or self.temp_rendering_path_template ) - - def _get_creator_settings(self, project_settings, settings_key=None): - if not settings_key: - settings_key = self.__class__.__name__ - return project_settings["fusion"]["create"][settings_key] From 98d27fa5a403143e5910d343ccc62af8b5e8cbb1 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 10 May 2023 12:54:30 +0200 Subject: [PATCH 34/52] fusion: frame padding from anatomy templates --- .../hosts/fusion/plugins/create/create_saver.py | 16 +++++++++++++--- .../defaults/project_settings/fusion.json | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/fusion/plugins/create/create_saver.py b/openpype/hosts/fusion/plugins/create/create_saver.py index 7378dec2b4..fb05d13597 100644 --- a/openpype/hosts/fusion/plugins/create/create_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_saver.py @@ -5,6 +5,7 @@ from openpype.hosts.fusion.api import ( get_current_comp, comp_lock_and_undo_chunk, ) +from openpype.hosts.fusion.api.lib import get_frame_path from openpype.lib import ( BoolDef, @@ -14,6 +15,7 @@ from openpype.pipeline import ( legacy_io, Creator as NewCreator, CreatedInstance, + Anatomy ) from openpype.client import ( get_asset_by_name, @@ -37,7 +39,7 @@ class CreateSaver(NewCreator): "Mask" ] temp_rendering_path_template = ( - "{workdir}/renders/fusion/{subset}/{subset}..{ext}") + "{workdir}/renders/fusion/{subset}/{subset}.{frame}.{ext}") def create(self, subset_name, instance_data, pre_create_data): # TODO: Add pre_create attributes to choose file format? @@ -139,10 +141,17 @@ class CreateSaver(NewCreator): def _configure_saver_tool(self, data, tool, subset): formatting_data = deepcopy(data) + # get frame padding from anatomy templates + anatomy = Anatomy() + frame_padding = int( + anatomy.templates["render"].get("frame_padding", 4) + ) + # Subset change detected workdir = os.path.normpath(legacy_io.Session["AVALON_WORKDIR"]) formatting_data.update({ "workdir": workdir.replace("\\", "/"), + "frame": "0" * frame_padding, "ext": "exr" }) @@ -180,8 +189,9 @@ class CreateSaver(NewCreator): path = tool["Clip"][comp.TIME_UNDEFINED] fname = os.path.basename(path) - fname, _ext = os.path.splitext(fname) - variant = fname.rstrip(".") + head, _, _ = get_frame_path(fname) + + variant = head.rstrip(".") subset = self.get_subset_name( variant=variant, task_name=task, diff --git a/openpype/settings/defaults/project_settings/fusion.json b/openpype/settings/defaults/project_settings/fusion.json index d76ed82942..066fc3816a 100644 --- a/openpype/settings/defaults/project_settings/fusion.json +++ b/openpype/settings/defaults/project_settings/fusion.json @@ -24,7 +24,7 @@ }, "create": { "CreateSaver": { - "temp_rendering_path_template": "{workdir}/renders/fusion/{subset}/{subset}..{ext}", + "temp_rendering_path_template": "{workdir}/renders/fusion/{subset}/{subset}.{frame}.{ext}", "default_variants": [ "Main", "Mask" From a08f9176b0bc26d9a5f32968f8643966f59ad502 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 10 May 2023 12:55:30 +0200 Subject: [PATCH 35/52] fusion: removing path making during creation --- openpype/hosts/fusion/plugins/create/create_saver.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/openpype/hosts/fusion/plugins/create/create_saver.py b/openpype/hosts/fusion/plugins/create/create_saver.py index fb05d13597..64a99d07c1 100644 --- a/openpype/hosts/fusion/plugins/create/create_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_saver.py @@ -159,11 +159,6 @@ class CreateSaver(NewCreator): filepath = self.temp_rendering_path_template.format( **formatting_data) - # create directory - if not os.path.isdir(os.path.dirname(filepath)): - self.log.warning("Path does not exist! I am creating it.") - os.makedirs(os.path.dirname(filepath)) - tool["Clip"] = filepath # Rename tool From 95237c43c775f3d15b8475ffacac53fcc4d94002 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 10 May 2023 13:00:53 +0200 Subject: [PATCH 36/52] adding todo for later renaming --- openpype/hosts/fusion/plugins/create/create_saver.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/fusion/plugins/create/create_saver.py b/openpype/hosts/fusion/plugins/create/create_saver.py index 64a99d07c1..bb4615db17 100644 --- a/openpype/hosts/fusion/plugins/create/create_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_saver.py @@ -38,6 +38,8 @@ class CreateSaver(NewCreator): "Main", "Mask" ] + + # TODO: This should be renamed together with Nuke so it is aligned temp_rendering_path_template = ( "{workdir}/renders/fusion/{subset}/{subset}.{frame}.{ext}") From 1a48ddefe4a665997dc9e4bac066200721949d3c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 10 May 2023 15:37:19 +0200 Subject: [PATCH 37/52] normalizing path --- openpype/hosts/fusion/plugins/create/create_saver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/fusion/plugins/create/create_saver.py b/openpype/hosts/fusion/plugins/create/create_saver.py index bb4615db17..f924c30b0a 100644 --- a/openpype/hosts/fusion/plugins/create/create_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_saver.py @@ -152,7 +152,7 @@ class CreateSaver(NewCreator): # Subset change detected workdir = os.path.normpath(legacy_io.Session["AVALON_WORKDIR"]) formatting_data.update({ - "workdir": workdir.replace("\\", "/"), + "workdir": workdir, "frame": "0" * frame_padding, "ext": "exr" }) @@ -161,7 +161,7 @@ class CreateSaver(NewCreator): filepath = self.temp_rendering_path_template.format( **formatting_data) - tool["Clip"] = filepath + tool["Clip"] = os.path.normpath(filepath) # Rename tool if tool.Name != subset: From 27ac1b4590c2211548d94dfc5a26b902c3938c72 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 10 May 2023 16:16:50 +0200 Subject: [PATCH 38/52] Removing unmanaged compatibility This fixes issue https://github.com/ynput/OpenPype/pull/4943#pullrequestreview-1420557288 --- .../fusion/plugins/create/create_saver.py | 48 ++++++------------- 1 file changed, 14 insertions(+), 34 deletions(-) diff --git a/openpype/hosts/fusion/plugins/create/create_saver.py b/openpype/hosts/fusion/plugins/create/create_saver.py index f924c30b0a..27394ae15b 100644 --- a/openpype/hosts/fusion/plugins/create/create_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_saver.py @@ -91,7 +91,7 @@ class CreateSaver(NewCreator): for tool in tools: data = self.get_managed_tool_data(tool) if not data: - data = self._collect_unmanaged_saver(tool) + data = self._collect_saver(tool) # Add instance created_instance = CreatedInstance.from_existing(data, self) @@ -168,43 +168,23 @@ class CreateSaver(NewCreator): print(f"Renaming {tool.Name} -> {subset}") tool.SetAttrs({"TOOLS_Name": subset}) - def _collect_unmanaged_saver(self, tool): - # TODO: this should not be done this way - this should actually - # get the data as stored on the tool explicitly (however) - # that would disallow any 'regular saver' to be collected - # unless the instance data is stored on it to begin with - - print("Collecting unmanaged saver..") - comp = tool.Comp() - - # Allow regular non-managed savers to also be picked up - project = legacy_io.Session["AVALON_PROJECT"] - asset = legacy_io.Session["AVALON_ASSET"] - task = legacy_io.Session["AVALON_TASK"] - - asset_doc = get_asset_by_name(project_name=project, asset_name=asset) - - path = tool["Clip"][comp.TIME_UNDEFINED] - fname = os.path.basename(path) - head, _, _ = get_frame_path(fname) - - variant = head.rstrip(".") - subset = self.get_subset_name( - variant=variant, - task_name=task, - asset_doc=asset_doc, - project_name=project, - ) - + def _collect_saver(self, tool): + print("Collecting saver..") attrs = tool.GetAttrs() + + ctx_data = {} + keys = ["asset", "subset", "task", "variant"] + for key in keys: + ctx_data[key] = tool.GetData(f"openpype.{key}") + passthrough = attrs["TOOLB_PassThrough"] return { # Required data - "project": project, - "asset": asset, - "subset": subset, - "task": task, - "variant": variant, + "project": self.project_name, + "asset": ctx_data["asset"], + "subset": ctx_data["subset"], + "task": ctx_data["task"], + "variant": ctx_data["variant"], "active": not passthrough, "family": self.family, # Unique identifier for instance and this creator From dfea365474995dfbdffd85d296f3b8e61b3e4ffd Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 10 May 2023 16:38:17 +0200 Subject: [PATCH 39/52] moving instnance id so it is imprinted once created addressing issue form here https://github.com/ynput/OpenPype/pull/4943#issuecomment-1542241467 --- openpype/hosts/fusion/plugins/create/create_saver.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/fusion/plugins/create/create_saver.py b/openpype/hosts/fusion/plugins/create/create_saver.py index 27394ae15b..224ec0d48e 100644 --- a/openpype/hosts/fusion/plugins/create/create_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_saver.py @@ -44,6 +44,11 @@ class CreateSaver(NewCreator): "{workdir}/renders/fusion/{subset}/{subset}.{frame}.{ext}") def create(self, subset_name, instance_data, pre_create_data): + instance_data.update({ + "id": "pyblish.avalon.instance", + "subset": subset_name + }) + # TODO: Add pre_create attributes to choose file format? file_format = "OpenEXRFormat" @@ -52,7 +57,6 @@ class CreateSaver(NewCreator): args = (-32768, -32768) # Magical position numbers saver = comp.AddTool("Saver", *args) - instance_data["subset"] = subset_name self._update_tool_with_data(saver, data=instance_data) saver["OutputFormat"] = file_format @@ -173,7 +177,7 @@ class CreateSaver(NewCreator): attrs = tool.GetAttrs() ctx_data = {} - keys = ["asset", "subset", "task", "variant"] + keys = ["id", "asset", "subset", "task", "variant"] for key in keys: ctx_data[key] = tool.GetData(f"openpype.{key}") @@ -188,7 +192,7 @@ class CreateSaver(NewCreator): "active": not passthrough, "family": self.family, # Unique identifier for instance and this creator - "id": "pyblish.avalon.instance", + "id": ctx_data["id"], "creator_identifier": self.identifier, } From f5215f323b3c3b8b76ef278de6771bfdf6b0dcca Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 10 May 2023 16:39:29 +0200 Subject: [PATCH 40/52] hound --- openpype/hosts/fusion/plugins/create/create_saver.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/fusion/plugins/create/create_saver.py b/openpype/hosts/fusion/plugins/create/create_saver.py index 224ec0d48e..ecdad30b4c 100644 --- a/openpype/hosts/fusion/plugins/create/create_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_saver.py @@ -5,7 +5,6 @@ from openpype.hosts.fusion.api import ( get_current_comp, comp_lock_and_undo_chunk, ) -from openpype.hosts.fusion.api.lib import get_frame_path from openpype.lib import ( BoolDef, @@ -17,9 +16,6 @@ from openpype.pipeline import ( CreatedInstance, Anatomy ) -from openpype.client import ( - get_asset_by_name, -) class CreateSaver(NewCreator): @@ -173,14 +169,11 @@ class CreateSaver(NewCreator): tool.SetAttrs({"TOOLS_Name": subset}) def _collect_saver(self, tool): - print("Collecting saver..") + self.log.info("Collecting saver..") attrs = tool.GetAttrs() - ctx_data = {} keys = ["id", "asset", "subset", "task", "variant"] - for key in keys: - ctx_data[key] = tool.GetData(f"openpype.{key}") - + ctx_data = {key: tool.GetData(f"openpype.{key}") for key in keys} passthrough = attrs["TOOLB_PassThrough"] return { # Required data From 4095c7bd008923f288c8b16f5ce94e0c0aacca99 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 10 May 2023 22:51:52 +0800 Subject: [PATCH 41/52] use rt.rendpickupframe and ondrej's comment --- openpype/hosts/max/api/lib.py | 6 ++--- .../max/plugins/publish/collect_render.py | 8 +++--- .../plugins/publish/validate_frame_range.py | 26 +++++++++++-------- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 1673fc5ab8..b21ce0f789 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -150,10 +150,10 @@ def set_render_frame_range(start_frame, end_frame): Todo: Current type is hard-coded, there should be a custom setting for this. """ - rt.rendTimeType = 3 + rt.rendTimeType = 4 if start_frame is not None and end_frame is not None: - rt.rendStart = int(start_frame) - rt.rendEnd = int(end_frame) + frame_range = "{0}-{1}".format(start_frame, end_frame) + rt.rendPickupFrames = frame_range def get_multipass_setting(project_setting=None): diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index 9d93a40021..2742c36fc8 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- """Collect Render""" import os +import re import pyblish.api from pymxs import runtime as rt @@ -46,7 +47,8 @@ class CollectRender(pyblish.api.InstancePlugin): self.log.debug(f"Setting {version_int} to context.") context.data["version"] = version_int - + pattern = r"^(?P-?[0-9]+)(?:(?:-)(?P-?[0-9]+))?$" + match = re.match(pattern, rt.rendPickupFrames) # setup the plugin as 3dsmax for the internal renderer data = { "subset": instance.name, @@ -59,8 +61,8 @@ class CollectRender(pyblish.api.InstancePlugin): "source": filepath, "expectedFiles": render_layer_files, "plugin": "3dsmax", - "frameStart": int(rt.rendStart), - "frameEnd": int(rt.rendEnd), + "frameStart": int(match.group("start")), + "frameEnd": int(match.group("end")), "version": version_int, "farm": True } diff --git a/openpype/hosts/max/plugins/publish/validate_frame_range.py b/openpype/hosts/max/plugins/publish/validate_frame_range.py index e07c6390c1..4cc9cb530c 100644 --- a/openpype/hosts/max/plugins/publish/validate_frame_range.py +++ b/openpype/hosts/max/plugins/publish/validate_frame_range.py @@ -44,19 +44,23 @@ class ValidateFrameRange(pyblish.api.InstancePlugin, inst_frame_start = int(instance.data.get("frameStart")) inst_frame_end = int(instance.data.get("frameEnd")) + errors = [] if frame_start != inst_frame_start: - raise PublishValidationError( - "startFrame on instance does not match" - " with startFrame from the context data." - " You can use repair action to fix it") - + errors.append( + f"Start frame ({inst_frame_start}) on instance does not match " + f"with the start frame ({frame_start}) set on the asset data. ") if frame_end != inst_frame_end: - raise PublishValidationError( - "endFrame on instance does not match" - " with endFrame from the context data." - " You can use repair action to fix it") + errors.append( + f"End frame ({inst_frame_end}) on instance does not match " + f"with the end frame ({frame_start}) from the asset data. ") + + if errors: + errors.append("You can use repair action to fix it.") + raise PublishValidationError("\n".join(errors)) @classmethod def repair(cls, instance): - rt.rendStart = instance.context.data.get("frameStart") - rt.rendEnd = instance.context.data.get("frameEnd") + start = instance.context.data.get("frameStart") + end = instance.context.data.get("frameEnd") + frame_range = "{0}-{1}".format(start, end) + rt.rendPickupFrames = frame_range From da7d8ac091d45d6d73a2d34177f2f7ab3992f9b0 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 10 May 2023 22:53:02 +0800 Subject: [PATCH 42/52] hound fix --- openpype/hosts/max/plugins/publish/validate_frame_range.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_frame_range.py b/openpype/hosts/max/plugins/publish/validate_frame_range.py index 4cc9cb530c..761e7bf085 100644 --- a/openpype/hosts/max/plugins/publish/validate_frame_range.py +++ b/openpype/hosts/max/plugins/publish/validate_frame_range.py @@ -47,8 +47,8 @@ class ValidateFrameRange(pyblish.api.InstancePlugin, errors = [] if frame_start != inst_frame_start: errors.append( - f"Start frame ({inst_frame_start}) on instance does not match " - f"with the start frame ({frame_start}) set on the asset data. ") + f"Start frame ({inst_frame_start}) on instance does not match " # noqa + f"with the start frame ({frame_start}) set on the asset data. ") # noqa if frame_end != inst_frame_end: errors.append( f"End frame ({inst_frame_end}) on instance does not match " From e69e1f76c8f86ab2cfc9ca7a9272b99d2ea98546 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 10 May 2023 23:16:39 +0800 Subject: [PATCH 43/52] use ranges --- openpype/hosts/max/api/lib.py | 6 +++--- openpype/hosts/max/plugins/publish/collect_render.py | 6 ++---- openpype/hosts/max/plugins/publish/validate_frame_range.py | 6 ++---- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index b21ce0f789..1673fc5ab8 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -150,10 +150,10 @@ def set_render_frame_range(start_frame, end_frame): Todo: Current type is hard-coded, there should be a custom setting for this. """ - rt.rendTimeType = 4 + rt.rendTimeType = 3 if start_frame is not None and end_frame is not None: - frame_range = "{0}-{1}".format(start_frame, end_frame) - rt.rendPickupFrames = frame_range + rt.rendStart = int(start_frame) + rt.rendEnd = int(end_frame) def get_multipass_setting(project_setting=None): diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index 2742c36fc8..31f1eba409 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -47,8 +47,6 @@ class CollectRender(pyblish.api.InstancePlugin): self.log.debug(f"Setting {version_int} to context.") context.data["version"] = version_int - pattern = r"^(?P-?[0-9]+)(?:(?:-)(?P-?[0-9]+))?$" - match = re.match(pattern, rt.rendPickupFrames) # setup the plugin as 3dsmax for the internal renderer data = { "subset": instance.name, @@ -61,8 +59,8 @@ class CollectRender(pyblish.api.InstancePlugin): "source": filepath, "expectedFiles": render_layer_files, "plugin": "3dsmax", - "frameStart": int(match.group("start")), - "frameEnd": int(match.group("end")), + "frameStart": int(rt.rendStart), + "frameEnd": int(rt.rendEnd), "version": version_int, "farm": True } diff --git a/openpype/hosts/max/plugins/publish/validate_frame_range.py b/openpype/hosts/max/plugins/publish/validate_frame_range.py index 761e7bf085..21e847405e 100644 --- a/openpype/hosts/max/plugins/publish/validate_frame_range.py +++ b/openpype/hosts/max/plugins/publish/validate_frame_range.py @@ -60,7 +60,5 @@ class ValidateFrameRange(pyblish.api.InstancePlugin, @classmethod def repair(cls, instance): - start = instance.context.data.get("frameStart") - end = instance.context.data.get("frameEnd") - frame_range = "{0}-{1}".format(start, end) - rt.rendPickupFrames = frame_range + rt.rendStart = instance.context.data.get("frameStart") + rt.rendEnd = instance.context.data.get("frameEnd") From 210ed4d41fdc8c28cb18e9a2d61b10b8848cbf26 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 10 May 2023 23:17:58 +0800 Subject: [PATCH 44/52] hound fix --- openpype/hosts/max/plugins/publish/collect_render.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index 31f1eba409..00e00a8eb5 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- """Collect Render""" import os -import re import pyblish.api from pymxs import runtime as rt From 9f1bdda8b35509907351cc12ac271299bb186c0b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 11 May 2023 12:18:36 +0200 Subject: [PATCH 45/52] fusion: removing obsolete code --- .../fusion/plugins/create/create_saver.py | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/openpype/hosts/fusion/plugins/create/create_saver.py b/openpype/hosts/fusion/plugins/create/create_saver.py index ecdad30b4c..2af811ef5b 100644 --- a/openpype/hosts/fusion/plugins/create/create_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_saver.py @@ -90,8 +90,6 @@ class CreateSaver(NewCreator): tools = comp.GetToolList(False, "Saver").values() for tool in tools: data = self.get_managed_tool_data(tool) - if not data: - data = self._collect_saver(tool) # Add instance created_instance = CreatedInstance.from_existing(data, self) @@ -168,27 +166,6 @@ class CreateSaver(NewCreator): print(f"Renaming {tool.Name} -> {subset}") tool.SetAttrs({"TOOLS_Name": subset}) - def _collect_saver(self, tool): - self.log.info("Collecting saver..") - attrs = tool.GetAttrs() - - keys = ["id", "asset", "subset", "task", "variant"] - ctx_data = {key: tool.GetData(f"openpype.{key}") for key in keys} - passthrough = attrs["TOOLB_PassThrough"] - return { - # Required data - "project": self.project_name, - "asset": ctx_data["asset"], - "subset": ctx_data["subset"], - "task": ctx_data["task"], - "variant": ctx_data["variant"], - "active": not passthrough, - "family": self.family, - # Unique identifier for instance and this creator - "id": ctx_data["id"], - "creator_identifier": self.identifier, - } - def get_managed_tool_data(self, tool): """Return data of the tool if it matches creator identifier""" data = tool.GetData("openpype") From bf2e02699a0b60af16b1a6057b4f82d9faa33373 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 11 May 2023 12:20:56 +0200 Subject: [PATCH 46/52] returning important condition --- openpype/hosts/fusion/plugins/create/create_saver.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/fusion/plugins/create/create_saver.py b/openpype/hosts/fusion/plugins/create/create_saver.py index 2af811ef5b..13836aa1a0 100644 --- a/openpype/hosts/fusion/plugins/create/create_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_saver.py @@ -90,6 +90,8 @@ class CreateSaver(NewCreator): 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) From d8b569b160167d491ab29367d00d0f504f40c0ab Mon Sep 17 00:00:00 2001 From: Sharkitty <81646000+Sharkitty@users.noreply.github.com> Date: Fri, 12 May 2023 14:14:44 +0000 Subject: [PATCH 47/52] Update openpype/plugins/inventory/remove_and_load.py Discover plugins only once Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/plugins/inventory/remove_and_load.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/inventory/remove_and_load.py b/openpype/plugins/inventory/remove_and_load.py index 062a44354b..d90cc9462c 100644 --- a/openpype/plugins/inventory/remove_and_load.py +++ b/openpype/plugins/inventory/remove_and_load.py @@ -17,10 +17,10 @@ class RemoveAndLoad(InventoryAction): def process(self, containers): project_name = get_current_project_name() + loaders = discover_loader_plugins(project_name=project_name) for container in containers: # Get loader loader_name = container["loader"] - loaders = discover_loader_plugins(project_name=project_name) for plugin in loaders: if get_loader_identifier(plugin) == loader_name: loader = plugin From 5ff9ab368b392e48dbc1c522a461d5b52c300b79 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Fri, 12 May 2023 17:07:52 +0200 Subject: [PATCH 48/52] pre cache loader by name, loader name in error --- openpype/plugins/inventory/remove_and_load.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/openpype/plugins/inventory/remove_and_load.py b/openpype/plugins/inventory/remove_and_load.py index d90cc9462c..ae66b95f6e 100644 --- a/openpype/plugins/inventory/remove_and_load.py +++ b/openpype/plugins/inventory/remove_and_load.py @@ -17,17 +17,18 @@ class RemoveAndLoad(InventoryAction): def process(self, containers): project_name = get_current_project_name() - loaders = discover_loader_plugins(project_name=project_name) + loaders_by_name = { + get_loader_identifier(plugin): plugin + for plugin in discover_loader_plugins(project_name=project_name) + } for container in containers: # Get loader loader_name = container["loader"] - for plugin in loaders: - if get_loader_identifier(plugin) == loader_name: - loader = plugin - break - else: + loader = loaders_by_name.get(loader_name, None) + if not loader: raise RuntimeError( - "Failed to get loader, can't remove and load container" + "Failed to get loader '{}', can't remove " + "and load container".format(loader_name) ) # Get representation From 927f92df87d3bae5d035c3ae4f857ac171e84dfa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 13 May 2023 03:26:01 +0000 Subject: [PATCH 49/52] 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 0d75b669d2..7d224aa73f 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.15.7-nightly.3 - 3.15.7-nightly.2 - 3.15.7-nightly.1 - 3.15.6 @@ -134,7 +135,6 @@ body: - 3.14.1-nightly.4 - 3.14.1-nightly.3 - 3.14.1-nightly.2 - - 3.14.1-nightly.1 validations: required: true - type: dropdown From 0845ba29dd4edd1872118a65fe72653b6e429531 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 15 May 2023 14:08:35 +0200 Subject: [PATCH 50/52] General: Project Anatomy on creators (#4962) * added project anatomy to create context * added project anatomy to create plugin --- openpype/pipeline/create/context.py | 19 ++++++++++++++++++- openpype/pipeline/create/creator_plugins.py | 16 +++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 382bbea05e..2fc0669732 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -23,7 +23,7 @@ from openpype.lib.attribute_definitions import ( get_default_values, ) from openpype.host import IPublishHost, IWorkfileHost -from openpype.pipeline import legacy_io +from openpype.pipeline import legacy_io, Anatomy from openpype.pipeline.plugin_discover import DiscoverResult from .creator_plugins import ( @@ -1383,6 +1383,8 @@ class CreateContext: self._current_task_name = None self._current_workfile_path = None + self._current_project_anatomy = None + self._host_is_valid = host_is_valid # Currently unused variable self.headless = headless @@ -1546,6 +1548,18 @@ class CreateContext: return self._current_workfile_path + def get_current_project_anatomy(self): + """Project anatomy for current project. + + Returns: + Anatomy: Anatomy object ready to be used. + """ + + if self._current_project_anatomy is None: + self._current_project_anatomy = Anatomy( + self._current_project_name) + return self._current_project_anatomy + @property def context_has_changed(self): """Host context has changed. @@ -1568,6 +1582,7 @@ class CreateContext: ) project_name = property(get_current_project_name) + project_anatomy = property(get_current_project_anatomy) @property def log(self): @@ -1680,6 +1695,8 @@ class CreateContext: self._current_task_name = task_name self._current_workfile_path = workfile_path + self._current_project_anatomy = None + def reset_plugins(self, discover_publish_plugins=True): """Reload plugins. diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index bd3fbaf78f..9e47e9cc12 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -231,10 +231,24 @@ class BaseCreator: @property def project_name(self): - """Family that plugin represents.""" + """Current project name. + + Returns: + str: Name of a project. + """ return self.create_context.project_name + @property + def project_anatomy(self): + """Current project anatomy. + + Returns: + Anatomy: Project anatomy object. + """ + + return self.create_context.project_anatomy + @property def host(self): return self.create_context.host From 40d4fea857202fcb4194376511f208d8db0f3655 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 15 May 2023 14:10:49 +0200 Subject: [PATCH 51/52] Maya: Validate shader name - OP-5903 (#4971) * Fix regex matching. * Add active setting * Update openpype/hosts/maya/plugins/publish/validate_shader_name.py --- .../plugins/publish/validate_shader_name.py | 20 +++++++++---------- .../defaults/project_settings/maya.json | 1 + .../schemas/schema_maya_publish.json | 5 +++++ 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_shader_name.py b/openpype/hosts/maya/plugins/publish/validate_shader_name.py index b3e51f011d..034db471da 100644 --- a/openpype/hosts/maya/plugins/publish/validate_shader_name.py +++ b/openpype/hosts/maya/plugins/publish/validate_shader_name.py @@ -50,7 +50,8 @@ class ValidateShaderName(pyblish.api.InstancePlugin): asset_name = instance.data.get("asset", None) # Check the number of connected shadingEngines per shape - r = re.compile(cls.regex) + regex_compile = re.compile(cls.regex) + error_message = "object {0} has invalid shader name {1}" for shape in shapes: shading_engines = cmds.listConnections(shape, destination=True, @@ -60,19 +61,18 @@ class ValidateShaderName(pyblish.api.InstancePlugin): ) for shader in shaders: - m = r.match(cls.regex, shader) + m = regex_compile.match(shader) if m is None: invalid.append(shape) - cls.log.error( - "object {0} has invalid shader name {1}".format(shape, - shader) - ) + cls.log.error(error_message.format(shape, shader)) else: - if 'asset' in r.groupindex: + if 'asset' in regex_compile.groupindex: if m.group('asset') != asset_name: invalid.append(shape) - cls.log.error(("object {0} has invalid " - "shader name {1}").format(shape, - shader)) + message = error_message + message += " with missing asset name \"{2}\"" + cls.log.error( + message.format(shape, shader, asset_name) + ) return invalid diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 72b330ce7a..a2a43eefb5 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -734,6 +734,7 @@ "ValidateShaderName": { "enabled": false, "optional": true, + "active": true, "regex": "(?P.*)_(.*)_SHD" }, "ValidateShadingEngine": { diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json index 346948c658..07c8d8715b 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json @@ -126,6 +126,11 @@ "key": "optional", "label": "Optional" }, + { + "type": "boolean", + "key": "active", + "label": "Active" + }, { "type": "label", "label": "Shader name regex can use named capture group asset to validate against current asset name.

Example:
^.*(?P=<asset>.+)_SHD

" From 628ecbe5e2e6fb2c11df0185726e60a168eba013 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Mon, 15 May 2023 17:58:12 +0200 Subject: [PATCH 52/52] :truck: move test file --- .../unreal}/plugins/publish/test_validate_sequence_frames.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/unit/openpype/{ => hosts/unreal}/plugins/publish/test_validate_sequence_frames.py (100%) diff --git a/tests/unit/openpype/plugins/publish/test_validate_sequence_frames.py b/tests/unit/openpype/hosts/unreal/plugins/publish/test_validate_sequence_frames.py similarity index 100% rename from tests/unit/openpype/plugins/publish/test_validate_sequence_frames.py rename to tests/unit/openpype/hosts/unreal/plugins/publish/test_validate_sequence_frames.py