From a644949a29b017f2d38c6697fd73c67e0d11f1c5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 29 Mar 2023 15:21:03 +0800 Subject: [PATCH 001/130] 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 002/130] 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 003/130] 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 6872e32c1eca3b739aed578b6aa059efbf2348bc Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 11 Apr 2023 21:45:16 +0200 Subject: [PATCH 004/130] Generate shelves only in UI mode + defer generation to avoid slow Houdini launch on Windows --- openpype/hosts/houdini/api/pipeline.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/api/pipeline.py b/openpype/hosts/houdini/api/pipeline.py index 45e2f8f87f..62a8fba55e 100644 --- a/openpype/hosts/houdini/api/pipeline.py +++ b/openpype/hosts/houdini/api/pipeline.py @@ -81,7 +81,13 @@ class HoudiniHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): # TODO: make sure this doesn't trigger when # opening with last workfile. _set_context_settings() - shelves.generate_shelves() + + if not IS_HEADLESS: + import hdefereval # noqa, hdefereval is only available in ui mode + # Defer generation of shelves due to issue on Windows where shelf + # initialization during start up delays Houdini UI by minutes + # making it extremely slow to launch. + hdefereval.executeDeferred(shelves.generate_shelves) def has_unsaved_changes(self): return hou.hipFile.hasUnsavedChanges() From db72ed2e634389e4f57ee555585efb1b934c3e32 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 18 Apr 2023 15:46:47 +0800 Subject: [PATCH 005/130] 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 006/130] 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 007/130] 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 008/130] 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 009/130] 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 010/130] 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 a1eff27bbfcb5e54e05061b644245c4eae9deaeb Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Fri, 28 Apr 2023 09:17:09 +0200 Subject: [PATCH 011/130] Added OptionalPyblishPluginMixin and is_active check --- .../plugins/publish/increment_current_file.py | 19 ++++++++++++++----- .../publish/validate_background_depth.py | 15 ++++++++++----- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/fusion/plugins/publish/increment_current_file.py b/openpype/hosts/fusion/plugins/publish/increment_current_file.py index 42891446f7..4facd61893 100644 --- a/openpype/hosts/fusion/plugins/publish/increment_current_file.py +++ b/openpype/hosts/fusion/plugins/publish/increment_current_file.py @@ -1,7 +1,10 @@ import pyblish.api +from openpype.pipeline import OptionalPyblishPluginMixin -class FusionIncrementCurrentFile(pyblish.api.ContextPlugin): +class FusionIncrementCurrentFile( + pyblish.api.ContextPlugin, OptionalPyblishPluginMixin +): """Increment the current file. Saves the current file with an increased version number. @@ -15,15 +18,21 @@ class FusionIncrementCurrentFile(pyblish.api.ContextPlugin): optional = True def process(self, context): + if not self.is_active(context.data): + return from openpype.lib import version_up from openpype.pipeline.publish import get_errored_plugins_from_context errored_plugins = get_errored_plugins_from_context(context) - if any(plugin.__name__ == "FusionSubmitDeadline" - for plugin in errored_plugins): - raise RuntimeError("Skipping incrementing current file because " - "submission to render farm failed.") + if any( + plugin.__name__ == "FusionSubmitDeadline" + for plugin in errored_plugins + ): + raise RuntimeError( + "Skipping incrementing current file because " + "submission to render farm failed." + ) comp = context.data.get("currentComp") assert comp, "Must have comp" diff --git a/openpype/hosts/fusion/plugins/publish/validate_background_depth.py b/openpype/hosts/fusion/plugins/publish/validate_background_depth.py index db2c4f0dd9..384f6c2979 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_background_depth.py +++ b/openpype/hosts/fusion/plugins/publish/validate_background_depth.py @@ -1,12 +1,14 @@ import pyblish.api -from openpype.pipeline.publish import RepairAction +from openpype.pipeline import publish, OptionalPyblishPluginMixin from openpype.pipeline import PublishValidationError from openpype.hosts.fusion.api.action import SelectInvalidAction -class ValidateBackgroundDepth(pyblish.api.InstancePlugin): +class ValidateBackgroundDepth( + pyblish.api.InstancePlugin, OptionalPyblishPluginMixin +): """Validate if all Background tool are set to float32 bit""" order = pyblish.api.ValidatorOrder @@ -15,11 +17,10 @@ class ValidateBackgroundDepth(pyblish.api.InstancePlugin): families = ["render"] optional = True - actions = [SelectInvalidAction, RepairAction] + actions = [SelectInvalidAction, publish.RepairAction] @classmethod def get_invalid(cls, instance): - context = instance.context comp = context.data.get("currentComp") assert comp, "Must have Comp object" @@ -31,12 +32,16 @@ class ValidateBackgroundDepth(pyblish.api.InstancePlugin): return [i for i in backgrounds if i.GetInput("Depth") != 4.0] def process(self, instance): + if not self.is_active(instance.data): + return + invalid = self.get_invalid(instance) if invalid: raise PublishValidationError( "Found {} Backgrounds tools which" " are not set to float32".format(len(invalid)), - title=self.label) + title=self.label, + ) @classmethod def repair(cls, instance): From 1cf5a9e6a726d68f995da56933edba7ebea00dea Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Fri, 28 Apr 2023 09:24:17 +0200 Subject: [PATCH 012/130] Cleaned up imports --- .../hosts/fusion/plugins/publish/increment_current_file.py | 1 + .../fusion/plugins/publish/validate_background_depth.py | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/fusion/plugins/publish/increment_current_file.py b/openpype/hosts/fusion/plugins/publish/increment_current_file.py index 4facd61893..938a0ed698 100644 --- a/openpype/hosts/fusion/plugins/publish/increment_current_file.py +++ b/openpype/hosts/fusion/plugins/publish/increment_current_file.py @@ -1,4 +1,5 @@ import pyblish.api + from openpype.pipeline import OptionalPyblishPluginMixin diff --git a/openpype/hosts/fusion/plugins/publish/validate_background_depth.py b/openpype/hosts/fusion/plugins/publish/validate_background_depth.py index 384f6c2979..6908889eb4 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_background_depth.py +++ b/openpype/hosts/fusion/plugins/publish/validate_background_depth.py @@ -1,7 +1,10 @@ import pyblish.api -from openpype.pipeline import publish, OptionalPyblishPluginMixin -from openpype.pipeline import PublishValidationError +from openpype.pipeline import ( + publish, + OptionalPyblishPluginMixin, + PublishValidationError, +) from openpype.hosts.fusion.api.action import SelectInvalidAction From d5b719b8c87ca2cee643b460436852e64bc5c912 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Fri, 28 Apr 2023 11:00:51 +0200 Subject: [PATCH 013/130] Made name more clear when showing up under `Context` in publisher --- openpype/hosts/fusion/plugins/publish/increment_current_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/plugins/publish/increment_current_file.py b/openpype/hosts/fusion/plugins/publish/increment_current_file.py index 938a0ed698..ed0fd0fbc8 100644 --- a/openpype/hosts/fusion/plugins/publish/increment_current_file.py +++ b/openpype/hosts/fusion/plugins/publish/increment_current_file.py @@ -12,7 +12,7 @@ class FusionIncrementCurrentFile( """ - label = "Increment current file" + label = "Increment workfile version" order = pyblish.api.IntegratorOrder + 9.0 hosts = ["fusion"] families = ["workfile"] From 4f5a85aee0d48738cfae3fda8acdf453d3e2556f Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Fri, 28 Apr 2023 11:01:26 +0200 Subject: [PATCH 014/130] Removed familiy so it can version up even if workfile isn't published --- openpype/hosts/fusion/plugins/publish/increment_current_file.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/fusion/plugins/publish/increment_current_file.py b/openpype/hosts/fusion/plugins/publish/increment_current_file.py index ed0fd0fbc8..de6f697073 100644 --- a/openpype/hosts/fusion/plugins/publish/increment_current_file.py +++ b/openpype/hosts/fusion/plugins/publish/increment_current_file.py @@ -15,7 +15,6 @@ class FusionIncrementCurrentFile( label = "Increment workfile version" order = pyblish.api.IntegratorOrder + 9.0 hosts = ["fusion"] - families = ["workfile"] optional = True def process(self, context): From eb78b8359b31ded4a4f3339b7d983599df667aff Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 28 Apr 2023 17:47:09 +0800 Subject: [PATCH 015/130] 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 016/130] 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 017/130] 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 018/130] 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 019/130] 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 020/130] 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 021/130] 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 022/130] 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 023/130] pre rebase From 865ba2c97539396ac4a871733d1088c2e25661bb Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Tue, 2 May 2023 17:12:36 +0200 Subject: [PATCH 024/130] 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 025/130] 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 026/130] 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 027/130] 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 028/130] 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 029/130] 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 64eea1dc4a58444afaa8a58e32e2b50d834144c7 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Wed, 3 May 2023 21:33:01 +0200 Subject: [PATCH 030/130] Updated raise error --- .../hosts/fusion/plugins/publish/increment_current_file.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/plugins/publish/increment_current_file.py b/openpype/hosts/fusion/plugins/publish/increment_current_file.py index de6f697073..08a65bf52d 100644 --- a/openpype/hosts/fusion/plugins/publish/increment_current_file.py +++ b/openpype/hosts/fusion/plugins/publish/increment_current_file.py @@ -1,6 +1,7 @@ import pyblish.api from openpype.pipeline import OptionalPyblishPluginMixin +from openpype.pipeline import KnownPublishError class FusionIncrementCurrentFile( @@ -29,7 +30,7 @@ class FusionIncrementCurrentFile( plugin.__name__ == "FusionSubmitDeadline" for plugin in errored_plugins ): - raise RuntimeError( + raise KnownPublishError( "Skipping incrementing current file because " "submission to render farm failed." ) From b9055d61af83760430cce647f7f2226b23b91bcf Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 4 May 2023 17:42:16 +0200 Subject: [PATCH 031/130] POC wip --- .../fusion/plugins/create/create_saver.py | 2 +- .../fusion/plugins/publish/collect_renders.py | 8 + .../plugins/publish/submit_fusion_deadline.py | 140 ++++++++++++++++-- 3 files changed, 133 insertions(+), 17 deletions(-) diff --git a/openpype/hosts/fusion/plugins/create/create_saver.py b/openpype/hosts/fusion/plugins/create/create_saver.py index cedc4029fa..a66d9b7e86 100644 --- a/openpype/hosts/fusion/plugins/create/create_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_saver.py @@ -28,7 +28,7 @@ class CreateSaver(Creator): description = "Fusion Saver to generate image sequence" icon = "fa5.eye" - instance_attributes = ["reviewable"] + instance_attributes = ["reviewable", "farm_rendering"] def create(self, subset_name, instance_data, pre_create_data): # TODO: Add pre_create attributes to choose file format? diff --git a/openpype/hosts/fusion/plugins/publish/collect_renders.py b/openpype/hosts/fusion/plugins/publish/collect_renders.py index 7f38e68447..b1c12c7393 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_renders.py +++ b/openpype/hosts/fusion/plugins/publish/collect_renders.py @@ -23,3 +23,11 @@ class CollectFusionRenders(pyblish.api.InstancePlugin): instance.data["families"].append( "{}.{}".format(family, render_target) ) + if render_target == "farm": + if "review" in instance.data["families"]: + instance.data["families"].remove("review") + + # Farm rendering + instance.data["transfer"] = False + instance.data["farm"] = True + self.log.info("Farm rendering ON ...") diff --git a/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py b/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py index 8570c759bc..2885d91d07 100644 --- a/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py @@ -1,15 +1,26 @@ import os import json import getpass +from pprint import pformat import requests import pyblish.api from openpype.pipeline import legacy_io +from openpype.pipeline.publish import ( + OpenPypePyblishPluginMixin +) +from openpype.lib import ( + BoolDef, + NumberDef +) -class FusionSubmitDeadline(pyblish.api.InstancePlugin): +class FusionSubmitDeadline( + pyblish.api.InstancePlugin, + OpenPypePyblishPluginMixin +): """Submit current Comp to Deadline Renders are submitted to a Deadline Web Service as @@ -17,12 +28,76 @@ class FusionSubmitDeadline(pyblish.api.InstancePlugin): """ - label = "Submit to Deadline" + label = "Submit Fusion to Deadline" order = pyblish.api.IntegratorOrder hosts = ["fusion"] - families = ["render.farm"] + families = ["render"] + targets = ["local"] + + # presets + priority = 50 + chunk_size = 1 + concurrent_tasks = 1 + group = "" + department = "" + limit_groups = {} + use_gpu = False + env_allowed_keys = [] + env_search_replace_values = {} + + @classmethod + def get_attribute_defs(cls): + return [ + NumberDef( + "priority", + label="Priority", + default=cls.priority, + decimals=0 + ), + NumberDef( + "chunk", + label="Frames Per Task", + default=cls.chunk_size, + decimals=0, + minimum=1, + maximum=1000 + ), + NumberDef( + "concurrency", + label="Concurrency", + default=cls.concurrent_tasks, + decimals=0, + minimum=1, + maximum=10 + ), + BoolDef( + "use_gpu", + default=cls.use_gpu, + label="Use GPU" + ), + BoolDef( + "suspend_publish", + default=False, + label="Suspend publish" + ) + ] def process(self, instance): + if not instance.data.get("farm"): + self.log.info("Skipping local instance.") + return + + attribute_values = self.get_attr_values_from_data( + instance.data) + + self.log.debug(pformat(attribute_values)) + + # add suspend_publish attributeValue to instance data + instance.data["suspend_publish"] = attribute_values[ + "suspend_publish"] + + instance.data["toBeRenderedOn"] = "deadline" + context = instance.context key = "__hasRun{}".format(self.__class__.__name__) @@ -33,24 +108,24 @@ class FusionSubmitDeadline(pyblish.api.InstancePlugin): from openpype.hosts.fusion.api.lib import get_frame_path - deadline_url = ( - context.data["system_settings"] - ["modules"] - ["deadline"] - ["DEADLINE_REST_URL"] - ) - assert deadline_url, "Requires DEADLINE_REST_URL" + # get default deadline webservice url from deadline module + deadline_url = instance.context.data["defaultDeadline"] + # if custom one is set in instance, use that + if instance.data.get("deadlineUrl"): + deadline_url = instance.data.get("deadlineUrl") + assert deadline_url, "Requires Deadline Webservice URL" # Collect all saver instances in context that are to be rendered saver_instances = [] - for instance in context[:]: - if not self.families[0] in instance.data.get("families"): + for instance in context: + if instance.data["family"] != "render": # Allow only saver family instances continue if not instance.data.get("publish", True): # Skip inactive instances continue + self.log.debug(instance.data["name"]) saver_instances.append(instance) @@ -58,11 +133,31 @@ class FusionSubmitDeadline(pyblish.api.InstancePlugin): raise RuntimeError("No instances found for Deadline submittion") fusion_version = int(context.data["fusionVersion"]) - filepath = context.data["currentFile"] - filename = os.path.basename(filepath) comment = context.data.get("comment", "") deadline_user = context.data.get("deadlineUser", getpass.getuser()) + script_path = context.data["currentFile"] + + for item in context: + if "workfile" in item.data["families"]: + msg = "Workfile (scene) must be published along" + assert item.data["publish"] is True, msg + + template_data = item.data.get("anatomyData") + rep = item.data.get("representations")[0].get("name") + template_data["representation"] = rep + template_data["ext"] = rep + template_data["comment"] = None + anatomy_filled = context.data["anatomy"].format(template_data) + template_filled = anatomy_filled["publish"]["path"] + script_path = os.path.normpath(template_filled) + + self.log.info( + "Using published scene for render {}".format(script_path) + ) + + filename = os.path.basename(script_path) + # Documentation for keys available at: # https://docs.thinkboxsoftware.com # /products/deadline/8.0/1_User%20Manual/manual @@ -73,11 +168,20 @@ class FusionSubmitDeadline(pyblish.api.InstancePlugin): "BatchName": filename, # Asset dependency to wait for at least the scene file to sync. - "AssetDependency0": filepath, + "AssetDependency0": script_path, # Job name, as seen in Monitor "Name": filename, + "Priority": attribute_values.get( + "priority", self.priority), + "ChunkSize": attribute_values.get( + "chunk", self.chunk_size), + "ConcurrentTasks": attribute_values.get( + "concurrency", + self.concurrent_tasks + ), + # User, as seen in Monitor "UserName": deadline_user, @@ -94,7 +198,7 @@ class FusionSubmitDeadline(pyblish.api.InstancePlugin): }, "PluginInfo": { # Input - "FlowFile": filepath, + "FlowFile": script_path, # Mandatory for Deadline "Version": str(fusion_version), @@ -109,6 +213,10 @@ class FusionSubmitDeadline(pyblish.api.InstancePlugin): # Proxy: higher numbers smaller images for faster test renders # 1 = no proxy quality "Proxy": 1, + + # using GPU by default + "UseGpu": attribute_values.get( + "use_gpu", self.use_gpu) }, # Mandatory for Deadline, may be empty From feaa01eff56d9fbae19adf97edb5ef3f620ac1f5 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 5 May 2023 11:49:47 +0200 Subject: [PATCH 032/130] :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 033/130] 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 034/130] 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 035/130] 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 036/130] 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 037/130] 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 038/130] 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 039/130] 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 1e26b177261a0dfe9ce5baf43ba43ecec22eb735 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 9 May 2023 17:20:36 +0200 Subject: [PATCH 040/130] Fusion: rewrite collecting renders --- .../publish/collect_expected_frames.py | 50 ----- .../plugins/publish/collect_fusion_version.py | 22 -- .../plugins/publish/collect_instances.py | 28 --- .../fusion/plugins/publish/collect_render.py | 206 ++++++++++++++++++ .../fusion/plugins/publish/collect_renders.py | 33 --- 5 files changed, 206 insertions(+), 133 deletions(-) delete mode 100644 openpype/hosts/fusion/plugins/publish/collect_expected_frames.py delete mode 100644 openpype/hosts/fusion/plugins/publish/collect_fusion_version.py create mode 100644 openpype/hosts/fusion/plugins/publish/collect_render.py delete mode 100644 openpype/hosts/fusion/plugins/publish/collect_renders.py diff --git a/openpype/hosts/fusion/plugins/publish/collect_expected_frames.py b/openpype/hosts/fusion/plugins/publish/collect_expected_frames.py deleted file mode 100644 index 0ba777629f..0000000000 --- a/openpype/hosts/fusion/plugins/publish/collect_expected_frames.py +++ /dev/null @@ -1,50 +0,0 @@ -import pyblish.api -from openpype.pipeline import publish -import os - - -class CollectFusionExpectedFrames( - pyblish.api.InstancePlugin, publish.ColormanagedPyblishPluginMixin -): - """Collect all frames needed to publish expected frames""" - - order = pyblish.api.CollectorOrder + 0.5 - label = "Collect Expected Frames" - hosts = ["fusion"] - families = ["render"] - - def process(self, instance): - context = instance.context - - frame_start = context.data["frameStartHandle"] - frame_end = context.data["frameEndHandle"] - path = instance.data["path"] - output_dir = instance.data["outputDir"] - - basename = os.path.basename(path) - head, ext = os.path.splitext(basename) - files = [ - f"{head}{str(frame).zfill(4)}{ext}" - for frame in range(frame_start, frame_end + 1) - ] - repre = { - "name": ext[1:], - "ext": ext[1:], - "frameStart": f"%0{len(str(frame_end))}d" % frame_start, - "files": files, - "stagingDir": output_dir, - } - - self.set_representation_colorspace( - representation=repre, - context=context, - ) - - # review representation - if instance.data.get("review", False): - repre["tags"] = ["review"] - - # add the repre to the instance - if "representations" not in instance.data: - instance.data["representations"] = [] - instance.data["representations"].append(repre) diff --git a/openpype/hosts/fusion/plugins/publish/collect_fusion_version.py b/openpype/hosts/fusion/plugins/publish/collect_fusion_version.py deleted file mode 100644 index 65d8386f33..0000000000 --- a/openpype/hosts/fusion/plugins/publish/collect_fusion_version.py +++ /dev/null @@ -1,22 +0,0 @@ -import pyblish.api - - -class CollectFusionVersion(pyblish.api.ContextPlugin): - """Collect current comp""" - - order = pyblish.api.CollectorOrder - label = "Collect Fusion Version" - hosts = ["fusion"] - - def process(self, context): - """Collect all image sequence tools""" - - comp = context.data.get("currentComp") - if not comp: - raise RuntimeError("No comp previously collected, unable to " - "retrieve Fusion version.") - - version = comp.GetApp().Version - context.data["fusionVersion"] = version - - self.log.info("Fusion version: %s" % version) diff --git a/openpype/hosts/fusion/plugins/publish/collect_instances.py b/openpype/hosts/fusion/plugins/publish/collect_instances.py index af227f03db..4608f79420 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_instances.py +++ b/openpype/hosts/fusion/plugins/publish/collect_instances.py @@ -49,31 +49,3 @@ class CollectInstanceData(pyblish.api.InstancePlugin): if instance.data.get("review", False): self.log.info("Adding review family..") instance.data["families"].append("review") - - if instance.data["family"] == "render": - # TODO: This should probably move into a collector of - # its own for the "render" family - from openpype.hosts.fusion.api.lib import get_frame_path - comp = context.data["currentComp"] - - # This is only the case for savers currently but not - # for workfile instances. So we assume saver here. - tool = instance.data["transientData"]["tool"] - path = tool["Clip"][comp.TIME_UNDEFINED] - - filename = os.path.basename(path) - head, padding, tail = get_frame_path(filename) - ext = os.path.splitext(path)[1] - assert tail == ext, ("Tail does not match %s" % ext) - - instance.data.update({ - "path": path, - "outputDir": os.path.dirname(path), - "ext": ext, # todo: should be redundant? - - # Backwards compatibility: embed tool in instance.data - "tool": tool - }) - - # Add tool itself as member - instance.append(tool) diff --git a/openpype/hosts/fusion/plugins/publish/collect_render.py b/openpype/hosts/fusion/plugins/publish/collect_render.py new file mode 100644 index 0000000000..87c1d952e8 --- /dev/null +++ b/openpype/hosts/fusion/plugins/publish/collect_render.py @@ -0,0 +1,206 @@ +import os +from pprint import pformat +import attr +import pyblish.api + +from openpype.pipeline import publish +from openpype.pipeline.publish import RenderInstance +from openpype.hosts.fusion.api.lib import get_frame_path + + +@attr.s +class FusionRenderInstance(RenderInstance): + # extend generic, composition name is needed + fps = attr.ib(default=None) + projectEntity = attr.ib(default=None) + stagingDir = attr.ib(default=None) + app_version = attr.ib(default=None) + toolSaver = attr.ib(default=None) + workfileComp = attr.ib(default=None) + publish_attributes = attr.ib(default={}) + + +class CollectFusionRender( + publish.AbstractCollectRender, + publish.ColormanagedPyblishPluginMixin +): + + order = pyblish.api.CollectorOrder + 0.09 + label = "Collect Fusion Render" + hosts = ["fusion"] + + def get_instances(self, context): + + comp = context.data.get("currentComp") + comp_frame_format_prefs = comp.GetPrefs("Comp.FrameFormat") + aspect_x = comp_frame_format_prefs.get("AspectX") + aspect_y = comp_frame_format_prefs.get("AspectY") + + instances = [] + instances_to_remove = [] + + current_file = context.data["currentFile"] + version = context.data["version"] + + project_entity = context.data["projectEntity"] + + for inst in context: + if not inst.data.get("active", True): + continue + + family = inst.data["family"] + if family not in ["render"]: + continue + + task_name = inst.data.get("task") # legacy + tool = inst.data["transientData"]["tool"] + + instance_families = inst.data.get("families", []) + subset_name = inst.data["subset"] + instance = FusionRenderInstance( + family="render", + toolSaver=tool, + workfileComp=comp, + families=instance_families, + version=version, + time="", + source=current_file, + label="{} - {}".format(subset_name, family), + subset=subset_name, + asset=inst.data["asset"], + task=task_name, + attachTo=False, + setMembers='', + publish=True, + name=subset_name, + resolutionWidth=comp_frame_format_prefs.get("Width"), + resolutionHeight=comp_frame_format_prefs.get("Height"), + pixelAspect=aspect_x / aspect_y, + tileRendering=False, + tilesX=0, + tilesY=0, + review="review" in instance_families, + frameStart=context.data["frameStart"], + frameEnd=context.data["frameEnd"], + handleStart=context.data["handleStart"], + handleEnd=context.data["handleEnd"], + frameStep=1, + fps=comp_frame_format_prefs.get("Rate"), + app_version=comp.GetApp().Version, + publish_attributes=inst.data.get("publish_attributes", {}) + ) + + render_target = inst.data["creator_attributes"]["render_target"] + self.log.debug("render_target: '{}'".format(render_target)) + + if render_target == "local": + # for local renders + self._instance_data_local_update( + project_entity, instance, f"render.{render_target}") + + if render_target == "frames": + self._instance_data_local_update( + project_entity, instance, f"render.{render_target}") + + if render_target == "farm": + fam = "render.farm" + if fam not in instance.families: + instance.families.append(fam) + instance.toBeRenderedOn = "deadline" + instance.farm = True # to skip integrate + if "review" in instance.families: + # to skip ExtractReview locally + instance.families.remove("review") + + instances.append(instance) + instances_to_remove.append(inst) + + for instance in instances_to_remove: + context.remove(instance) + + return instances + + def post_collecting_action(self): + for instance in self._context: + if "render.frames" in instance.data.get("families", []): + self._update_for_frames(instance) + self.log.debug(pformat(instance.data)) + + def get_expected_files(self, render_instance): + """ + Returns list of rendered files that should be created by + Deadline. These are not published directly, they are source + for later 'submit_publish_job'. + + Args: + render_instance (RenderInstance): to pull anatomy and parts used + in url + + Returns: + (list) of absolute urls to rendered file + """ + start = render_instance.frameStart - render_instance.handleStart + end = render_instance.frameEnd + render_instance.handleEnd + + path = ( + render_instance.toolSaver["Clip"] + [render_instance.workfileComp.TIME_UNDEFINED] + ) + output_dir = os.path.dirname(path) + render_instance.outputDir = output_dir + + basename = os.path.basename(path) + + head, padding, ext = get_frame_path(basename) + + expected_files = [] + for frame in range(start, end + 1): + expected_files.append( + os.path.join( + output_dir, + f"{head}{str(frame).zfill(padding)}{ext}" + ) + ) + + return expected_files + + def _update_for_frames(self, instance): + """Update old saved instances to current publishing format""" + + expected_files = instance.data["expectedFiles"] + + start = instance.data["frameStart"] - instance.data["handleStart"] + + path = expected_files[0] + basename = os.path.basename(path) + staging_dir = os.path.dirname(path) + _, padding, ext = get_frame_path(basename) + + repre = { + "name": ext[1:], + "ext": ext[1:], + "frameStart": f"%0{padding}d" % start, + "files": [os.path.basename(f) for f in expected_files], + "stagingDir": staging_dir, + } + + self.set_representation_colorspace( + representation=repre, + context=instance.context, + ) + + # review representation + if instance.data.get("review", False): + repre["tags"] = ["review"] + + # add the repre to the instance + if "representations" not in instance.data: + instance.data["representations"] = [] + instance.data["representations"].append(repre) + + return instance + + def _instance_data_local_update(self, project_entity, instance, family): + instance.projectEntity = project_entity + if family not in instance.families: + instance.families.append(family) diff --git a/openpype/hosts/fusion/plugins/publish/collect_renders.py b/openpype/hosts/fusion/plugins/publish/collect_renders.py deleted file mode 100644 index b1c12c7393..0000000000 --- a/openpype/hosts/fusion/plugins/publish/collect_renders.py +++ /dev/null @@ -1,33 +0,0 @@ -import pyblish.api - - -class CollectFusionRenders(pyblish.api.InstancePlugin): - """Collect current saver node's render Mode - - Options: - local (Render locally) - frames (Use existing frames) - - """ - - order = pyblish.api.CollectorOrder + 0.4 - label = "Collect Renders" - hosts = ["fusion"] - families = ["render"] - - def process(self, instance): - render_target = instance.data["render_target"] - family = instance.data["family"] - - # add targeted family to families - instance.data["families"].append( - "{}.{}".format(family, render_target) - ) - if render_target == "farm": - if "review" in instance.data["families"]: - instance.data["families"].remove("review") - - # Farm rendering - instance.data["transfer"] = False - instance.data["farm"] = True - self.log.info("Farm rendering ON ...") From 7013be47de335bcb9d118d60e67d234360839d8b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 9 May 2023 17:21:16 +0200 Subject: [PATCH 041/130] Fusion: refactor validators to work with new collected data --- .../publish/validate_create_folder_checked.py | 2 +- .../validate_expected_frames_existence.py | 27 ++++++------------- .../validate_filename_has_extension.py | 4 +-- .../publish/validate_saver_has_input.py | 2 +- .../publish/validate_saver_passthrough.py | 2 +- .../publish/validate_unique_subsets.py | 2 +- 6 files changed, 14 insertions(+), 25 deletions(-) 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 8a91f23578..82d34b0b5d 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_create_folder_checked.py +++ b/openpype/hosts/fusion/plugins/publish/validate_create_folder_checked.py @@ -21,7 +21,7 @@ class ValidateCreateFolderChecked(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): - tool = instance[0] + tool = instance.data["toolSaver"] create_dir = tool.GetInput("CreateDir") if create_dir == 0.0: cls.log.error( diff --git a/openpype/hosts/fusion/plugins/publish/validate_expected_frames_existence.py b/openpype/hosts/fusion/plugins/publish/validate_expected_frames_existence.py index c208b8ef15..befaae13be 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_expected_frames_existence.py +++ b/openpype/hosts/fusion/plugins/publish/validate_expected_frames_existence.py @@ -14,7 +14,7 @@ class ValidateLocalFramesExistence(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Validate Expected Frames Exists" - families = ["render"] + families = ["render.frames"] hosts = ["fusion"] actions = [RepairAction, SelectInvalidAction] @@ -23,25 +23,15 @@ class ValidateLocalFramesExistence(pyblish.api.InstancePlugin): if non_existing_frames is None: non_existing_frames = [] - if instance.data.get("render_target") == "frames": - tool = instance[0] + if "render.frames" in instance.data.get("families", []): + tool = instance.data["toolSaver"] - frame_start = instance.data["frameStart"] - frame_end = instance.data["frameEnd"] - path = instance.data["path"] - output_dir = instance.data["outputDir"] + expected_files = instance.data["expectedFiles"] - basename = os.path.basename(path) - head, ext = os.path.splitext(basename) - files = [ - f"{head}{str(frame).zfill(4)}{ext}" - for frame in range(frame_start, frame_end + 1) - ] - - for file in files: - if not os.path.exists(os.path.join(output_dir, file)): + for file in expected_files: + if not os.path.exists(file): cls.log.error( - f"Missing file: {os.path.join(output_dir, file)}" + f"Missing file: {file}" ) non_existing_frames.append(file) @@ -67,8 +57,7 @@ class ValidateLocalFramesExistence(pyblish.api.InstancePlugin): def repair(cls, instance): invalid = cls.get_invalid(instance) if invalid: - tool = invalid[0] - + tool = instance.data["toolSaver"] # Change render target to local to render locally tool.SetData("openpype.creator_attributes.render_target", "local") 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 bbba2dde6e..1bf603e5b1 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_filename_has_extension.py +++ b/openpype/hosts/fusion/plugins/publish/validate_filename_has_extension.py @@ -30,11 +30,11 @@ class ValidateFilenameHasExtension(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): - path = instance.data["path"] + path = instance.data["expectedFiles"][0] fname, ext = os.path.splitext(path) if not ext: - tool = instance[0] + tool = instance.data["toolSaver"] cls.log.error("%s has no extension specified" % tool.Name) return [tool] 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 e02125f531..b409608ec3 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_saver_has_input.py +++ b/openpype/hosts/fusion/plugins/publish/validate_saver_has_input.py @@ -20,7 +20,7 @@ class ValidateSaverHasInput(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): - saver = instance[0] + saver = instance.data["toolSaver"] if not saver.Input.GetConnectedOutput(): return [saver] diff --git a/openpype/hosts/fusion/plugins/publish/validate_saver_passthrough.py b/openpype/hosts/fusion/plugins/publish/validate_saver_passthrough.py index 56f2e7e6b8..677861a654 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_saver_passthrough.py +++ b/openpype/hosts/fusion/plugins/publish/validate_saver_passthrough.py @@ -37,7 +37,7 @@ class ValidateSaverPassthrough(pyblish.api.ContextPlugin): def is_invalid(self, instance): - saver = instance[0] + saver = instance.data["toolSaver"] attr = saver.GetAttrs() active = not attr["TOOLB_PassThrough"] diff --git a/openpype/hosts/fusion/plugins/publish/validate_unique_subsets.py b/openpype/hosts/fusion/plugins/publish/validate_unique_subsets.py index 5b6ceb2fdb..6a65182fae 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_unique_subsets.py +++ b/openpype/hosts/fusion/plugins/publish/validate_unique_subsets.py @@ -43,7 +43,7 @@ class ValidateUniqueSubsets(pyblish.api.ContextPlugin): invalid.extend(instances) # Return tools for the invalid instances so they can be selected - invalid = [instance.data["tool"] for instance in invalid] + invalid = [instance.data["toolSaver"] for instance in invalid] return invalid From b8e8ce66606d643e82bccf3d8864063581754867 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 9 May 2023 17:22:01 +0200 Subject: [PATCH 042/130] fusion: rewriting render local to work with new instance data also adding colorspace data to representation --- .../plugins/publish/extract_render_local.py | 52 +++++++++++++++++-- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/fusion/plugins/publish/extract_render_local.py b/openpype/hosts/fusion/plugins/publish/extract_render_local.py index 5a0140c525..c2e38884c7 100644 --- a/openpype/hosts/fusion/plugins/publish/extract_render_local.py +++ b/openpype/hosts/fusion/plugins/publish/extract_render_local.py @@ -1,8 +1,11 @@ +import os import logging import contextlib import pyblish.api -from openpype.hosts.fusion.api import comp_lock_and_undo_chunk +from openpype.pipeline import publish +from openpype.hosts.fusion.api import comp_lock_and_undo_chunk +from openpype.hosts.fusion.api.lib import get_frame_path log = logging.getLogger(__name__) @@ -38,7 +41,10 @@ def enabled_savers(comp, savers): saver.SetAttrs({"TOOLB_PassThrough": original_state}) -class FusionRenderLocal(pyblish.api.InstancePlugin): +class FusionRenderLocal( + pyblish.api.InstancePlugin, + publish.ColormanagedPyblishPluginMixin +): """Render the current Fusion composition locally.""" order = pyblish.api.ExtractorOrder - 0.2 @@ -52,6 +58,8 @@ class FusionRenderLocal(pyblish.api.InstancePlugin): # Start render self.render_once(context) + self._add_representation(instance) + # Log render status self.log.info( "Rendered '{nm}' for asset '{ast}' under the task '{tsk}'".format( @@ -71,11 +79,11 @@ class FusionRenderLocal(pyblish.api.InstancePlugin): savers_to_render = [ # Get the saver tool from the instance - instance[0] for instance in context if + instance.data["toolSaver"] for instance in context if # Only active instances instance.data.get("publish", True) and # Only render.local instances - "render.local" in instance.data["families"] + "render.local" in instance.data.get("families") ] if key not in context.data: @@ -107,3 +115,39 @@ class FusionRenderLocal(pyblish.api.InstancePlugin): if context.data[key] is False: raise RuntimeError("Comp render failed") + + def _add_representation(self, instance): + """Add representation to instance""" + + expected_files = instance.data["expectedFiles"] + + start = instance.data["frameStart"] - instance.data["handleStart"] + + path = expected_files[0] + _, padding, ext = get_frame_path(path) + + staging_dir = os.path.dirname(path) + + repre = { + "name": ext[1:], + "ext": ext[1:], + "frameStart": f"%0{padding}d" % start, + "files": [os.path.basename(f) for f in expected_files], + "stagingDir": staging_dir, + } + + self.set_representation_colorspace( + representation=repre, + context=instance.context, + ) + + # review representation + if instance.data.get("review", False): + repre["tags"] = ["review"] + + # add the repre to the instance + if "representations" not in instance.data: + instance.data["representations"] = [] + instance.data["representations"].append(repre) + + return instance From aace680fa19dee09dcb19d9692fd75ec4e6859ab Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 9 May 2023 17:22:31 +0200 Subject: [PATCH 043/130] fusion deadline, rewriting to new instance data --- .../plugins/publish/submit_fusion_deadline.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py b/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py index 2885d91d07..092c317ce3 100644 --- a/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py @@ -96,8 +96,6 @@ class FusionSubmitDeadline( instance.data["suspend_publish"] = attribute_values[ "suspend_publish"] - instance.data["toBeRenderedOn"] = "deadline" - context = instance.context key = "__hasRun{}".format(self.__class__.__name__) @@ -132,8 +130,7 @@ class FusionSubmitDeadline( if not saver_instances: raise RuntimeError("No instances found for Deadline submittion") - fusion_version = int(context.data["fusionVersion"]) - comment = context.data.get("comment", "") + comment = instance.data.get("comment", "") deadline_user = context.data.get("deadlineUser", getpass.getuser()) script_path = context.data["currentFile"] @@ -201,7 +198,7 @@ class FusionSubmitDeadline( "FlowFile": script_path, # Mandatory for Deadline - "Version": str(fusion_version), + "Version": str(instance.data["app_version"]), # Render in high quality "HighQuality": True, @@ -225,7 +222,9 @@ class FusionSubmitDeadline( # Enable going to rendered frames from Deadline Monitor for index, instance in enumerate(saver_instances): - head, padding, tail = get_frame_path(instance.data["path"]) + head, padding, tail = get_frame_path( + instance.data["expectedFiles"][0] + ) path = "{}{}{}".format(head, "#" * padding, tail) folder, filename = os.path.split(path) payload["JobInfo"]["OutputDirectory%d" % index] = folder From b09efa96756ecb02cd0c1ae8b1183c692ff38147 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 10 May 2023 11:18:30 +0200 Subject: [PATCH 044/130] fusion: storing asset frame attribute at comp openpype_instance data --- openpype/hosts/fusion/api/lib.py | 7 ++++ .../publish/collect_comp_frame_range.py | 38 ++++++++++++++----- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/fusion/api/lib.py b/openpype/hosts/fusion/api/lib.py index 40cc4d2963..8f7b29f0c4 100644 --- a/openpype/hosts/fusion/api/lib.py +++ b/openpype/hosts/fusion/api/lib.py @@ -51,6 +51,12 @@ def update_frame_range(start, end, comp=None, set_render_range=True, "COMPN_GlobalStart": start - handle_start, "COMPN_GlobalEnd": end + handle_end } + frame_data = { + "frameStart": start, + "frameEnd": end, + "handleStart": handle_start, + "handleEnd": handle_end + } # set frame range if set_render_range: @@ -61,6 +67,7 @@ def update_frame_range(start, end, comp=None, set_render_range=True, with comp_lock_and_undo_chunk(comp): comp.SetAttrs(attrs) + comp.SetData("openpype_instance", frame_data) def set_asset_framerange(): diff --git a/openpype/hosts/fusion/plugins/publish/collect_comp_frame_range.py b/openpype/hosts/fusion/plugins/publish/collect_comp_frame_range.py index fbd7606cd7..2db0002ee6 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_comp_frame_range.py +++ b/openpype/hosts/fusion/plugins/publish/collect_comp_frame_range.py @@ -9,14 +9,20 @@ def get_comp_render_range(comp): global_start = comp_attrs["COMPN_GlobalStart"] global_end = comp_attrs["COMPN_GlobalEnd"] + frame_data = comp.GetData("openpype_instance") + handle_start = frame_data.get("handleStart", 0) + handle_end = frame_data.get("handleEnd", 0) + frame_start = frame_data.get("frameStart", 0) + frame_end = frame_data.get("frameEnd", 0) + # Whenever render ranges are undefined fall back # to the comp's global start and end if start == -1000000000: - start = global_start + start = frame_start if end == -1000000000: - end = global_end + end = frame_end - return start, end, global_start, global_end + return start, end, global_start, global_end, handle_start, handle_end class CollectFusionCompFrameRanges(pyblish.api.ContextPlugin): @@ -34,10 +40,22 @@ class CollectFusionCompFrameRanges(pyblish.api.ContextPlugin): comp = context.data["currentComp"] # Store comp render ranges - start, end, global_start, global_end = get_comp_render_range(comp) - context.data["frameStart"] = int(start) - context.data["frameEnd"] = int(end) - context.data["frameStartHandle"] = int(global_start) - context.data["frameEndHandle"] = int(global_end) - context.data["handleStart"] = int(start) - int(global_start) - context.data["handleEnd"] = int(global_end) - int(end) + ( + start, end, + global_start, + global_end, + handle_start, + handle_end + ) = get_comp_render_range(comp) + + data = {} + data["frameStart"] = int(start) + data["frameEnd"] = int(end) + data["frameStartHandle"] = int(global_start) + data["frameEndHandle"] = int(global_end) + data["handleStart"] = int(handle_start) + data["handleEnd"] = int(handle_end) + + self.log.debug("_ data: {}".format(data)) + + context.data.update(data) From 903d873dbe253e92b3b805f798b21c374af0a661 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 10 May 2023 11:19:08 +0200 Subject: [PATCH 045/130] fusion: frame ranges taken from instance --- .../hosts/fusion/plugins/publish/collect_render.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/fusion/plugins/publish/collect_render.py b/openpype/hosts/fusion/plugins/publish/collect_render.py index 87c1d952e8..5adb8a13f0 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_render.py +++ b/openpype/hosts/fusion/plugins/publish/collect_render.py @@ -49,7 +49,7 @@ class CollectFusionRender( continue family = inst.data["family"] - if family not in ["render"]: + if family != "render": continue task_name = inst.data.get("task") # legacy @@ -80,10 +80,11 @@ class CollectFusionRender( tilesX=0, tilesY=0, review="review" in instance_families, - frameStart=context.data["frameStart"], - frameEnd=context.data["frameEnd"], - handleStart=context.data["handleStart"], - handleEnd=context.data["handleEnd"], + frameStart=inst.data["frameStart"], + frameEnd=inst.data["frameEnd"], + handleStart=inst.data["handleStart"], + handleEnd=inst.data["handleEnd"], + ignoreFrameHandleCheck=True, frameStep=1, fps=comp_frame_format_prefs.get("Rate"), app_version=comp.GetApp().Version, From 9cbbbe818ca7df153ea00f04a452705d9279efca Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 10 May 2023 11:19:40 +0200 Subject: [PATCH 046/130] deadline fusion: frame range taken from handles version --- .../deadline/plugins/publish/submit_fusion_deadline.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py b/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py index 092c317ce3..891bfde6c5 100644 --- a/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py @@ -128,7 +128,7 @@ class FusionSubmitDeadline( saver_instances.append(instance) if not saver_instances: - raise RuntimeError("No instances found for Deadline submittion") + raise RuntimeError("No instances found for Deadline submission") comment = instance.data.get("comment", "") deadline_user = context.data.get("deadlineUser", getpass.getuser()) @@ -187,8 +187,8 @@ class FusionSubmitDeadline( "Plugin": "Fusion", "Frames": "{start}-{end}".format( - start=int(context.data["frameStart"]), - end=int(context.data["frameEnd"]) + start=int(instance.data["frameStartHandle"]), + end=int(instance.data["frameEndHandle"]) ), "Comment": comment, From 6ea1ab1c9f3da8d06bea10bb3b2b8a3305139120 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 10 May 2023 11:20:09 +0200 Subject: [PATCH 047/130] deadline submitter settings for aov filter --- openpype/settings/defaults/project_settings/deadline.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index fdd70f1a44..3f114025f3 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -114,6 +114,9 @@ ], "max": [ ".*" + ], + "fusion": [ + ".*" ] } } From 55bebf86426cf467f6f4268882ec00c7b44234e2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 10 May 2023 12:33:38 +0200 Subject: [PATCH 048/130] 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 049/130] 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 050/130] 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 051/130] 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 052/130] 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 a42fbf5a47f8fcbeca35f6f50ee0fd091d45fe36 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 10 May 2023 14:51:45 +0100 Subject: [PATCH 053/130] Fix missing parameter when updating alembic staticmesh --- openpype/hosts/unreal/plugins/load/load_staticmesh_abc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/unreal/plugins/load/load_staticmesh_abc.py b/openpype/hosts/unreal/plugins/load/load_staticmesh_abc.py index c435b8843d..befc7b0ac9 100644 --- a/openpype/hosts/unreal/plugins/load/load_staticmesh_abc.py +++ b/openpype/hosts/unreal/plugins/load/load_staticmesh_abc.py @@ -135,7 +135,7 @@ class StaticMeshAlembicLoader(plugin.Loader): source_path = get_representation_path(representation) destination_path = container["namespace"] - task = self.get_task(source_path, destination_path, name, True) + task = self.get_task(source_path, destination_path, name, True, False) # do import fbx and replace existing data unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) From 27ac1b4590c2211548d94dfc5a26b902c3938c72 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 10 May 2023 16:16:50 +0200 Subject: [PATCH 054/130] 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 055/130] 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 056/130] 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 057/130] 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 058/130] 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 059/130] 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 060/130] 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 061/130] 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 062/130] 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 03d777503cc8da73cd46ad213d9b249c3b3f7aa3 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 11 May 2023 16:14:16 +0100 Subject: [PATCH 063/130] Remove render extractor --- .../unreal/plugins/publish/extract_render.py | 48 ------------------- 1 file changed, 48 deletions(-) delete mode 100644 openpype/hosts/unreal/plugins/publish/extract_render.py diff --git a/openpype/hosts/unreal/plugins/publish/extract_render.py b/openpype/hosts/unreal/plugins/publish/extract_render.py deleted file mode 100644 index 8ff38fbee0..0000000000 --- a/openpype/hosts/unreal/plugins/publish/extract_render.py +++ /dev/null @@ -1,48 +0,0 @@ -from pathlib import Path - -import unreal - -from openpype.pipeline import publish - - -class ExtractRender(publish.Extractor): - """Extract render.""" - - label = "Extract Render" - hosts = ["unreal"] - families = ["render"] - optional = True - - def process(self, instance): - # Define extract output file path - stagingdir = self.staging_dir(instance) - - # Perform extraction - self.log.info("Performing extraction..") - - # Get the render output directory - project_dir = unreal.Paths.project_dir() - render_dir = (f"{project_dir}/Saved/MovieRenders/" - f"{instance.data['subset']}") - - assert unreal.Paths.directory_exists(render_dir), \ - "Render directory does not exist" - - render_path = Path(render_dir) - - frames = [] - - for x in render_path.iterdir(): - if x.is_file() and x.suffix == '.png': - frames.append(str(x)) - - if "representations" not in instance.data: - instance.data["representations"] = [] - - render_representation = { - 'name': 'png', - 'ext': 'png', - 'files': frames, - "stagingDir": stagingdir, - } - instance.data["representations"].append(render_representation) From 9086511970927f662807e67db02e43628f1adce9 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 11 May 2023 16:14:41 +0100 Subject: [PATCH 064/130] Fix start and end frames to be int --- .../hosts/unreal/plugins/publish/collect_render_instances.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/unreal/plugins/publish/collect_render_instances.py b/openpype/hosts/unreal/plugins/publish/collect_render_instances.py index 6697a6b90d..a352b2c3f3 100644 --- a/openpype/hosts/unreal/plugins/publish/collect_render_instances.py +++ b/openpype/hosts/unreal/plugins/publish/collect_render_instances.py @@ -73,8 +73,8 @@ class CollectRenderInstances(pyblish.api.InstancePlugin): new_data["level"] = data.get("level") new_data["output"] = s.get('output') new_data["fps"] = seq.get_display_rate().numerator - new_data["frameStart"] = s.get('frame_range')[0] - new_data["frameEnd"] = s.get('frame_range')[1] + new_data["frameStart"] = int(s.get('frame_range')[0]) + new_data["frameEnd"] = int(s.get('frame_range')[1]) new_data["sequence"] = seq.get_path_name() new_data["master_sequence"] = data["master_sequence"] new_data["master_level"] = data["master_level"] From fbee0a8b3c295dc55cd64a3037f517f88de150fa Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 12 May 2023 11:07:50 +0200 Subject: [PATCH 065/130] pr comments --- .../fusion/plugins/publish/collect_render.py | 3 --- .../validate_expected_frames_existence.py | 23 +++++++++---------- .../plugins/publish/submit_fusion_deadline.py | 3 --- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/openpype/hosts/fusion/plugins/publish/collect_render.py b/openpype/hosts/fusion/plugins/publish/collect_render.py index 5adb8a13f0..4898226f03 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_render.py +++ b/openpype/hosts/fusion/plugins/publish/collect_render.py @@ -1,5 +1,4 @@ import os -from pprint import pformat import attr import pyblish.api @@ -92,7 +91,6 @@ class CollectFusionRender( ) render_target = inst.data["creator_attributes"]["render_target"] - self.log.debug("render_target: '{}'".format(render_target)) if render_target == "local": # for local renders @@ -125,7 +123,6 @@ class CollectFusionRender( for instance in self._context: if "render.frames" in instance.data.get("families", []): self._update_for_frames(instance) - self.log.debug(pformat(instance.data)) def get_expected_files(self, render_instance): """ diff --git a/openpype/hosts/fusion/plugins/publish/validate_expected_frames_existence.py b/openpype/hosts/fusion/plugins/publish/validate_expected_frames_existence.py index befaae13be..aa89799867 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_expected_frames_existence.py +++ b/openpype/hosts/fusion/plugins/publish/validate_expected_frames_existence.py @@ -23,21 +23,20 @@ class ValidateLocalFramesExistence(pyblish.api.InstancePlugin): if non_existing_frames is None: non_existing_frames = [] - if "render.frames" in instance.data.get("families", []): - tool = instance.data["toolSaver"] + tool = instance.data["toolSaver"] - expected_files = instance.data["expectedFiles"] + expected_files = instance.data["expectedFiles"] - for file in expected_files: - if not os.path.exists(file): - cls.log.error( - f"Missing file: {file}" - ) - non_existing_frames.append(file) + for file in expected_files: + if not os.path.exists(file): + cls.log.error( + f"Missing file: {file}" + ) + non_existing_frames.append(file) - if len(non_existing_frames) > 0: - cls.log.error(f"Some of {tool.Name}'s files does not exist") - return [tool] + if len(non_existing_frames) > 0: + cls.log.error(f"Some of {tool.Name}'s files does not exist") + return [tool] def process(self, instance): non_existing_frames = [] diff --git a/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py b/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py index 891bfde6c5..af4bd37302 100644 --- a/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py @@ -1,7 +1,6 @@ import os import json import getpass -from pprint import pformat import requests @@ -90,8 +89,6 @@ class FusionSubmitDeadline( attribute_values = self.get_attr_values_from_data( instance.data) - self.log.debug(pformat(attribute_values)) - # add suspend_publish attributeValue to instance data instance.data["suspend_publish"] = attribute_values[ "suspend_publish"] From 801b904aea357ac0523fd4b75f0f2e7bd8ac5df9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 12 May 2023 11:29:29 +0200 Subject: [PATCH 066/130] rewriting logic for frame ranges --- openpype/hosts/fusion/api/lib.py | 7 ---- .../fusion/plugins/create/create_saver.py | 3 ++ .../publish/collect_comp_frame_range.py | 26 ++++----------- .../plugins/publish/collect_instances.py | 32 +++++++++++++++---- 4 files changed, 36 insertions(+), 32 deletions(-) diff --git a/openpype/hosts/fusion/api/lib.py b/openpype/hosts/fusion/api/lib.py index 8f7b29f0c4..40cc4d2963 100644 --- a/openpype/hosts/fusion/api/lib.py +++ b/openpype/hosts/fusion/api/lib.py @@ -51,12 +51,6 @@ def update_frame_range(start, end, comp=None, set_render_range=True, "COMPN_GlobalStart": start - handle_start, "COMPN_GlobalEnd": end + handle_end } - frame_data = { - "frameStart": start, - "frameEnd": end, - "handleStart": handle_start, - "handleEnd": handle_end - } # set frame range if set_render_range: @@ -67,7 +61,6 @@ def update_frame_range(start, end, comp=None, set_render_range=True, with comp_lock_and_undo_chunk(comp): comp.SetAttrs(attrs) - comp.SetData("openpype_instance", frame_data) def set_asset_framerange(): diff --git a/openpype/hosts/fusion/plugins/create/create_saver.py b/openpype/hosts/fusion/plugins/create/create_saver.py index ecdad30b4c..58ffbe928a 100644 --- a/openpype/hosts/fusion/plugins/create/create_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_saver.py @@ -223,6 +223,9 @@ class CreateSaver(NewCreator): attr_defs = [ self._get_render_target_enum(), self._get_reviewable_bool(), + BoolDef( + "custom_range", label="Custom range", default=False, + ) ] return attr_defs diff --git a/openpype/hosts/fusion/plugins/publish/collect_comp_frame_range.py b/openpype/hosts/fusion/plugins/publish/collect_comp_frame_range.py index 2db0002ee6..38d6577667 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_comp_frame_range.py +++ b/openpype/hosts/fusion/plugins/publish/collect_comp_frame_range.py @@ -9,20 +9,14 @@ def get_comp_render_range(comp): global_start = comp_attrs["COMPN_GlobalStart"] global_end = comp_attrs["COMPN_GlobalEnd"] - frame_data = comp.GetData("openpype_instance") - handle_start = frame_data.get("handleStart", 0) - handle_end = frame_data.get("handleEnd", 0) - frame_start = frame_data.get("frameStart", 0) - frame_end = frame_data.get("frameEnd", 0) - # Whenever render ranges are undefined fall back # to the comp's global start and end if start == -1000000000: - start = frame_start + start = global_start if end == -1000000000: - end = frame_end + end = global_end - return start, end, global_start, global_end, handle_start, handle_end + return start, end, global_start, global_end class CollectFusionCompFrameRanges(pyblish.api.ContextPlugin): @@ -44,18 +38,12 @@ class CollectFusionCompFrameRanges(pyblish.api.ContextPlugin): start, end, global_start, global_end, - handle_start, - handle_end ) = get_comp_render_range(comp) data = {} - data["frameStart"] = int(start) - data["frameEnd"] = int(end) - data["frameStartHandle"] = int(global_start) - data["frameEndHandle"] = int(global_end) - data["handleStart"] = int(handle_start) - data["handleEnd"] = int(handle_end) - - self.log.debug("_ data: {}".format(data)) + data["compFrameStart"] = int(start) + data["compFrameEnd"] = int(end) + data["compFrameStartHandle"] = int(global_start) + data["compFrameEndHandle"] = int(global_end) context.data.update(data) diff --git a/openpype/hosts/fusion/plugins/publish/collect_instances.py b/openpype/hosts/fusion/plugins/publish/collect_instances.py index 4608f79420..9c27e3f027 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_instances.py +++ b/openpype/hosts/fusion/plugins/publish/collect_instances.py @@ -1,4 +1,6 @@ +from math import e import os +from turtle import st import pyblish.api @@ -24,6 +26,23 @@ class CollectInstanceData(pyblish.api.InstancePlugin): creator_attributes = instance.data["creator_attributes"] instance.data.update(creator_attributes) + # get asset frame ranges + start = context.data["frameStart"] + end = context.data["frameEnd"] + handle_start = context.data["handleStart"] + handle_end = context.data["handleEnd"] + start_handle = start - handle_start + end_handle = end + handle_end + + if creator_attributes["custom_range"]: + # get comp frame ranges + start = context.data["compFrameStart"] + end = context.data["compFrameEnd"] + handle_start = 0 + handle_end = 0 + start_handle = context.data["compFrameStartHandle"] + end_handle = context.data["compFrameEndHandle"] + # Include start and end render frame in label subset = instance.data["subset"] start = context.data["frameStart"] @@ -31,16 +50,17 @@ class CollectInstanceData(pyblish.api.InstancePlugin): label = "{subset} ({start}-{end})".format(subset=subset, start=int(start), end=int(end)) + instance.data.update({ "label": label, # todo: Allow custom frame range per instance - "frameStart": context.data["frameStart"], - "frameEnd": context.data["frameEnd"], - "frameStartHandle": context.data["frameStartHandle"], - "frameEndHandle": context.data["frameStartHandle"], - "handleStart": context.data["handleStart"], - "handleEnd": context.data["handleEnd"], + "frameStart": start, + "frameEnd": end, + "frameStartHandle": start_handle, + "frameEndHandle": end_handle, + "handleStart": handle_start, + "handleEnd": handle_end, "fps": context.data["fps"], }) From c80842f5a95aa88a6eaec0dd19b8326c89db760a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 12 May 2023 13:19:33 +0200 Subject: [PATCH 067/130] fusion: improving custom frame range --- .../fusion/plugins/create/create_saver.py | 21 +++++++++++++++++++ .../plugins/publish/collect_instances.py | 8 +++---- .../fusion/plugins/publish/collect_render.py | 2 +- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/fusion/plugins/create/create_saver.py b/openpype/hosts/fusion/plugins/create/create_saver.py index 58ffbe928a..860a873442 100644 --- a/openpype/hosts/fusion/plugins/create/create_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_saver.py @@ -40,6 +40,11 @@ class CreateSaver(NewCreator): "{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_data.update({ "id": "pyblish.avalon.instance", "subset": subset_name @@ -215,6 +220,9 @@ class CreateSaver(NewCreator): attr_defs = [ self._get_render_target_enum(), self._get_reviewable_bool(), + BoolDef( + "custom_range", label="Custom range", default=False, + ) ] return attr_defs @@ -229,6 +237,19 @@ class CreateSaver(NewCreator): ] return attr_defs + def pass_pre_attributes_to_instance( + self, + instance_data, + pre_create_data, + keys=None + ): + if not keys: + keys = pre_create_data.keys() + + creator_attrs = instance_data["creator_attributes"] = {} + for pass_key in 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 ? diff --git a/openpype/hosts/fusion/plugins/publish/collect_instances.py b/openpype/hosts/fusion/plugins/publish/collect_instances.py index 9c27e3f027..997bd66e4a 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_instances.py +++ b/openpype/hosts/fusion/plugins/publish/collect_instances.py @@ -34,19 +34,17 @@ class CollectInstanceData(pyblish.api.InstancePlugin): start_handle = start - handle_start end_handle = end + handle_end - if creator_attributes["custom_range"]: + if creator_attributes.get("custom_range"): # get comp frame ranges start = context.data["compFrameStart"] end = context.data["compFrameEnd"] handle_start = 0 handle_end = 0 - start_handle = context.data["compFrameStartHandle"] - end_handle = context.data["compFrameEndHandle"] + start_handle = start + end_handle = end # Include start and end render frame in label subset = instance.data["subset"] - start = context.data["frameStart"] - end = context.data["frameEnd"] label = "{subset} ({start}-{end})".format(subset=subset, start=int(start), end=int(end)) diff --git a/openpype/hosts/fusion/plugins/publish/collect_render.py b/openpype/hosts/fusion/plugins/publish/collect_render.py index 4898226f03..26355b24d3 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_render.py +++ b/openpype/hosts/fusion/plugins/publish/collect_render.py @@ -83,7 +83,7 @@ class CollectFusionRender( frameEnd=inst.data["frameEnd"], handleStart=inst.data["handleStart"], handleEnd=inst.data["handleEnd"], - ignoreFrameHandleCheck=True, + ignoreFrameHandleCheck=(not inst.data.get("custom_range")), frameStep=1, fps=comp_frame_format_prefs.get("Rate"), app_version=comp.GetApp().Version, From 78b0e3daa1922402b4e8d2068208959cf22fa793 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 12 May 2023 13:35:15 +0200 Subject: [PATCH 068/130] removing unusable attribute GPU --- .../plugins/publish/submit_fusion_deadline.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py b/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py index af4bd37302..d51299506c 100644 --- a/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py @@ -40,7 +40,6 @@ class FusionSubmitDeadline( group = "" department = "" limit_groups = {} - use_gpu = False env_allowed_keys = [] env_search_replace_values = {} @@ -69,11 +68,6 @@ class FusionSubmitDeadline( minimum=1, maximum=10 ), - BoolDef( - "use_gpu", - default=cls.use_gpu, - label="Use GPU" - ), BoolDef( "suspend_publish", default=False, @@ -206,11 +200,7 @@ class FusionSubmitDeadline( # Proxy: higher numbers smaller images for faster test renders # 1 = no proxy quality - "Proxy": 1, - - # using GPU by default - "UseGpu": attribute_values.get( - "use_gpu", self.use_gpu) + "Proxy": 1 }, # Mandatory for Deadline, may be empty From 5896c080132f4d62b3bc337ee586b40d7b4d991d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 12 May 2023 13:38:23 +0200 Subject: [PATCH 069/130] hound --- openpype/hosts/fusion/plugins/publish/collect_instances.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openpype/hosts/fusion/plugins/publish/collect_instances.py b/openpype/hosts/fusion/plugins/publish/collect_instances.py index 997bd66e4a..5a6a918730 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_instances.py +++ b/openpype/hosts/fusion/plugins/publish/collect_instances.py @@ -1,7 +1,3 @@ -from math import e -import os -from turtle import st - import pyblish.api From e56f4286c123149f3595920fcb1d550e0c1a8c38 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 12 May 2023 13:39:51 +0200 Subject: [PATCH 070/130] remove python syntax available since 3.8 from unreal addon --- openpype/hosts/unreal/addon.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/unreal/addon.py b/openpype/hosts/unreal/addon.py index db40d629bc..9ded333d7d 100644 --- a/openpype/hosts/unreal/addon.py +++ b/openpype/hosts/unreal/addon.py @@ -23,9 +23,10 @@ class UnrealAddon(OpenPypeModule, IHostAddon): UNREAL_ROOT_DIR, "integration", f"UE_{ue_version}", "Ayon" ) if not Path(unreal_plugin_path).exists(): - if compatible_versions := get_compatible_integration( + compatible_versions = get_compatible_integration( ue_version, Path(UNREAL_ROOT_DIR) / "integration" - ): + ) + if compatible_versions: unreal_plugin_path = compatible_versions[-1] / "Ayon" unreal_plugin_path = unreal_plugin_path.as_posix() From 233c7b34545f9c6cfec2e87eb41983099bc96849 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Fri, 12 May 2023 14:12:40 +0200 Subject: [PATCH 071/130] Update openpype/hosts/fusion/plugins/create/create_saver.py Co-authored-by: Roy Nieterau --- openpype/hosts/fusion/plugins/create/create_saver.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/fusion/plugins/create/create_saver.py b/openpype/hosts/fusion/plugins/create/create_saver.py index 860a873442..4993c882de 100644 --- a/openpype/hosts/fusion/plugins/create/create_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_saver.py @@ -174,7 +174,6 @@ class CreateSaver(NewCreator): tool.SetAttrs({"TOOLS_Name": subset}) def _collect_saver(self, tool): - self.log.info("Collecting saver..") attrs = tool.GetAttrs() keys = ["id", "asset", "subset", "task", "variant"] From 02279a51c8f97d0abf8e2d53d4d89acf1b13f1ce Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 12 May 2023 14:19:13 +0200 Subject: [PATCH 072/130] pr comments https://github.com/ynput/OpenPype/pull/4955#discussion_r1192264433 https://github.com/ynput/OpenPype/pull/4955#discussion_r1192267231 --- .../hosts/fusion/plugins/create/create_saver.py | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/fusion/plugins/create/create_saver.py b/openpype/hosts/fusion/plugins/create/create_saver.py index 4993c882de..caffb8e4a1 100644 --- a/openpype/hosts/fusion/plugins/create/create_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_saver.py @@ -227,26 +227,15 @@ class CreateSaver(NewCreator): def get_instance_attr_defs(self): """Settings for publish page""" - attr_defs = [ - self._get_render_target_enum(), - self._get_reviewable_bool(), - BoolDef( - "custom_range", label="Custom range", default=False, - ) - ] - return attr_defs + return self.get_pre_create_attr_defs() def pass_pre_attributes_to_instance( self, instance_data, - pre_create_data, - keys=None + pre_create_data ): - if not keys: - keys = pre_create_data.keys() - creator_attrs = instance_data["creator_attributes"] = {} - for pass_key in keys: + 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 From 425ddc7b2bb9399dd2d187761a4cc8755b000b79 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 12 May 2023 14:26:15 +0200 Subject: [PATCH 073/130] fusion: reversing toolSaver back to tool --- .../hosts/fusion/plugins/publish/collect_render.py | 14 ++++++++++---- .../fusion/plugins/publish/extract_render_local.py | 2 +- .../publish/validate_create_folder_checked.py | 2 +- .../publish/validate_expected_frames_existence.py | 4 ++-- .../publish/validate_filename_has_extension.py | 2 +- .../plugins/publish/validate_saver_has_input.py | 2 +- .../plugins/publish/validate_saver_passthrough.py | 2 +- .../plugins/publish/validate_unique_subsets.py | 2 +- 8 files changed, 18 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/fusion/plugins/publish/collect_render.py b/openpype/hosts/fusion/plugins/publish/collect_render.py index 26355b24d3..64d9aedc3b 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_render.py +++ b/openpype/hosts/fusion/plugins/publish/collect_render.py @@ -14,7 +14,7 @@ class FusionRenderInstance(RenderInstance): projectEntity = attr.ib(default=None) stagingDir = attr.ib(default=None) app_version = attr.ib(default=None) - toolSaver = attr.ib(default=None) + tool = attr.ib(default=None) workfileComp = attr.ib(default=None) publish_attributes = attr.ib(default={}) @@ -58,7 +58,7 @@ class CollectFusionRender( subset_name = inst.data["subset"] instance = FusionRenderInstance( family="render", - toolSaver=tool, + tool=tool, workfileComp=comp, families=instance_families, version=version, @@ -111,6 +111,8 @@ class CollectFusionRender( # to skip ExtractReview locally instance.families.remove("review") + # add new instance to the list and remove the original + # instance since it is not needed anymore instances.append(instance) instances_to_remove.append(inst) @@ -141,7 +143,7 @@ class CollectFusionRender( end = render_instance.frameEnd + render_instance.handleEnd path = ( - render_instance.toolSaver["Clip"] + render_instance.tool["Clip"] [render_instance.workfileComp.TIME_UNDEFINED] ) output_dir = os.path.dirname(path) @@ -163,7 +165,11 @@ class CollectFusionRender( return expected_files def _update_for_frames(self, instance): - """Update old saved instances to current publishing format""" + """Updating instance for render.frames family + + Adding representation data to the instance. Also setting + colorspaceData to the representation based on file rules. + """ expected_files = instance.data["expectedFiles"] diff --git a/openpype/hosts/fusion/plugins/publish/extract_render_local.py b/openpype/hosts/fusion/plugins/publish/extract_render_local.py index c2e38884c7..f093f7793f 100644 --- a/openpype/hosts/fusion/plugins/publish/extract_render_local.py +++ b/openpype/hosts/fusion/plugins/publish/extract_render_local.py @@ -79,7 +79,7 @@ class FusionRenderLocal( savers_to_render = [ # Get the saver tool from the instance - instance.data["toolSaver"] for instance in context if + instance.data["tool"] for instance in context if # Only active instances instance.data.get("publish", True) and # Only render.local instances 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 82d34b0b5d..35c92163eb 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_create_folder_checked.py +++ b/openpype/hosts/fusion/plugins/publish/validate_create_folder_checked.py @@ -21,7 +21,7 @@ class ValidateCreateFolderChecked(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): - tool = instance.data["toolSaver"] + tool = instance.data["tool"] create_dir = tool.GetInput("CreateDir") if create_dir == 0.0: cls.log.error( diff --git a/openpype/hosts/fusion/plugins/publish/validate_expected_frames_existence.py b/openpype/hosts/fusion/plugins/publish/validate_expected_frames_existence.py index aa89799867..3f84f59678 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_expected_frames_existence.py +++ b/openpype/hosts/fusion/plugins/publish/validate_expected_frames_existence.py @@ -23,7 +23,7 @@ class ValidateLocalFramesExistence(pyblish.api.InstancePlugin): if non_existing_frames is None: non_existing_frames = [] - tool = instance.data["toolSaver"] + tool = instance.data["tool"] expected_files = instance.data["expectedFiles"] @@ -56,7 +56,7 @@ class ValidateLocalFramesExistence(pyblish.api.InstancePlugin): def repair(cls, instance): invalid = cls.get_invalid(instance) if invalid: - tool = instance.data["toolSaver"] + tool = instance.data["tool"] # Change render target to local to render locally tool.SetData("openpype.creator_attributes.render_target", "local") 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 1bf603e5b1..537e43c875 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_filename_has_extension.py +++ b/openpype/hosts/fusion/plugins/publish/validate_filename_has_extension.py @@ -34,7 +34,7 @@ class ValidateFilenameHasExtension(pyblish.api.InstancePlugin): fname, ext = os.path.splitext(path) if not ext: - tool = instance.data["toolSaver"] + tool = instance.data["tool"] cls.log.error("%s has no extension specified" % tool.Name) return [tool] 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 b409608ec3..faf2102a8b 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_saver_has_input.py +++ b/openpype/hosts/fusion/plugins/publish/validate_saver_has_input.py @@ -20,7 +20,7 @@ class ValidateSaverHasInput(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): - saver = instance.data["toolSaver"] + saver = instance.data["tool"] if not saver.Input.GetConnectedOutput(): return [saver] diff --git a/openpype/hosts/fusion/plugins/publish/validate_saver_passthrough.py b/openpype/hosts/fusion/plugins/publish/validate_saver_passthrough.py index 677861a654..9004976dc5 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_saver_passthrough.py +++ b/openpype/hosts/fusion/plugins/publish/validate_saver_passthrough.py @@ -37,7 +37,7 @@ class ValidateSaverPassthrough(pyblish.api.ContextPlugin): def is_invalid(self, instance): - saver = instance.data["toolSaver"] + saver = instance.data["tool"] attr = saver.GetAttrs() active = not attr["TOOLB_PassThrough"] diff --git a/openpype/hosts/fusion/plugins/publish/validate_unique_subsets.py b/openpype/hosts/fusion/plugins/publish/validate_unique_subsets.py index 6a65182fae..5b6ceb2fdb 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_unique_subsets.py +++ b/openpype/hosts/fusion/plugins/publish/validate_unique_subsets.py @@ -43,7 +43,7 @@ class ValidateUniqueSubsets(pyblish.api.ContextPlugin): invalid.extend(instances) # Return tools for the invalid instances so they can be selected - invalid = [instance.data["toolSaver"] for instance in invalid] + invalid = [instance.data["tool"] for instance in invalid] return invalid From 6968c7c8ba0bed66e131c81ec4b0f53eaa99c3da Mon Sep 17 00:00:00 2001 From: Thomas Fricard <51854004+friquette@users.noreply.github.com> Date: Fri, 12 May 2023 14:57:17 +0200 Subject: [PATCH 074/130] add shortcut to action if in configuration (#4927) Co-authored-by: Thomas Fricard --- .../python/common/scriptsmenu/scriptsmenu.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/openpype/vendor/python/common/scriptsmenu/scriptsmenu.py b/openpype/vendor/python/common/scriptsmenu/scriptsmenu.py index 6f6d0b5715..8ab621f757 100644 --- a/openpype/vendor/python/common/scriptsmenu/scriptsmenu.py +++ b/openpype/vendor/python/common/scriptsmenu/scriptsmenu.py @@ -19,9 +19,9 @@ class ScriptsMenu(QtWidgets.QMenu): Args: title (str): the name of the root menu which will be created - + parent (QtWidgets.QObject) : the QObject to parent the menu to - + Returns: None @@ -94,7 +94,7 @@ class ScriptsMenu(QtWidgets.QMenu): parent(QtWidgets.QWidget): the object to parent the menu to title(str): the title of the menu - + Returns: QtWidget.QMenu instance """ @@ -111,7 +111,7 @@ class ScriptsMenu(QtWidgets.QMenu): return menu def add_script(self, parent, title, command, sourcetype, icon=None, - tags=None, label=None, tooltip=None): + tags=None, label=None, tooltip=None, shortcut=None): """Create an action item which runs a script when clicked Args: @@ -134,6 +134,8 @@ class ScriptsMenu(QtWidgets.QMenu): tooltip (str): A tip for the user about the usage fo the tool + shortcut (str): A shortcut to run the command + Returns: QtWidget.QAction instance @@ -166,6 +168,9 @@ class ScriptsMenu(QtWidgets.QMenu): raise RuntimeError("Script action can't be " "processed: {}".format(e)) + if shortcut: + script_action.setShortcut(shortcut) + if icon: iconfile = os.path.expandvars(icon) script_action.iconfile = iconfile @@ -253,7 +258,7 @@ class ScriptsMenu(QtWidgets.QMenu): def _update_search(self, search): """Hide all the samples which do not match the user's import - + Returns: None From d3428da3937bb0dbad2e1f86705e95d2bbe6b695 Mon Sep 17 00:00:00 2001 From: kaa Date: Fri, 12 May 2023 15:19:23 +0200 Subject: [PATCH 075/130] fix get_linked_assets project name arg (#4940) --- openpype/pipeline/workfile/build_workfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/workfile/build_workfile.py b/openpype/pipeline/workfile/build_workfile.py index 26b17fa151..8329487839 100644 --- a/openpype/pipeline/workfile/build_workfile.py +++ b/openpype/pipeline/workfile/build_workfile.py @@ -186,7 +186,7 @@ class BuildWorkfile: if link_context_profiles: # Find and append linked assets if preset has set linked mapping - link_assets = get_linked_assets(current_asset_entity) + link_assets = get_linked_assets(project_name, current_asset_entity) if link_assets: assets.extend(link_assets) 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 076/130] 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 9d6cd8d2c83fe3d98aca92fea206c34b208f49a0 Mon Sep 17 00:00:00 2001 From: Seyedmohammadreza Hashemizadeh Date: Fri, 12 May 2023 16:21:58 +0200 Subject: [PATCH 077/130] add options --- openpype/hosts/maya/api/lib.py | 4 ++-- openpype/hosts/maya/plugins/load/load_reference.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index f814187cc1..d44f1754f9 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -3937,7 +3937,7 @@ def get_capture_preset(task_name, task_type, subset, project_settings, log): return capture_preset or {} -def create_rig_animation_instance(nodes, context, namespace, log=None): +def create_rig_animation_instance(nodes, context, namespace, options, log=None): """Create an animation publish instance for loaded rigs. See the RecreateRigAnimationInstance inventory action on how to use this @@ -3982,6 +3982,6 @@ def create_rig_animation_instance(nodes, context, namespace, log=None): creator_plugin, name=namespace, asset=asset, - options={"useSelection": True}, + options=options.update({"useSelection": True}), data={"dependencies": dependency} ) diff --git a/openpype/hosts/maya/plugins/load/load_reference.py b/openpype/hosts/maya/plugins/load/load_reference.py index 7d717dcd44..31b6e9d624 100644 --- a/openpype/hosts/maya/plugins/load/load_reference.py +++ b/openpype/hosts/maya/plugins/load/load_reference.py @@ -223,7 +223,7 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): def _post_process_rig(self, name, namespace, context, options): nodes = self[:] create_rig_animation_instance( - nodes, context, namespace, log=self.log + nodes, context, namespace, options, log=self.log ) def _lock_camera_transforms(self, nodes): From ce1e45f7082a108258b2fa1bbd3e5a37e25f52e3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 12 May 2023 16:35:10 +0200 Subject: [PATCH 078/130] fix key assignment on instance data (#4966) --- .../ftrack/plugins/publish/integrate_hierarchy_ftrack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py b/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py index 9f35424d42..6daaea5f18 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py @@ -378,7 +378,7 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): existing_tasks.append(task_name_low) for instance in instances_by_task_name[task_name_low]: - instance["ftrackTask"] = child + instance.data["ftrackTask"] = child for task_name in tasks: task_type = tasks[task_name]["type"] From ab357eb03e316691057633b5762cdb094b35ef8d Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Fri, 12 May 2023 16:44:44 +0200 Subject: [PATCH 079/130] Addons directory (#4893) * Add addons directory * add addons dir to modules dirs automatically --------- Co-authored-by: iLLiCiTiT --- .gitignore | 6 ++++++ openpype/addons/README.md | 3 +++ openpype/modules/base.py | 6 +++++- 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 openpype/addons/README.md diff --git a/.gitignore b/.gitignore index 18e7cd7bf2..50f52f65a3 100644 --- a/.gitignore +++ b/.gitignore @@ -112,3 +112,9 @@ tools/run_eventserver.* tools/dev_* .github_changelog_generator + + +# Addons +######## +/openpype/addons/* +!/openpype/addons/README.md diff --git a/openpype/addons/README.md b/openpype/addons/README.md new file mode 100644 index 0000000000..92b8b8c07c --- /dev/null +++ b/openpype/addons/README.md @@ -0,0 +1,3 @@ +This directory is for storing external addons that needs to be included in the pipeline when distributed. + +The directory is ignored by Git, but included in the zip and installation files. diff --git a/openpype/modules/base.py b/openpype/modules/base.py index ed1eeb04cd..732525b6eb 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -311,6 +311,7 @@ def _load_modules(): # Look for OpenPype modules in paths defined with `get_module_dirs` # - dynamically imported OpenPype modules and addons module_dirs = get_module_dirs() + # Add current directory at first place # - has small differences in import logic current_dir = os.path.abspath(os.path.dirname(__file__)) @@ -318,8 +319,11 @@ def _load_modules(): module_dirs.insert(0, hosts_dir) module_dirs.insert(0, current_dir) + addons_dir = os.path.join(os.path.dirname(current_dir), "addons") + module_dirs.append(addons_dir) + processed_paths = set() - for dirpath in module_dirs: + for dirpath in frozenset(module_dirs): # Skip already processed paths if dirpath in processed_paths: continue From 5ff9ab368b392e48dbc1c522a461d5b52c300b79 Mon Sep 17 00:00:00 2001 From: Sharkitty Date: Fri, 12 May 2023 17:07:52 +0200 Subject: [PATCH 080/130] 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 78f8bbfd8066604d490aa89b56e0061a0d8d191b Mon Sep 17 00:00:00 2001 From: Ember Light <49758407+EmberLightVFX@users.noreply.github.com> Date: Fri, 12 May 2023 17:29:38 +0200 Subject: [PATCH 081/130] Kitsu - Add "image", "online" and "plate" to review families (#4923) * Add kitsu review to the default png review's tags * Add "image", "online" and "plate" as possible review families --- .../modules/kitsu/plugins/publish/integrate_kitsu_note.py | 7 ++++--- .../kitsu/plugins/publish/integrate_kitsu_review.py | 3 +-- openpype/settings/defaults/project_settings/global.json | 3 ++- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py index f8e56377bb..6e5dd056f3 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py @@ -9,7 +9,7 @@ class IntegrateKitsuNote(pyblish.api.ContextPlugin): order = pyblish.api.IntegratorOrder label = "Kitsu Note and Status" - families = ["render", "kitsu"] + families = ["render", "image", "online", "plate", "kitsu"] # status settings set_status_note = False @@ -52,8 +52,9 @@ class IntegrateKitsuNote(pyblish.api.ContextPlugin): for instance in context: # Check if instance is a review by checking its family # Allow a match to primary family or any of families - families = set([instance.data["family"]] + - instance.data.get("families", [])) + families = set( + [instance.data["family"]] + instance.data.get("families", []) + ) if "review" not in families: continue diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py index e05ff05f50..bbed4a3024 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py @@ -8,11 +8,10 @@ class IntegrateKitsuReview(pyblish.api.InstancePlugin): order = pyblish.api.IntegratorOrder + 0.01 label = "Kitsu Review" - families = ["render", "kitsu"] + families = ["render", "image", "online", "plate", "kitsu"] optional = True def process(self, instance): - # Check comment has been created comment_id = instance.data.get("kitsu_comment", {}).get("id") if not comment_id: diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 50b62737d8..75f335f1de 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -82,7 +82,8 @@ "png": { "ext": "png", "tags": [ - "ftrackreview" + "ftrackreview", + "kitsureview" ], "burnins": [], "ffmpeg_args": { From 454abe459ed834e6d10f014708c83a7142d1840d Mon Sep 17 00:00:00 2001 From: Seyedmohammadreza Hashemizadeh Date: Fri, 12 May 2023 17:53:05 +0200 Subject: [PATCH 082/130] add custom animation subset --- openpype/hosts/maya/api/lib.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index d44f1754f9..124e0e5b8a 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -3937,7 +3937,9 @@ def get_capture_preset(task_name, task_type, subset, project_settings, log): return capture_preset or {} -def create_rig_animation_instance(nodes, context, namespace, options, log=None): +def create_rig_animation_instance( + nodes, context, namespace, options, log=None +): """Create an animation publish instance for loaded rigs. See the RecreateRigAnimationInstance inventory action on how to use this @@ -3971,6 +3973,12 @@ def create_rig_animation_instance(nodes, context, namespace, options, log=None): asset = legacy_io.Session["AVALON_ASSET"] dependency = str(context["representation"]["_id"]) + custom_subset = options.get("animationSubsetName") + + if custom_subset: + rig_subset = context['subset']['name'] + namespace = namespace.replace(rig_subset, custom_subset) + if log: log.info("Creating subset: {}".format(namespace)) @@ -3982,6 +3990,6 @@ def create_rig_animation_instance(nodes, context, namespace, options, log=None): creator_plugin, name=namespace, asset=asset, - options=options.update({"useSelection": True}), + options={"useSelection": True}, data={"dependencies": dependency} ) From eaefd594b6f2f89a0e8a1accbb5912b1a744035a Mon Sep 17 00:00:00 2001 From: Ynbot Date: Sat, 13 May 2023 03:25:18 +0000 Subject: [PATCH 083/130] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 7df154fe1e..319a58d384 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.7-nightly.2" +__version__ = "3.15.7-nightly.3" From 927f92df87d3bae5d035c3ae4f857ac171e84dfa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 13 May 2023 03:26:01 +0000 Subject: [PATCH 084/130] 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 11b895624afb2d1dde099eefe09c46b7d3f65b24 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Mon, 15 May 2023 10:44:58 +0100 Subject: [PATCH 085/130] Fix transform when loading layout to match existing assets --- .../plugins/load/load_layout_existing.py | 81 +++++++------------ 1 file changed, 28 insertions(+), 53 deletions(-) diff --git a/openpype/hosts/unreal/plugins/load/load_layout_existing.py b/openpype/hosts/unreal/plugins/load/load_layout_existing.py index 96ee8cfc25..f4a2d1e7cc 100644 --- a/openpype/hosts/unreal/plugins/load/load_layout_existing.py +++ b/openpype/hosts/unreal/plugins/load/load_layout_existing.py @@ -89,50 +89,26 @@ class ExistingLayoutLoader(plugin.Loader): raise NotImplementedError( f"Unreal version {ue_major} not supported") - def _get_transform(self, ext, import_data, lasset): - conversion = unreal.Matrix.IDENTITY.transform() - fbx_tuning = unreal.Matrix.IDENTITY.transform() + def _transform_from_basis(self, transform, basis): + """Transform a transform from a basis to a new basis.""" + # Get the basis matrix + basis_matrix = unreal.Matrix( + basis[0], + basis[1], + basis[2], + basis[3] + ) + transform_matrix = unreal.Matrix( + transform[0], + transform[1], + transform[2], + transform[3] + ) - basis = unreal.Matrix( - lasset.get('basis')[0], - lasset.get('basis')[1], - lasset.get('basis')[2], - lasset.get('basis')[3] - ).transform() - transform = unreal.Matrix( - lasset.get('transform_matrix')[0], - lasset.get('transform_matrix')[1], - lasset.get('transform_matrix')[2], - lasset.get('transform_matrix')[3] - ).transform() + new_transform = ( + basis_matrix.get_inverse() * transform_matrix * basis_matrix) - # Check for the conversion settings. We cannot access - # the alembic conversion settings, so we assume that - # the maya ones have been applied. - if ext == '.fbx': - loc = import_data.import_translation - rot = import_data.import_rotation.to_vector() - scale = import_data.import_uniform_scale - conversion = unreal.Transform( - location=[loc.x, loc.y, loc.z], - rotation=[rot.x, rot.y, rot.z], - scale=[-scale, scale, scale] - ) - fbx_tuning = unreal.Transform( - rotation=[180.0, 0.0, 90.0], - scale=[1.0, 1.0, 1.0] - ) - elif ext == '.abc': - # This is the standard conversion settings for - # alembic files from Maya. - conversion = unreal.Transform( - location=[0.0, 0.0, 0.0], - rotation=[0.0, 0.0, 0.0], - scale=[1.0, -1.0, 1.0] - ) - - new_transform = (basis.inverse() * transform * basis) - return fbx_tuning * conversion.inverse() * new_transform + return new_transform.transform() def _spawn_actor(self, obj, lasset): actor = EditorLevelLibrary.spawn_actor_from_object( @@ -140,16 +116,13 @@ class ExistingLayoutLoader(plugin.Loader): ) actor.set_actor_label(lasset.get('instance_name')) - smc = actor.get_editor_property('static_mesh_component') - mesh = smc.get_editor_property('static_mesh') - import_data = mesh.get_editor_property('asset_import_data') - filename = import_data.get_first_filename() - path = Path(filename) - transform = self._get_transform( - path.suffix, import_data, lasset) + transform = lasset.get('transform_matrix') + basis = lasset.get('basis') - actor.set_actor_transform(transform, False, True) + t = self._transform_from_basis(transform, basis) + + actor.set_actor_transform(t, False, True) @staticmethod def _get_fbx_loader(loaders, family): @@ -320,9 +293,11 @@ class ExistingLayoutLoader(plugin.Loader): containers.append(container) # Set the transform for the actor. - transform = self._get_transform( - path.suffix, import_data, lasset) - actor.set_actor_transform(transform, False, True) + transform = lasset.get('transform_matrix') + basis = lasset.get('basis') + + t = self._transform_from_basis(transform, basis) + actor.set_actor_transform(t, False, True) actors_matched.append(actor) found = True From 5625c1f45b542286877bc7a8894c7562053f7a30 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Mon, 15 May 2023 12:09:25 +0100 Subject: [PATCH 086/130] Use proper variable names --- .../hosts/unreal/plugins/load/load_layout_existing.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/unreal/plugins/load/load_layout_existing.py b/openpype/hosts/unreal/plugins/load/load_layout_existing.py index f4a2d1e7cc..929a9a1399 100644 --- a/openpype/hosts/unreal/plugins/load/load_layout_existing.py +++ b/openpype/hosts/unreal/plugins/load/load_layout_existing.py @@ -120,9 +120,9 @@ class ExistingLayoutLoader(plugin.Loader): transform = lasset.get('transform_matrix') basis = lasset.get('basis') - t = self._transform_from_basis(transform, basis) + computed_transform = self._transform_from_basis(transform, basis) - actor.set_actor_transform(t, False, True) + actor.set_actor_transform(computed_transform, False, True) @staticmethod def _get_fbx_loader(loaders, family): @@ -296,8 +296,9 @@ class ExistingLayoutLoader(plugin.Loader): transform = lasset.get('transform_matrix') basis = lasset.get('basis') - t = self._transform_from_basis(transform, basis) - actor.set_actor_transform(t, False, True) + computed_transform = self._transform_from_basis( + transform, basis) + actor.set_actor_transform(computed_transform, False, True) actors_matched.append(actor) found = True 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 087/130] 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 088/130] 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 251470997783c56c0bf075fd1e206e37e1ccb9d3 Mon Sep 17 00:00:00 2001 From: Seyedmohammadreza Hashemizadeh Date: Mon, 15 May 2023 15:46:20 +0200 Subject: [PATCH 089/130] use custom namespace for animation instance --- openpype/hosts/maya/api/lib.py | 55 +++++++++++++++++++++++++++++-- openpype/hosts/maya/api/plugin.py | 38 --------------------- 2 files changed, 52 insertions(+), 41 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 124e0e5b8a..b84056f5ce 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -190,6 +190,44 @@ def maintained_selection(): cmds.select(clear=True) +def get_custom_namespace(custom_namespace): + """Return unique namespace. + + The input namespace can contain a single group + of '#' number tokens to indicate where the namespace's + unique index should go. The amount of tokens defines + the zero padding of the number, e.g ### turns into 001. + + Warning: Note that a namespace will always be + prefixed with a _ if it starts with a digit + + Example: + >>> get_custom_namespace("myspace_##_") + # myspace_01_ + >>> get_custom_namespace("##_myspace") + # _01_myspace + >>> get_custom_namespace("myspace##") + # myspace01 + + """ + split = re.split("([#]+)", custom_namespace, 1) + + if len(split) == 3: + base, padding, suffix = split + padding = "%0{}d".format(len(padding)) + else: + base = split[0] + padding = "%02d" # default padding + suffix = "" + + return unique_namespace( + base, + format=padding, + prefix="_" if not base or base[0].isdigit() else "", + suffix=suffix + ) + + def unique_namespace(namespace, format="%02d", prefix="", suffix=""): """Return unique namespace @@ -3974,10 +4012,21 @@ def create_rig_animation_instance( dependency = str(context["representation"]["_id"]) custom_subset = options.get("animationSubsetName") - if custom_subset: - rig_subset = context['subset']['name'] - namespace = namespace.replace(rig_subset, custom_subset) + formatting_data = { + "asset_name": context['asset']['name'], + "asset_type": context['asset']['type'], + "subset": context['subset']['name'], + "family": ( + context['subset']['data'].get('family') or + context['subset']['data']['families'][0] + ) + } + namespace = get_custom_namespace( + custom_subset.format( + **formatting_data + ) + ) if log: log.info("Creating subset: {}".format(namespace)) diff --git a/openpype/hosts/maya/api/plugin.py b/openpype/hosts/maya/api/plugin.py index 714278ba6c..3fce92db28 100644 --- a/openpype/hosts/maya/api/plugin.py +++ b/openpype/hosts/maya/api/plugin.py @@ -84,44 +84,6 @@ def get_reference_node_parents(ref): return parents -def get_custom_namespace(custom_namespace): - """Return unique namespace. - - The input namespace can contain a single group - of '#' number tokens to indicate where the namespace's - unique index should go. The amount of tokens defines - the zero padding of the number, e.g ### turns into 001. - - Warning: Note that a namespace will always be - prefixed with a _ if it starts with a digit - - Example: - >>> get_custom_namespace("myspace_##_") - # myspace_01_ - >>> get_custom_namespace("##_myspace") - # _01_myspace - >>> get_custom_namespace("myspace##") - # myspace01 - - """ - split = re.split("([#]+)", custom_namespace, 1) - - if len(split) == 3: - base, padding, suffix = split - padding = "%0{}d".format(len(padding)) - else: - base = split[0] - padding = "%02d" # default padding - suffix = "" - - return lib.unique_namespace( - base, - format=padding, - prefix="_" if not base or base[0].isdigit() else "", - suffix=suffix - ) - - class Creator(LegacyCreator): defaults = ['Main'] From 8ffe7f55522b364c07e6fd65cc8ad9cf5a125ed9 Mon Sep 17 00:00:00 2001 From: Seyedmohammadreza Hashemizadeh Date: Mon, 15 May 2023 16:27:03 +0200 Subject: [PATCH 090/130] fix function call --- openpype/hosts/maya/api/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/plugin.py b/openpype/hosts/maya/api/plugin.py index 3fce92db28..604ff101db 100644 --- a/openpype/hosts/maya/api/plugin.py +++ b/openpype/hosts/maya/api/plugin.py @@ -178,7 +178,7 @@ class ReferenceLoader(Loader): count = options.get("count") or 1 for c in range(0, count): - namespace = get_custom_namespace(custom_namespace) + namespace = lib.get_custom_namespace(custom_namespace) group_name = "{}:{}".format( namespace, custom_group_name From b192f8395d7cdd9c417e96e06a112e5761983b9d Mon Sep 17 00:00:00 2001 From: Seyedmohammadreza Hashemizadeh Date: Mon, 15 May 2023 16:34:37 +0200 Subject: [PATCH 091/130] update docstring --- openpype/hosts/maya/api/lib.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index b84056f5ce..56ba59186d 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -3987,6 +3987,7 @@ def create_rig_animation_instance( nodes (list): Member nodes of the rig instance. context (dict): Representation context of the rig container namespace (str): Namespace of the rig container + options (dict): Additional loader data log (logging.Logger, optional): Logger to log to if provided Returns: From f8ad6966fb53248e4c33ea2d3166c076fb078a42 Mon Sep 17 00:00:00 2001 From: Seyedmohammadreza Hashemizadeh Date: Mon, 15 May 2023 17:12:17 +0200 Subject: [PATCH 092/130] make options optional --- openpype/hosts/maya/api/lib.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 56ba59186d..7f160afd3e 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -3976,7 +3976,7 @@ def get_capture_preset(task_name, task_type, subset, project_settings, log): def create_rig_animation_instance( - nodes, context, namespace, options, log=None + nodes, context, namespace, options=None, log=None ): """Create an animation publish instance for loaded rigs. @@ -3987,13 +3987,16 @@ def create_rig_animation_instance( nodes (list): Member nodes of the rig instance. context (dict): Representation context of the rig container namespace (str): Namespace of the rig container - options (dict): Additional loader data + options (dict, optional): Additional loader data log (logging.Logger, optional): Logger to log to if provided Returns: None """ + if options is None: + options = {} + output = next((node for node in nodes if node.endswith("out_SET")), None) controls = next((node for node in nodes if From a051bb281a880c155c59083c153d9ed4e44e615f Mon Sep 17 00:00:00 2001 From: Mreza Hashemizadeh <68907585+mre7a@users.noreply.github.com> Date: Mon, 15 May 2023 17:22:40 +0200 Subject: [PATCH 093/130] Update openpype/hosts/maya/plugins/load/load_reference.py Co-authored-by: Toke Jepsen --- openpype/hosts/maya/plugins/load/load_reference.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/load_reference.py b/openpype/hosts/maya/plugins/load/load_reference.py index 31b6e9d624..f4a4a44344 100644 --- a/openpype/hosts/maya/plugins/load/load_reference.py +++ b/openpype/hosts/maya/plugins/load/load_reference.py @@ -223,7 +223,7 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): def _post_process_rig(self, name, namespace, context, options): nodes = self[:] create_rig_animation_instance( - nodes, context, namespace, options, log=self.log + nodes, context, namespace, options=options, log=self.log ) def _lock_camera_transforms(self, nodes): From 628ecbe5e2e6fb2c11df0185726e60a168eba013 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Mon, 15 May 2023 17:58:12 +0200 Subject: [PATCH 094/130] :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 From 3df689787699f6f51557e058357273689a64cbf0 Mon Sep 17 00:00:00 2001 From: Seyedmohammadreza Hashemizadeh Date: Mon, 15 May 2023 18:01:52 +0200 Subject: [PATCH 095/130] Add some documentations --- website/docs/admin_hosts_maya.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/website/docs/admin_hosts_maya.md b/website/docs/admin_hosts_maya.md index f0b8710246..700822843f 100644 --- a/website/docs/admin_hosts_maya.md +++ b/website/docs/admin_hosts_maya.md @@ -247,15 +247,24 @@ Fill in the necessary fields (the optional fields are regex filters) ![new place holder](assets/maya-placeholder_new.png) - - Builder type: Whether the the placeholder should load current asset representations or linked assets representations + - ***Builder type***: Whether the the placeholder should load current asset representations or linked assets representations - - Representation: Representation that will be loaded (ex: ma, abc, png, etc...) + - ***Representation***: Representation that will be loaded (ex: ma, abc, png, etc...) - - Family: Family of the representation to load (main, look, image, etc ...) + - ***Family***: Family of the representation to load (main, look, image, etc ...) - - Loader: Placeholder loader name that will be used to load corresponding representations + - ***Loader***: Placeholder loader name that will be used to load corresponding representations + + - ***Order***: Priority for current placeholder loader (priority is lowest first, highest last) + + - ***Loader arguments***: Loader arguments dictionary can be used to pass optional data to loaders. + One use case is to define a custom Subset name for the animation instances created while loading Rig references.This follows the custom namespace system used by loaders. + + **Example** + ``` + {"animationSubsetName": "{asset_name}_animation_{subset}_##_"} + ``` - - Order: Priority for current placeholder loader (priority is lowest first, highet last) - **Save your template** From f51af7de278e77724c058196eed7dd487da07dfe Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 15 May 2023 21:54:01 +0200 Subject: [PATCH 096/130] fusion: renaming comp frame range related attributes --- .../fusion/plugins/publish/collect_comp_frame_range.py | 8 ++++---- .../hosts/fusion/plugins/publish/collect_instances.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/fusion/plugins/publish/collect_comp_frame_range.py b/openpype/hosts/fusion/plugins/publish/collect_comp_frame_range.py index 38d6577667..08bdad3120 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_comp_frame_range.py +++ b/openpype/hosts/fusion/plugins/publish/collect_comp_frame_range.py @@ -41,9 +41,9 @@ class CollectFusionCompFrameRanges(pyblish.api.ContextPlugin): ) = get_comp_render_range(comp) data = {} - data["compFrameStart"] = int(start) - data["compFrameEnd"] = int(end) - data["compFrameStartHandle"] = int(global_start) - data["compFrameEndHandle"] = int(global_end) + data["renderFrameStart"] = int(start) + data["renderFrameEnd"] = int(end) + data["compFrameStart"] = int(global_start) + data["compFrameEnd"] = int(global_end) context.data.update(data) diff --git a/openpype/hosts/fusion/plugins/publish/collect_instances.py b/openpype/hosts/fusion/plugins/publish/collect_instances.py index 5a6a918730..c1c23ec570 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_instances.py +++ b/openpype/hosts/fusion/plugins/publish/collect_instances.py @@ -32,8 +32,8 @@ class CollectInstanceData(pyblish.api.InstancePlugin): if creator_attributes.get("custom_range"): # get comp frame ranges - start = context.data["compFrameStart"] - end = context.data["compFrameEnd"] + start = context.data["renderFrameStart"] + end = context.data["renderFrameEnd"] handle_start = 0 handle_end = 0 start_handle = start From 478c85d7cc96188db55af4cbc8c1a2479ff8717e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 15 May 2023 22:09:18 +0200 Subject: [PATCH 097/130] Fusion: renaming confusing attribute name and label --- openpype/hosts/fusion/plugins/create/create_saver.py | 4 +++- openpype/hosts/fusion/plugins/publish/collect_instances.py | 2 +- openpype/hosts/fusion/plugins/publish/collect_render.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/fusion/plugins/create/create_saver.py b/openpype/hosts/fusion/plugins/create/create_saver.py index 1a60526e42..67b1465ec7 100644 --- a/openpype/hosts/fusion/plugins/create/create_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_saver.py @@ -200,7 +200,9 @@ class CreateSaver(NewCreator): self._get_render_target_enum(), self._get_reviewable_bool(), BoolDef( - "custom_range", label="Custom range", default=False, + "viewer_render_range", + label="Viewer render in/out", + default=False, ) ] return attr_defs diff --git a/openpype/hosts/fusion/plugins/publish/collect_instances.py b/openpype/hosts/fusion/plugins/publish/collect_instances.py index c1c23ec570..6887f4f4e9 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_instances.py +++ b/openpype/hosts/fusion/plugins/publish/collect_instances.py @@ -30,7 +30,7 @@ class CollectInstanceData(pyblish.api.InstancePlugin): start_handle = start - handle_start end_handle = end + handle_end - if creator_attributes.get("custom_range"): + if creator_attributes.get("viewer_render_range"): # get comp frame ranges start = context.data["renderFrameStart"] end = context.data["renderFrameEnd"] diff --git a/openpype/hosts/fusion/plugins/publish/collect_render.py b/openpype/hosts/fusion/plugins/publish/collect_render.py index 64d9aedc3b..c3ae9f381d 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_render.py +++ b/openpype/hosts/fusion/plugins/publish/collect_render.py @@ -83,7 +83,7 @@ class CollectFusionRender( frameEnd=inst.data["frameEnd"], handleStart=inst.data["handleStart"], handleEnd=inst.data["handleEnd"], - ignoreFrameHandleCheck=(not inst.data.get("custom_range")), + ignoreFrameHandleCheck=(not inst.data.get("viewer_render_range")), frameStep=1, fps=comp_frame_format_prefs.get("Rate"), app_version=comp.GetApp().Version, From e14e0f5a40c6a48ffc9302544c99e0c3f757b2b6 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 15 May 2023 22:11:35 +0200 Subject: [PATCH 098/130] hound --- openpype/hosts/fusion/plugins/publish/collect_render.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/plugins/publish/collect_render.py b/openpype/hosts/fusion/plugins/publish/collect_render.py index c3ae9f381d..6956b566ad 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_render.py +++ b/openpype/hosts/fusion/plugins/publish/collect_render.py @@ -83,7 +83,8 @@ class CollectFusionRender( frameEnd=inst.data["frameEnd"], handleStart=inst.data["handleStart"], handleEnd=inst.data["handleEnd"], - ignoreFrameHandleCheck=(not inst.data.get("viewer_render_range")), + ignoreFrameHandleCheck=( + not inst.data.get("viewer_render_range")), frameStep=1, fps=comp_frame_format_prefs.get("Rate"), app_version=comp.GetApp().Version, From 380a9cba51448a90e3e7b1c67fd50a818837f20c Mon Sep 17 00:00:00 2001 From: Ynbot Date: Tue, 16 May 2023 08:44:24 +0000 Subject: [PATCH 099/130] [Automated] Release --- CHANGELOG.md | 297 ++++++++++++++++++++++++++++++++++++++++++++ openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 299 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07c1e7d5fd..bba6b64bfe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,303 @@ # Changelog +## [3.15.7](https://github.com/ynput/OpenPype/tree/3.15.7) + + +[Full Changelog](https://github.com/ynput/OpenPype/compare/3.15.6...3.15.7) + +### **🆕 New features** + + +
+Addons directory #4893 + +This adds a directory for Addons, for easier distribution of studio specific code. + + +___ + +
+ + +
+Kitsu - Add "image", "online" and "plate" to review families #4923 + +This PR adds "image", "online" and "plate" to the review families so they also can be uploaded to Kitsu.It also adds the `Add review to Kitsu` tag to the default png review. Without it the user would manually need to add it for single image uploads to Kitsu and might confuse users (it confused me first for a while as movies did work). + + +___ + +
+ + +
+Feature/remove and load inv action #4930 + +Added the ability to remove and load a container, as a way to reset it.This can be useful in cases where a container breaks in a way that can be fixed by removing it, then reloading it.Also added the ability to add `InventoryAction` plugins by placing them in `openpype/plugins/inventory`. + + +___ + +
+ +### **🚀 Enhancements** + + +
+Load Rig References - Change Rig to Animation in Animation instance #4877 + +We are using the template builder to build an animation scene. All the rig placeholders are imported correctly, but the automatically created animation instances retain the rig family in their names and subsets. In our example, we need animationMain instead of rigMain, because this name will be used in the following steps like lighting.Here is the result we need. I checked, and it's not a template builder problem, because even if I load a rig as a reference, the result is the same. For me, since we are in the animation instance, it makes more sense to have animation instead of rig in the name. The naming is just fine if we use create from the Openpype menu. + + +___ + +
+ + +
+Maya template builder - preserve all references when importing a template #4797 + +When building a template with Maya template builder, we import the template and also the references inside the template file. This causes some problems: +- We cannot use the references to version assets imported by the template. +- When we import the file, the internal reference files are also imported. As a side effect, Maya complains about a reference that no longer exists.`// Error: file: /xxx/maya/2023.3/linux/scripts/AETemplates/AEtransformRelated.mel line 58: Reference node 'turntable_mayaSceneMain_01_RN' is not associated with a reference file.` + + +___ + +
+ + +
+Unreal: Renaming the integration plugin to Ayon. #4646 + +Renamed the .h, and .cpp files to Ayon. Also renamed the classes to with the Ayon keyword. + + +___ + +
+ + +
+3dsMax: render dialogue needs to be closed #4729 + +Make sure the render setup dialog is in a closed state for the update of resolution and other render settings + + +___ + +
+ + +
+Maya Template Builder - Remove default cameras from renderable cameras #4815 + +When we build an asset workfile with build workfile from template inside Maya, we load our turntable camera. But then we end up with 2 renderables camera : **persp** the one imported from the template.We need to remove the **persp** camera (or any other default camera) from renderable cameras when building the work file. + + +___ + +
+ + +
+Validators for Frame Range in Max #4914 + +Switch Render Frame Range Type to 3 for specific ranges (initial setup for the range type is 4)Reset Frame Range will also set the frame range for render settingsRender Collector won't take the frame range from context data but take the range directly from render settingAdd validators for render frame range type and frame range respectively with repair action + + +___ + +
+ + +
+Fusion: Saver creator settings #4943 + +Adding Saver creator settings and enhanced rendering path with template. + + +___ + +
+ + +
+General: Project Anatomy on creators #4962 + +Anatomy object of current project is available on `CreateContext` and create plugins. + + +___ + +
+ +### **🐛 Bug fixes** + + +
+Maya: Validate shader name - OP-5903 #4971 + +Running the plugin would error with: +``` +// TypeError: 'str' object cannot be interpreted as an integer +```Fixed and added setting `active`. + + +___ + +
+ + +
+Houdini: Fix slow Houdini launch due to shelves generation #4829 + +Shelf generation during Houdini startup would add an insane amount of delay for the Houdini UI to launch correctly. By deferring the shelf generation this takes away the 5+ minutes of delay for the Houdini UI to launch. + + +___ + +
+ + +
+Fusion - Fixed "optional validation" #4912 + +Added OptionalPyblishPluginMixin and is_active checks for all publish tools that should be optional + + +___ + +
+ + +
+Bug: add missing `pyblish.util` import #4937 + +remote publishing was missing import of `remote_publish`. This is adding it back. + + +___ + +
+ + +
+Unreal: Fix missing 'object_path' property #4938 + +Epic removed the `object_path` property from `AssetData`. This PR fixes usages of that property.Fixes #4936 + + +___ + +
+ + +
+Remove obsolete global validator #4939 + +Removing `Validate Sequence Frames` validator from global plugins as it wasn't handling correctly many things and was by mistake enabled, breaking functionality on Deadline. + + +___ + +
+ + +
+General: fix build_workfile get_linked_assets missing project_name arg #4940 + +Linked assets collection don't work within `build_workfile` because `get_linked_assets` function call has a missing `project_name`argument. +- Added the `project_name` arg to the `get_linked_assets` function call. + + +___ + +
+ + +
+General: fix Scene Inventory switch version error dialog missing parent arg on init #4941 + +QuickFix for the switch version error dialog to set inventory widget as parent. + + +___ + +
+ + +
+Unreal: Fix camera frame range #4956 + +Fix the frame range of the level sequence for the Camera in Unreal. + + +___ + +
+ + +
+Unreal: Fix missing parameter when updating Alembic StaticMesh #4957 + +Fix an error when updating an Alembic StaticMesh in Unreal, due to a missing parameter in a function call. + + +___ + +
+ + +
+Unreal: Fix render extraction #4963 + +Fix a problem with the extraction of renders in Unreal. + + +___ + +
+ + +
+Unreal: Remove Python 3.8 syntax from addon #4965 + +Removed Python 3.8 syntax from addon. + + +___ + +
+ + +
+Ftrack: Fix editorial task creation #4966 + +Fix key assignment on instance data during editorial publishing in ftrack hierarchy integration. + + +___ + +
+ +### **Merged pull requests** + + +
+Add "shortcut" to Scripts Menu Definition #4927 + +Add the possibility to associate a shorcut for an entry in the script menu definition with the key "shortcut" + + +___ + +
+ + + + ## [3.15.6](https://github.com/ynput/OpenPype/tree/3.15.6) diff --git a/openpype/version.py b/openpype/version.py index 319a58d384..3a0d05be0e 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.7-nightly.3" +__version__ = "3.15.7" diff --git a/pyproject.toml b/pyproject.toml index 003f6cf2d3..190ecb9329 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.15.6" # OpenPype +version = "3.15.7" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 28e0838d00e9b3edc8880c3bc59a753cb6727929 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 16 May 2023 08:45:43 +0000 Subject: [PATCH 100/130] 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 7d224aa73f..08d20c2058 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 - 3.15.7-nightly.3 - 3.15.7-nightly.2 - 3.15.7-nightly.1 @@ -134,7 +135,6 @@ body: - 3.14.1 - 3.14.1-nightly.4 - 3.14.1-nightly.3 - - 3.14.1-nightly.2 validations: required: true - type: dropdown From 10b953f8bf0a16c2f49f5d89d3f0df6030ea85c9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 16 May 2023 11:34:54 +0200 Subject: [PATCH 101/130] fusion: converting frame range source to enum --- .../fusion/plugins/create/create_saver.py | 19 +++++++++++++------ .../plugins/publish/collect_instances.py | 5 +++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/fusion/plugins/create/create_saver.py b/openpype/hosts/fusion/plugins/create/create_saver.py index 67b1465ec7..80f60a0c6e 100644 --- a/openpype/hosts/fusion/plugins/create/create_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_saver.py @@ -199,11 +199,7 @@ class CreateSaver(NewCreator): attr_defs = [ self._get_render_target_enum(), self._get_reviewable_bool(), - BoolDef( - "viewer_render_range", - label="Viewer render in/out", - default=False, - ) + self._get_frame_range_enum() ] return attr_defs @@ -222,7 +218,6 @@ class CreateSaver(NewCreator): # 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", @@ -235,6 +230,18 @@ class CreateSaver(NewCreator): "render_target", items=rendering_targets, label="Render target" ) + def _get_frame_range_enum(self): + frame_range_options = { + "asset_db": "From asset database", + "viewer_render_range": "From viewer render in/out" + } + + return EnumDef( + "frame_range_source", + items=frame_range_options, + label="Frame range source" + ) + def _get_reviewable_bool(self): return BoolDef( "review", diff --git a/openpype/hosts/fusion/plugins/publish/collect_instances.py b/openpype/hosts/fusion/plugins/publish/collect_instances.py index 6887f4f4e9..61ce10d32f 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_instances.py +++ b/openpype/hosts/fusion/plugins/publish/collect_instances.py @@ -20,6 +20,7 @@ class CollectInstanceData(pyblish.api.InstancePlugin): # Include creator attributes directly as instance data creator_attributes = instance.data["creator_attributes"] + frame_range_source = creator_attributes.get("frame_range_source") instance.data.update(creator_attributes) # get asset frame ranges @@ -30,8 +31,8 @@ class CollectInstanceData(pyblish.api.InstancePlugin): start_handle = start - handle_start end_handle = end + handle_end - if creator_attributes.get("viewer_render_range"): - # get comp frame ranges + if frame_range_source == "viewer_render_range": + # set comp render frame ranges start = context.data["renderFrameStart"] end = context.data["renderFrameEnd"] handle_start = 0 From 2d6919297cdc370bc43a4cc76390021e2ed8564b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 16 May 2023 11:40:00 +0200 Subject: [PATCH 102/130] fusion: adding comp range option --- .../hosts/fusion/plugins/create/create_saver.py | 3 ++- .../fusion/plugins/publish/collect_instances.py | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/plugins/create/create_saver.py b/openpype/hosts/fusion/plugins/create/create_saver.py index 80f60a0c6e..d5e77730c8 100644 --- a/openpype/hosts/fusion/plugins/create/create_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_saver.py @@ -233,7 +233,8 @@ class CreateSaver(NewCreator): def _get_frame_range_enum(self): frame_range_options = { "asset_db": "From asset database", - "viewer_render_range": "From viewer render in/out" + "viewer_render_range": "From viewer render in/out", + "comp_range": "From composition timeline" } return EnumDef( diff --git a/openpype/hosts/fusion/plugins/publish/collect_instances.py b/openpype/hosts/fusion/plugins/publish/collect_instances.py index 61ce10d32f..59ff52f5b2 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_instances.py +++ b/openpype/hosts/fusion/plugins/publish/collect_instances.py @@ -40,6 +40,19 @@ class CollectInstanceData(pyblish.api.InstancePlugin): start_handle = start end_handle = end + if frame_range_source == "comp_range": + comp_start = context.data["compFrameStart"] + comp_end = context.data["compFrameEnd"] + render_start = context.data["renderFrameStart"] + render_end = context.data["renderFrameEnd"] + # set comp frame ranges + start = render_start + end = render_end + handle_start = render_start - comp_start + handle_end = comp_end - render_end + start_handle = comp_start + end_handle = comp_end + # Include start and end render frame in label subset = instance.data["subset"] label = "{subset} ({start}-{end})".format(subset=subset, From 16f24c253803026ab0f768ef5c3dca1320561ca4 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 16 May 2023 22:48:02 +0800 Subject: [PATCH 103/130] fix the bug of fbx loaders --- openpype/hosts/max/plugins/load/load_camera_fbx.py | 10 +++++++--- openpype/hosts/max/plugins/load/load_model_fbx.py | 9 +++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/max/plugins/load/load_camera_fbx.py b/openpype/hosts/max/plugins/load/load_camera_fbx.py index 3a6947798e..ce4dec32a0 100644 --- a/openpype/hosts/max/plugins/load/load_camera_fbx.py +++ b/openpype/hosts/max/plugins/load/load_camera_fbx.py @@ -36,12 +36,16 @@ importFile @"{filepath}" #noPrompt using:FBXIMP self.log.debug(f"Executing command: {fbx_import_cmd}") rt.execute(fbx_import_cmd) - container_name = f"{name}_CON" + container = rt.getNodeByName(f"{name}") + if not container: + container = rt.container() + container.name = f"{name}" - asset = rt.getNodeByName(f"{name}") + for selection in rt.getCurrentSelection(): + selection.Parent = container return containerise( - name, [asset], context, loader=self.__class__.__name__) + name, [container], context, loader=self.__class__.__name__) def update(self, container, representation): from pymxs import runtime as rt diff --git a/openpype/hosts/max/plugins/load/load_model_fbx.py b/openpype/hosts/max/plugins/load/load_model_fbx.py index 88b8f1ed89..7532e3a8a0 100644 --- a/openpype/hosts/max/plugins/load/load_model_fbx.py +++ b/openpype/hosts/max/plugins/load/load_model_fbx.py @@ -36,11 +36,16 @@ importFile @"{filepath}" #noPrompt using:FBXIMP self.log.debug(f"Executing command: {fbx_import_cmd}") rt.execute(fbx_import_cmd) + container = rt.getNodeByName(f"{name}") + if not container: + container = rt.container() + container.name = f"{name}" - asset = rt.getNodeByName(f"{name}") + for selection in rt.getCurrentSelection(): + selection.Parent = container return containerise( - name, [asset], context, loader=self.__class__.__name__) + name, [container], context, loader=self.__class__.__name__) def update(self, container, representation): from pymxs import runtime as rt From 255a72bfed1e50dcc30ad46cee1bc44b82d1f2bd Mon Sep 17 00:00:00 2001 From: Ynbot Date: Wed, 17 May 2023 03:25:43 +0000 Subject: [PATCH 104/130] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 3a0d05be0e..954cfa945b 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.7" +__version__ = "3.15.8-nightly.1" From c85ebbfac2236aa3bf761817024290a2a5c1a015 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 17 May 2023 03:26:23 +0000 Subject: [PATCH 105/130] 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 08d20c2058..0f58d61881 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.8-nightly.1 - 3.15.7 - 3.15.7-nightly.3 - 3.15.7-nightly.2 @@ -134,7 +135,6 @@ body: - 3.14.2-nightly.1 - 3.14.1 - 3.14.1-nightly.4 - - 3.14.1-nightly.3 validations: required: true - type: dropdown From 9112b87ba6ffc095fd43d5a769ce3501ef85e765 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 17 May 2023 12:20:43 +0200 Subject: [PATCH 106/130] :truck: move unreal plugin to separate repository --- openpype/hosts/unreal/integration/README.md | 10 - .../integration/UE_4.27/Ayon/.gitignore | 35 --- .../integration/UE_4.27/Ayon/Ayon.uplugin | 23 -- .../Ayon/Config/DefaultAyonSettings.ini | 2 - .../UE_4.27/Ayon/Config/FilterPlugin.ini | 8 - .../Ayon/Content/Python/init_unreal.py | 30 --- .../unreal/integration/UE_4.27/Ayon/README.md | 3 - .../UE_4.27/Ayon/Resources/ayon128.png | Bin 2358 -> 0 bytes .../UE_4.27/Ayon/Resources/ayon40.png | Bin 721 -> 0 bytes .../UE_4.27/Ayon/Resources/ayon512.png | Bin 16705 -> 0 bytes .../UE_4.27/Ayon/Source/Ayon/Ayon.Build.cs | 61 ------ .../UE_4.27/Ayon/Source/Ayon/Private/Ayon.cpp | 156 -------------- .../Ayon/Private/AyonAssetContainer.cpp | 114 ---------- .../Private/AyonAssetContainerFactory.cpp | 20 -- .../Ayon/Source/Ayon/Private/AyonLib.cpp | 53 ----- .../Ayon/Private/AyonPublishInstance.cpp | 203 ----------------- .../Private/AyonPublishInstanceFactory.cpp | 23 -- .../Source/Ayon/Private/AyonPythonBridge.cpp | 14 -- .../Ayon/Source/Ayon/Private/AyonSettings.cpp | 20 -- .../Ayon/Source/Ayon/Private/AyonStyle.cpp | 70 ------ .../Private/Commandlets/AyonActionResult.cpp | 41 ---- .../AyonGenerateProjectCommandlet.cpp | 141 ------------ .../Ayon/Private/OpenPypePublishInstance.cpp | 203 ----------------- .../UE_4.27/Ayon/Source/Ayon/Public/Ayon.h | 20 -- .../Source/Ayon/Public/AyonAssetContainer.h | 39 ---- .../Ayon/Public/AyonAssetContainerFactory.h | 21 -- .../Ayon/Source/Ayon/Public/AyonConstants.h | 15 -- .../UE_4.27/Ayon/Source/Ayon/Public/AyonLib.h | 19 -- .../Source/Ayon/Public/AyonPublishInstance.h | 103 --------- .../Ayon/Public/AyonPublishInstanceFactory.h | 22 -- .../Source/Ayon/Public/AyonPythonBridge.h | 20 -- .../Ayon/Source/Ayon/Public/AyonSettings.h | 31 --- .../Ayon/Source/Ayon/Public/AyonStyle.h | 23 -- .../Public/Commandlets/AyonActionResult.h | 83 ------- .../AyonGenerateProjectCommandlet.h | 60 ------ .../Source/Ayon/Public/Logging/Ayon_Log.h | 4 - .../Ayon/Public/OpenPypePublishInstance.h | 103 --------- .../integration/UE_4.27/BuildPlugin_4-27.bat | 1 - .../UE_4.27/BuildPlugin_4-27_Window.bat | 1 - .../UE_4.27/CommandletProject/.gitignore | 8 - .../CommandletProject.uproject | 12 -- .../unreal/integration/UE_5.0/Ayon/.gitignore | 35 --- .../integration/UE_5.0/Ayon/Ayon.uplugin | 24 --- .../Ayon/Config/DefaultAyonSettings.ini | 2 - .../UE_5.0/Ayon/Config/FilterPlugin.ini | 8 - .../UE_5.0/Ayon/Content/Python/init_unreal.py | 30 --- .../unreal/integration/UE_5.0/Ayon/README.md | 3 - .../UE_5.0/Ayon/Resources/ayon128.png | Bin 2358 -> 0 bytes .../UE_5.0/Ayon/Resources/ayon40.png | Bin 721 -> 0 bytes .../UE_5.0/Ayon/Resources/ayon512.png | Bin 16705 -> 0 bytes .../UE_5.0/Ayon/Source/Ayon/Ayon.Build.cs | 65 ------ .../UE_5.0/Ayon/Source/Ayon/Private/Ayon.cpp | 139 ------------ .../Ayon/Private/AyonAssetContainer.cpp | 113 ---------- .../Private/AyonAssetContainerFactory.cpp | 20 -- .../Ayon/Source/Ayon/Private/AyonCommands.cpp | 13 -- .../Ayon/Source/Ayon/Private/AyonLib.cpp | 51 ----- .../Ayon/Private/AyonPublishInstance.cpp | 204 ------------------ .../Private/AyonPublishInstanceFactory.cpp | 23 -- .../Source/Ayon/Private/AyonPythonBridge.cpp | 14 -- .../Ayon/Source/Ayon/Private/AyonSettings.cpp | 21 -- .../Ayon/Source/Ayon/Private/AyonStyle.cpp | 62 ------ .../Private/Commandlets/AyonActionResult.cpp | 40 ---- .../AyonGenerateProjectCommandlet.cpp | 140 ------------ .../Ayon/Private/OpenPypePublishInstance.cpp | 204 ------------------ .../UE_5.0/Ayon/Source/Ayon/Public/Ayon.h | 24 --- .../Source/Ayon/Public/AyonAssetContainer.h | 34 --- .../Ayon/Public/AyonAssetContainerFactory.h | 18 -- .../Ayon/Source/Ayon/Public/AyonCommands.h | 24 --- .../Ayon/Source/Ayon/Public/AyonConstants.h | 13 -- .../UE_5.0/Ayon/Source/Ayon/Public/AyonLib.h | 19 -- .../Source/Ayon/Public/AyonPublishInstance.h | 104 --------- .../Ayon/Public/AyonPublishInstanceFactory.h | 22 -- .../Source/Ayon/Public/AyonPythonBridge.h | 20 -- .../Ayon/Source/Ayon/Public/AyonSettings.h | 32 --- .../Ayon/Source/Ayon/Public/AyonStyle.h | 19 -- .../Public/Commandlets/AyonActionResult.h | 83 ------- .../AyonGenerateProjectCommandlet.h | 61 ------ .../Source/Ayon/Public/Logging/Ayon_Log.h | 4 - .../Ayon/Public/OpenPypePublishInstance.h | 104 --------- .../integration/UE_5.0/BuildPlugin_5-0.bat | 1 - .../UE_5.0/BuildPlugin_5-0_Window.bat | 1 - .../UE_5.0/CommandletProject/.gitignore | 41 ---- .../CommandletProject.uproject | 20 -- .../unreal/integration/UE_5.1/Ayon/.gitignore | 35 --- .../integration/UE_5.1/Ayon/Ayon.uplugin | 24 --- .../Ayon/Config/DefaultAyonSettings.ini | 2 - .../UE_5.1/Ayon/Config/FilterPlugin.ini | 8 - .../UE_5.1/Ayon/Content/Python/init_unreal.py | 30 --- .../unreal/integration/UE_5.1/Ayon/README.md | 3 - .../UE_5.1/Ayon/Resources/ayon128.png | Bin 2358 -> 0 bytes .../UE_5.1/Ayon/Resources/ayon40.png | Bin 721 -> 0 bytes .../UE_5.1/Ayon/Resources/ayon512.png | Bin 16705 -> 0 bytes .../UE_5.1/Ayon/Source/Ayon/Ayon.Build.cs | 65 ------ .../UE_5.1/Ayon/Source/Ayon/Private/Ayon.cpp | 139 ------------ .../Ayon/Private/AyonAssetContainer.cpp | 113 ---------- .../Private/AyonAssetContainerFactory.cpp | 20 -- .../Ayon/Source/Ayon/Private/AyonCommands.cpp | 13 -- .../Ayon/Source/Ayon/Private/AyonLib.cpp | 51 ----- .../Ayon/Private/AyonPublishInstance.cpp | 204 ------------------ .../Private/AyonPublishInstanceFactory.cpp | 23 -- .../Source/Ayon/Private/AyonPythonBridge.cpp | 14 -- .../Ayon/Source/Ayon/Private/AyonSettings.cpp | 21 -- .../Ayon/Source/Ayon/Private/AyonStyle.cpp | 62 ------ .../Private/Commandlets/AyonActionResult.cpp | 40 ---- .../AyonGenerateProjectCommandlet.cpp | 140 ------------ .../Ayon/Private/OpenPypePublishInstance.cpp | 204 ------------------ .../UE_5.1/Ayon/Source/Ayon/Public/Ayon.h | 24 --- .../Source/Ayon/Public/AyonAssetContainer.h | 34 --- .../Ayon/Public/AyonAssetContainerFactory.h | 18 -- .../Ayon/Source/Ayon/Public/AyonCommands.h | 24 --- .../Ayon/Source/Ayon/Public/AyonConstants.h | 13 -- .../UE_5.1/Ayon/Source/Ayon/Public/AyonLib.h | 19 -- .../Source/Ayon/Public/AyonPublishInstance.h | 104 --------- .../Ayon/Public/AyonPublishInstanceFactory.h | 22 -- .../Source/Ayon/Public/AyonPythonBridge.h | 20 -- .../Ayon/Source/Ayon/Public/AyonSettings.h | 32 --- .../Ayon/Source/Ayon/Public/AyonStyle.h | 19 -- .../Public/Commandlets/AyonActionResult.h | 83 ------- .../AyonGenerateProjectCommandlet.h | 61 ------ .../Source/Ayon/Public/Logging/Ayon_Log.h | 4 - .../Ayon/Public/OpenPypePublishInstance.h | 104 --------- .../integration/UE_5.1/BuildPlugin_5-1.bat | 1 - .../UE_5.1/BuildPlugin_5-1_Window.bat | 1 - .../UE_5.1/CommandletProject/.gitignore | 41 ---- .../CommandletProject.uproject | 20 -- 125 files changed, 5525 deletions(-) delete mode 100644 openpype/hosts/unreal/integration/README.md delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/.gitignore delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Ayon.uplugin delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Config/DefaultAyonSettings.ini delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Config/FilterPlugin.ini delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Content/Python/init_unreal.py delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/README.md delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Resources/ayon128.png delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Resources/ayon40.png delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Resources/ayon512.png delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Ayon.Build.cs delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/Ayon.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonAssetContainer.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonAssetContainerFactory.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonLib.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonPublishInstance.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonPublishInstanceFactory.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonPythonBridge.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonSettings.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonStyle.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/Commandlets/AyonActionResult.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/Commandlets/Implementations/AyonGenerateProjectCommandlet.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/OpenPypePublishInstance.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/Ayon.h delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonAssetContainer.h delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonAssetContainerFactory.h delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonConstants.h delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonLib.h delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonPublishInstance.h delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonPublishInstanceFactory.h delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonPythonBridge.h delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonSettings.h delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonStyle.h delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/Commandlets/AyonActionResult.h delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/Commandlets/Implementations/AyonGenerateProjectCommandlet.h delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/Logging/Ayon_Log.h delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/OpenPypePublishInstance.h delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/BuildPlugin_4-27.bat delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/BuildPlugin_4-27_Window.bat delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/CommandletProject/.gitignore delete mode 100644 openpype/hosts/unreal/integration/UE_4.27/CommandletProject/CommandletProject.uproject delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/.gitignore delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Ayon.uplugin delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Config/DefaultAyonSettings.ini delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Config/FilterPlugin.ini delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Content/Python/init_unreal.py delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/README.md delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Resources/ayon128.png delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Resources/ayon40.png delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Resources/ayon512.png delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Ayon.Build.cs delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/Ayon.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonAssetContainer.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonAssetContainerFactory.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonCommands.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonLib.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonPublishInstance.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonPublishInstanceFactory.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonPythonBridge.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonSettings.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonStyle.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/Commandlets/AyonActionResult.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/Commandlets/Implementations/AyonGenerateProjectCommandlet.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/OpenPypePublishInstance.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/Ayon.h delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonAssetContainer.h delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonAssetContainerFactory.h delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonCommands.h delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonConstants.h delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonLib.h delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonPublishInstance.h delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonPublishInstanceFactory.h delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonPythonBridge.h delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonSettings.h delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonStyle.h delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/Commandlets/AyonActionResult.h delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/Commandlets/Implementations/AyonGenerateProjectCommandlet.h delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/Logging/Ayon_Log.h delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/OpenPypePublishInstance.h delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/BuildPlugin_5-0.bat delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/BuildPlugin_5-0_Window.bat delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/CommandletProject/.gitignore delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/CommandletProject/CommandletProject.uproject delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/.gitignore delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Ayon.uplugin delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Config/DefaultAyonSettings.ini delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Config/FilterPlugin.ini delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Content/Python/init_unreal.py delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/README.md delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Resources/ayon128.png delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Resources/ayon40.png delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Resources/ayon512.png delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Ayon.Build.cs delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/Ayon.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonAssetContainer.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonAssetContainerFactory.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonCommands.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonLib.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonPublishInstance.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonPublishInstanceFactory.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonPythonBridge.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonSettings.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonStyle.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/Commandlets/AyonActionResult.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/Commandlets/Implementations/AyonGenerateProjectCommandlet.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/OpenPypePublishInstance.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/Ayon.h delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonAssetContainer.h delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonAssetContainerFactory.h delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonCommands.h delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonConstants.h delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonLib.h delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonPublishInstance.h delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonPublishInstanceFactory.h delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonPythonBridge.h delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonSettings.h delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonStyle.h delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/Commandlets/AyonActionResult.h delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/Commandlets/Implementations/AyonGenerateProjectCommandlet.h delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/Logging/Ayon_Log.h delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/OpenPypePublishInstance.h delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/BuildPlugin_5-1.bat delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/BuildPlugin_5-1_Window.bat delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/CommandletProject/.gitignore delete mode 100644 openpype/hosts/unreal/integration/UE_5.1/CommandletProject/CommandletProject.uproject diff --git a/openpype/hosts/unreal/integration/README.md b/openpype/hosts/unreal/integration/README.md deleted file mode 100644 index 961eea83e6..0000000000 --- a/openpype/hosts/unreal/integration/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Building the plugin - -In order to successfully build the plugin, make sure that the path to the UnrealBuildTool.exe is specified correctly. -After the UBT path specify for which platform it will be compiled. in the -Project parameter, specify the path to the -CommandletProject.uproject file. Next the build type has to be specified (DebugGame, Development, Package, etc.) and then the -TargetType (Editor, Runtime, etc.) - -`BuildPlugin_[Ver].bat` runs the building process in the background. If you want to show the progress inside the -command prompt, use the `BuildPlugin_[Ver]_Window.bat` file. - - diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/.gitignore b/openpype/hosts/unreal/integration/UE_4.27/Ayon/.gitignore deleted file mode 100644 index b32a6f55e5..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/Ayon/.gitignore +++ /dev/null @@ -1,35 +0,0 @@ -# Prerequisites -*.d - -# Compiled Object files -*.slo -*.lo -*.o -*.obj - -# Precompiled Headers -*.gch -*.pch - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Fortran module files -*.mod -*.smod - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables -*.exe -*.out -*.app - -/Binaries -/Intermediate diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Ayon.uplugin b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Ayon.uplugin deleted file mode 100644 index 0838da5577..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Ayon.uplugin +++ /dev/null @@ -1,23 +0,0 @@ -{ - "FileVersion": 3, - "Version": 1, - "VersionName": "1.0", - "FriendlyName": "Ayon", - "Description": "Ayon Integration", - "Category": "Ayon.Integration", - "CreatedBy": "Ondrej Samohel", - "CreatedByURL": "https://ayon.ynput.io", - "DocsURL": "https://ayon.ynput.io/docs/artist_hosts_unreal", - "MarketplaceURL": "", - "SupportURL": "https://ynput.io/", - "EngineVersion": "4.27", - "CanContainContent": true, - "Installed": true, - "Modules": [ - { - "Name": "Ayon", - "Type": "Editor", - "LoadingPhase": "Default" - } - ] -} diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Config/DefaultAyonSettings.ini b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Config/DefaultAyonSettings.ini deleted file mode 100644 index 9ad7f55201..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Config/DefaultAyonSettings.ini +++ /dev/null @@ -1,2 +0,0 @@ -[/Script/Ayon.AyonSettings] -FolderColor=(R=91,G=197,B=220,A=255) \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Config/FilterPlugin.ini b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Config/FilterPlugin.ini deleted file mode 100644 index ccebca2f32..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Config/FilterPlugin.ini +++ /dev/null @@ -1,8 +0,0 @@ -[FilterPlugin] -; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and -; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively. -; -; Examples: -; /README.txt -; /Extras/... -; /Binaries/ThirdParty/*.dll diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Content/Python/init_unreal.py b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Content/Python/init_unreal.py deleted file mode 100644 index 43d6b8b7cf..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Content/Python/init_unreal.py +++ /dev/null @@ -1,30 +0,0 @@ -import unreal - -ayon_detected = True -try: - from openpype.pipeline import install_host - from openpype.hosts.unreal.api import UnrealHost - - ayon_host = UnrealHost() -except ImportError as exc: - ayon_host = None - ayon_detected = False - unreal.log_error(f"OpenPype: cannot load Ayon [ {exc} ]") - -if ayon_detected: - install_host(ayon_host) - - -@unreal.uclass() -class AyonIntegration(unreal.AyonPythonBridge): - @unreal.ufunction(override=True) - def RunInPython_Popup(self): - unreal.log_warning("Ayon: showing tools popup") - if ayon_detected: - ayon_host.show_tools_popup() - - @unreal.ufunction(override=True) - def RunInPython_Dialog(self): - unreal.log_warning("Ayon: showing tools dialog") - if ayon_detected: - ayon_host.show_tools_dialog() diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/README.md b/openpype/hosts/unreal/integration/UE_4.27/Ayon/README.md deleted file mode 100644 index 77ae8c7e98..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/Ayon/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Ayon Unreal Integration plugin - UE 4.x - -This is plugin for Unreal Editor, creating menu for [Ayon](https://github.com/ynput/OpenPype) tools to run. diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Resources/ayon128.png b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Resources/ayon128.png deleted file mode 100644 index 799d849aa3163ecb16be39c641a6ac30324906b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2358 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 D;gw7~ diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Resources/ayon512.png b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Resources/ayon512.png deleted file mode 100644 index 990d5917e232a0644820428fb2790943de5ffaa4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16705 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"LevelEditor"); - - TSharedPtr MenuExtender = MakeShareable(new FExtender()); - TSharedPtr ToolbarExtender = MakeShareable(new FExtender()); - - MenuExtender->AddMenuExtension( - "LevelEditor", - EExtensionHook::After, - NULL, - FMenuExtensionDelegate::CreateRaw(this, &FAyonModule::AddMenuEntry) - ); - ToolbarExtender->AddToolBarExtension( - "Settings", - EExtensionHook::After, - NULL, - FToolBarExtensionDelegate::CreateRaw(this, &FAyonModule::AddToobarEntry)); - - - LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender); - LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender); - - RegisterSettings(); - } -} - -void FAyonModule::ShutdownModule() -{ - FAyonStyle::Shutdown(); -} - - -void FAyonModule::AddMenuEntry(FMenuBuilder& MenuBuilder) -{ - // Create Section - MenuBuilder.BeginSection("Ayon", TAttribute(FText::FromString("Ayon"))); - { - // Create a Submenu inside of the Section - MenuBuilder.AddMenuEntry( - FText::FromString("Tools..."), - FText::FromString("Pipeline tools"), - FSlateIcon(FAyonStyle::GetStyleSetName(), "Ayon.Logo"), - FUIAction(FExecuteAction::CreateRaw(this, &FAyonModule::MenuPopup)) - ); - - MenuBuilder.AddMenuEntry( - FText::FromString("Tools dialog..."), - FText::FromString("Pipeline tools dialog"), - FSlateIcon(FAyonStyle::GetStyleSetName(), "Ayon.Logo"), - FUIAction(FExecuteAction::CreateRaw(this, &FAyonModule::MenuDialog)) - ); - } - MenuBuilder.EndSection(); -} - -void FAyonModule::AddToobarEntry(FToolBarBuilder& ToolbarBuilder) -{ - ToolbarBuilder.BeginSection(TEXT("Ayon")); - { - ToolbarBuilder.AddToolBarButton( - FUIAction( - FExecuteAction::CreateRaw(this, &FAyonModule::MenuPopup), - NULL, - FIsActionChecked() - - ), - NAME_None, - LOCTEXT("Ayon_label", "Ayon"), - LOCTEXT("Ayon_tooltip", "Ayon Tools"), - FSlateIcon(FAyonStyle::GetStyleSetName(), "Ayon.Logo") - ); - } - ToolbarBuilder.EndSection(); -} - -void FAyonModule::RegisterSettings() -{ - ISettingsModule& SettingsModule = FModuleManager::LoadModuleChecked("Settings"); - - // Create the new category - // TODO: After the movement of the plugin from the game to editor, it might be necessary to move this! - ISettingsContainerPtr SettingsContainer = SettingsModule.GetContainer("Project"); - - UAyonSettings* Settings = GetMutableDefault(); - - // Register the settings - ISettingsSectionPtr SettingsSection = SettingsModule.RegisterSettings("Project", "Ayon", "General", - LOCTEXT("RuntimeGeneralSettingsName", - "General"), - LOCTEXT("RuntimeGeneralSettingsDescription", - "Base configuration for Open Pype Module"), - Settings - ); - - // Register the save handler to your settings, you might want to use it to - // validate those or just act to settings changes. - if (SettingsSection.IsValid()) - { - SettingsSection->OnModified().BindRaw(this, &FAyonModule::HandleSettingsSaved); - } -} - -bool FAyonModule::HandleSettingsSaved() -{ - UAyonSettings* Settings = GetMutableDefault(); - bool ResaveSettings = false; - - // You can put any validation code in here and resave the settings in case an invalid - // value has been entered - - if (ResaveSettings) - { - Settings->SaveConfig(); - } - - return true; -} - - -void FAyonModule::MenuPopup() -{ - UAyonPythonBridge* bridge = UAyonPythonBridge::Get(); - bridge->RunInPython_Popup(); -} - -void FAyonModule::MenuDialog() -{ - UAyonPythonBridge* bridge = UAyonPythonBridge::Get(); - bridge->RunInPython_Dialog(); -} - -IMPLEMENT_MODULE(FAyonModule, Ayon) diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonAssetContainer.cpp b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonAssetContainer.cpp deleted file mode 100644 index e3989eb03c..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonAssetContainer.cpp +++ /dev/null @@ -1,114 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#include "AyonAssetContainer.h" -#include "AssetRegistryModule.h" -#include "Misc/PackageName.h" -#include "Containers/UnrealString.h" - -UAyonAssetContainer::UAyonAssetContainer(const FObjectInitializer& ObjectInitializer) -: UAssetUserData(ObjectInitializer) -{ - FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); - FString path = UAyonAssetContainer::GetPathName(); - UE_LOG(LogTemp, Warning, TEXT("UAyonAssetContainer %s"), *path); - FARFilter Filter; - Filter.PackagePaths.Add(FName(*path)); - - AssetRegistryModule.Get().OnAssetAdded().AddUObject(this, &UAyonAssetContainer::OnAssetAdded); - AssetRegistryModule.Get().OnAssetRemoved().AddUObject(this, &UAyonAssetContainer::OnAssetRemoved); - AssetRegistryModule.Get().OnAssetRenamed().AddUObject(this, &UAyonAssetContainer::OnAssetRenamed); -} - -void UAyonAssetContainer::OnAssetAdded(const FAssetData& AssetData) -{ - TArray split; - - // get directory of current container - FString selfFullPath = UAyonAssetContainer::GetPathName(); - FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath); - - // get asset path and class - FString assetPath = AssetData.GetFullName(); - FString assetFName = AssetData.AssetClass.ToString(); - - // split path - assetPath.ParseIntoArray(split, TEXT(" "), true); - - FString assetDir = FPackageName::GetLongPackagePath(*split[1]); - - // take interest only in paths starting with path of current container - if (assetDir.StartsWith(*selfDir)) - { - // exclude self - if (assetFName != "AyonAssetContainer") - { - assets.Add(assetPath); - assetsData.Add(AssetData); - UE_LOG(LogTemp, Log, TEXT("%s: asset added to %s"), *selfFullPath, *selfDir); - } - } -} - -void UAyonAssetContainer::OnAssetRemoved(const FAssetData& AssetData) -{ - TArray split; - - // get directory of current container - FString selfFullPath = UAyonAssetContainer::GetPathName(); - FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath); - - // get asset path and class - FString assetPath = AssetData.GetFullName(); - FString assetFName = AssetData.AssetClass.ToString(); - - // split path - assetPath.ParseIntoArray(split, TEXT(" "), true); - - FString assetDir = FPackageName::GetLongPackagePath(*split[1]); - - // take interest only in paths starting with path of current container - FString path = UAyonAssetContainer::GetPathName(); - FString lpp = FPackageName::GetLongPackagePath(*path); - - if (assetDir.StartsWith(*selfDir)) - { - // exclude self - if (assetFName != "AyonAssetContainer") - { - // UE_LOG(LogTemp, Warning, TEXT("%s: asset removed"), *lpp); - assets.Remove(assetPath); - assetsData.Remove(AssetData); - } - } -} - -void UAyonAssetContainer::OnAssetRenamed(const FAssetData& AssetData, const FString& str) -{ - TArray split; - - // get directory of current container - FString selfFullPath = UAyonAssetContainer::GetPathName(); - FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath); - - // get asset path and class - FString assetPath = AssetData.GetFullName(); - FString assetFName = AssetData.AssetClass.ToString(); - - // split path - assetPath.ParseIntoArray(split, TEXT(" "), true); - - FString assetDir = FPackageName::GetLongPackagePath(*split[1]); - if (assetDir.StartsWith(*selfDir)) - { - // exclude self - if (assetFName != "AyonAssetContainer") - { - - assets.Remove(str); - assets.Add(assetPath); - assetsData.Remove(AssetData); - // UE_LOG(LogTemp, Warning, TEXT("%s: asset renamed %s"), *lpp, *str); - } - } -} - diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonAssetContainerFactory.cpp b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonAssetContainerFactory.cpp deleted file mode 100644 index 086fc1036e..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonAssetContainerFactory.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include "AyonAssetContainerFactory.h" -#include "AyonAssetContainer.h" - -UAyonAssetContainerFactory::UAyonAssetContainerFactory(const FObjectInitializer& ObjectInitializer) - : UFactory(ObjectInitializer) -{ - SupportedClass = UAyonAssetContainer::StaticClass(); - bCreateNew = false; - bEditorImport = true; -} - -UObject* UAyonAssetContainerFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) -{ - UAyonAssetContainer* AssetContainer = NewObject(InParent, Class, Name, Flags); - return AssetContainer; -} - -bool UAyonAssetContainerFactory::ShouldShowInNewMenu() const { - return false; -} diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonLib.cpp b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonLib.cpp deleted file mode 100644 index bff99caee3..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonLib.cpp +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#include "AyonLib.h" - -#include "AssetViewUtils.h" -#include "Misc/Paths.h" -#include "Misc/ConfigCacheIni.h" -#include "UObject/UnrealType.h" - -/** - * Sets color on folder icon on given path - * @param InPath - path to folder - * @param InFolderColor - color of the folder - * @warning This color will appear only after Editor restart. Is there a better way? - */ - -bool UAyonLib::SetFolderColor(const FString& FolderPath, const FLinearColor& FolderColor, const bool& bForceAdd) -{ - if (AssetViewUtils::DoesFolderExist(FolderPath)) - { - const TSharedPtr LinearColor = MakeShared(FolderColor); - - AssetViewUtils::SaveColor(FolderPath, LinearColor, true); - UE_LOG(LogAssetData, Display, TEXT("A color {%s} has been set to folder \"%s\""), *LinearColor->ToString(), - *FolderPath) - return true; - } - - UE_LOG(LogAssetData, Display, TEXT("Setting a color {%s} to folder \"%s\" has failed! Directory doesn't exist!"), - *FolderColor.ToString(), *FolderPath) - return false; -} - -/** - * Returns all poperties on given object - * @param cls - class - * @return TArray of properties - */ -TArray UAyonLib::GetAllProperties(UClass* cls) -{ - TArray Ret; - if (cls != nullptr) - { - for (TFieldIterator It(cls); It; ++It) - { - FProperty* Property = *It; - if (Property->HasAnyPropertyFlags(EPropertyFlags::CPF_Edit)) - { - Ret.Add(Property->GetName()); - } - } - } - return Ret; -} diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonPublishInstance.cpp b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonPublishInstance.cpp deleted file mode 100644 index d7550e2ed1..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonPublishInstance.cpp +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -// Deprecation warning: this is left here just for backwards compatibility -// and will be removed in next versions of Ayon. -#pragma once - -#include "AyonPublishInstance.h" -#include "AssetRegistryModule.h" -#include "AyonLib.h" -#include "AyonSettings.h" -#include "Framework/Notifications/NotificationManager.h" -#include "Widgets/Notifications/SNotificationList.h" - -//Moves all the invalid pointers to the end to prepare them for the shrinking -#define REMOVE_INVALID_ENTRIES(VAR) VAR.CompactStable(); \ - VAR.Shrink(); - -UAyonPublishInstance::UAyonPublishInstance(const FObjectInitializer& ObjectInitializer) - : UPrimaryDataAsset(ObjectInitializer) -{ - const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked< - FAssetRegistryModule>("AssetRegistry"); - - const FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked( - "PropertyEditor"); - - FString Left, Right; - GetPathName().Split("/" + GetName(), &Left, &Right); - - FARFilter Filter; - Filter.PackagePaths.Emplace(FName(Left)); - - TArray FoundAssets; - AssetRegistryModule.GetRegistry().GetAssets(Filter, FoundAssets); - - for (const FAssetData& AssetData : FoundAssets) - OnAssetCreated(AssetData); - - REMOVE_INVALID_ENTRIES(AssetDataInternal) - REMOVE_INVALID_ENTRIES(AssetDataExternal) - - AssetRegistryModule.Get().OnAssetAdded().AddUObject(this, &UAyonPublishInstance::OnAssetCreated); - AssetRegistryModule.Get().OnAssetRemoved().AddUObject(this, &UAyonPublishInstance::OnAssetRemoved); - AssetRegistryModule.Get().OnAssetUpdated().AddUObject(this, &UAyonPublishInstance::OnAssetUpdated); - -#ifdef WITH_EDITOR - ColorAyonDirs(); -#endif - -} - -void UAyonPublishInstance::OnAssetCreated(const FAssetData& InAssetData) -{ - TArray split; - - UObject* Asset = InAssetData.GetAsset(); - - if (!IsValid(Asset)) - { - UE_LOG(LogAssetData, Warning, TEXT("Asset \"%s\" is not valid! Skipping the addition."), - *InAssetData.ObjectPath.ToString()); - return; - } - - const bool result = IsUnderSameDir(Asset) && Cast(Asset) == nullptr; - - if (result) - { - if (AssetDataInternal.Emplace(Asset).IsValidId()) - { - UE_LOG(LogTemp, Log, TEXT("Added an Asset to PublishInstance - Publish Instance: %s, Asset %s"), - *this->GetName(), *Asset->GetName()); - } - } -} - -void UAyonPublishInstance::OnAssetRemoved(const FAssetData& InAssetData) -{ - if (Cast(InAssetData.GetAsset()) == nullptr) - { - if (AssetDataInternal.Contains(nullptr)) - { - AssetDataInternal.Remove(nullptr); - REMOVE_INVALID_ENTRIES(AssetDataInternal) - } - else - { - AssetDataExternal.Remove(nullptr); - REMOVE_INVALID_ENTRIES(AssetDataExternal) - } - } -} - -void UAyonPublishInstance::OnAssetUpdated(const FAssetData& InAssetData) -{ - REMOVE_INVALID_ENTRIES(AssetDataInternal); - REMOVE_INVALID_ENTRIES(AssetDataExternal); -} - -bool UAyonPublishInstance::IsUnderSameDir(const UObject* InAsset) const -{ - FString ThisLeft, ThisRight; - this->GetPathName().Split(this->GetName(), &ThisLeft, &ThisRight); - - return InAsset->GetPathName().StartsWith(ThisLeft); -} - -#ifdef WITH_EDITOR - -void UAyonPublishInstance::ColorAyonDirs() -{ - FString PathName = this->GetPathName(); - - //Check whether the path contains the defined Ayon folder - if (!PathName.Contains(TEXT("Ayon"))) return; - - //Get the base path for open pype - FString PathLeft, PathRight; - PathName.Split(FString("Ayon"), &PathLeft, &PathRight); - - if (PathLeft.IsEmpty() || PathRight.IsEmpty()) - { - UE_LOG(LogAssetData, Error, TEXT("Failed to retrieve the base Ayon directory!")) - return; - } - - PathName.RemoveFromEnd(PathRight, ESearchCase::CaseSensitive); - - //Get the current settings - const UAyonSettings* Settings = GetMutableDefault(); - - //Color the base folder - UAyonLib::SetFolderColor(PathName, Settings->GetFolderFColor(), false); - - //Get Sub paths, iterate through them and color them according to the folder color in UAyonSettings - const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked( - "AssetRegistry"); - - TArray PathList; - - AssetRegistryModule.Get().GetSubPaths(PathName, PathList, true); - - if (PathList.Num() > 0) - { - for (const FString& Path : PathList) - { - UAyonLib::SetFolderColor(Path, Settings->GetFolderFColor(), false); - } - } -} - -void UAyonPublishInstance::SendNotification(const FString& Text) const -{ - FNotificationInfo Info{FText::FromString(Text)}; - - Info.bFireAndForget = true; - Info.bUseLargeFont = false; - Info.bUseThrobber = false; - Info.bUseSuccessFailIcons = false; - Info.ExpireDuration = 4.f; - Info.FadeOutDuration = 2.f; - - FSlateNotificationManager::Get().AddNotification(Info); - - UE_LOG(LogAssetData, Warning, - TEXT( - "Removed duplicated asset from the AssetsDataExternal in Container \"%s\", Asset is already included in the AssetDataInternal!" - ), *GetName() - ) -} - - -void UAyonPublishInstance::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) -{ - Super::PostEditChangeProperty(PropertyChangedEvent); - - if (PropertyChangedEvent.ChangeType == EPropertyChangeType::ValueSet && - PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED( - UAyonPublishInstance, AssetDataExternal)) - { - // Check for duplicated assets - for (const auto& Asset : AssetDataInternal) - { - if (AssetDataExternal.Contains(Asset)) - { - AssetDataExternal.Remove(Asset); - return SendNotification( - "You are not allowed to add assets into AssetDataExternal which are already included in AssetDataInternal!"); - } - } - - // Check if no UAyonPublishInstance type assets are included - for (const auto& Asset : AssetDataExternal) - { - if (Cast(Asset.Get()) != nullptr) - { - AssetDataExternal.Remove(Asset); - return SendNotification("You are not allowed to add publish instances!"); - } - } - } -} - -#endif diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonPublishInstanceFactory.cpp b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonPublishInstanceFactory.cpp deleted file mode 100644 index f79c428a6d..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonPublishInstanceFactory.cpp +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -// Deprecation warning: this is left here just for backwards compatibility -// and will be removed in next versions of Ayon. -#include "AyonPublishInstanceFactory.h" -#include "AyonPublishInstance.h" - -UAyonPublishInstanceFactory::UAyonPublishInstanceFactory(const FObjectInitializer& ObjectInitializer) - : UFactory(ObjectInitializer) -{ - SupportedClass = UAyonPublishInstance::StaticClass(); - bCreateNew = false; - bEditorImport = true; -} - -UObject* UAyonPublishInstanceFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) -{ - check(InClass->IsChildOf(UAyonPublishInstance::StaticClass())); - return NewObject(InParent, InClass, InName, Flags); -} - -bool UAyonPublishInstanceFactory::ShouldShowInNewMenu() const { - return false; -} diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonPythonBridge.cpp b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonPythonBridge.cpp deleted file mode 100644 index 0ed4b2f704..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonPythonBridge.cpp +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#include "AyonPythonBridge.h" - -UAyonPythonBridge* UAyonPythonBridge::Get() -{ - TArray AyonPythonBridgeClasses; - GetDerivedClasses(UAyonPythonBridge::StaticClass(), AyonPythonBridgeClasses); - int32 NumClasses = AyonPythonBridgeClasses.Num(); - if (NumClasses > 0) - { - return Cast(AyonPythonBridgeClasses[NumClasses - 1]->GetDefaultObject()); - } - return nullptr; -}; \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonSettings.cpp b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonSettings.cpp deleted file mode 100644 index 509b7268ba..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonSettings.cpp +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. - -#include "AyonSettings.h" - -#include "Interfaces/IPluginManager.h" - -/** - * Mainly is used for initializing default values if the DefaultAyonSettings.ini file does not exist in the saved config - */ -UAyonSettings::UAyonSettings(const FObjectInitializer& ObjectInitializer) -{ - - const FString ConfigFilePath = AYON_SETTINGS_FILEPATH; - - // This has to be probably in the future set using the UE Reflection system - FColor Color; - GConfig->GetColor(TEXT("/Script/Ayon.AyonSettings"), TEXT("FolderColor"), Color, ConfigFilePath); - - FolderColor = Color; -} diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonStyle.cpp b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonStyle.cpp deleted file mode 100644 index b133225fd5..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonStyle.cpp +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#include "AyonStyle.h" -#include "Framework/Application/SlateApplication.h" -#include "Styling/SlateStyle.h" -#include "Styling/SlateStyleRegistry.h" - - -TUniquePtr< FSlateStyleSet > FAyonStyle::AyonStyleInstance = nullptr; - -void FAyonStyle::Initialize() -{ - if (!AyonStyleInstance.IsValid()) - { - AyonStyleInstance = Create(); - FSlateStyleRegistry::RegisterSlateStyle(*AyonStyleInstance); - } -} - -void FAyonStyle::Shutdown() -{ - if (AyonStyleInstance.IsValid()) - { - FSlateStyleRegistry::UnRegisterSlateStyle(*AyonStyleInstance); - AyonStyleInstance.Reset(); - } -} - -FName FAyonStyle::GetStyleSetName() -{ - static FName StyleSetName(TEXT("AyonStyle")); - return StyleSetName; -} - -FName FAyonStyle::GetContextName() -{ - static FName ContextName(TEXT("Ayon")); - return ContextName; -} - -#define IMAGE_BRUSH(RelativePath, ...) FSlateImageBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) - -const FVector2D Icon40x40(40.0f, 40.0f); - -TUniquePtr< FSlateStyleSet > FAyonStyle::Create() -{ - TUniquePtr< FSlateStyleSet > Style = MakeUnique(GetStyleSetName()); - Style->SetContentRoot(FPaths::EnginePluginsDir() / TEXT("Marketplace/Ayon/Resources")); - - return Style; -} - -void FAyonStyle::SetIcon(const FString& StyleName, const FString& ResourcePath) -{ - FSlateStyleSet* Style = AyonStyleInstance.Get(); - - FString Name(GetContextName().ToString()); - Name = Name + "." + StyleName; - Style->Set(*Name, new FSlateImageBrush(Style->RootToContentDir(ResourcePath, TEXT(".png")), Icon40x40)); - - - FSlateApplication::Get().GetRenderer()->ReloadTextureResources(); -} - -#undef IMAGE_BRUSH - -const ISlateStyle& FAyonStyle::Get() -{ - check(AyonStyleInstance); - return *AyonStyleInstance; -} diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/Commandlets/AyonActionResult.cpp b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/Commandlets/AyonActionResult.cpp deleted file mode 100644 index 49376e8648..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/Commandlets/AyonActionResult.cpp +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. - - -#include "Commandlets/AyonActionResult.h" -#include "Logging/Ayon_Log.h" - -EAyon_ActionResult::Type& FAyon_ActionResult::GetStatus() -{ - return Status; -} - -FText& FAyon_ActionResult::GetReason() -{ - return Reason; -} - -FAyon_ActionResult::FAyon_ActionResult():Status(EAyon_ActionResult::Type::Ok) -{ - -} - -FAyon_ActionResult::FAyon_ActionResult(const EAyon_ActionResult::Type& InEnum):Status(InEnum) -{ - TryLog(); -} - -FAyon_ActionResult::FAyon_ActionResult(const EAyon_ActionResult::Type& InEnum, const FText& InReason):Status(InEnum), Reason(InReason) -{ - TryLog(); -}; - -bool FAyon_ActionResult::IsProblem() const -{ - return Status != EAyon_ActionResult::Ok; -} - -void FAyon_ActionResult::TryLog() const -{ - if(IsProblem()) - UE_LOG(LogCommandletOPGenerateProject, Error, TEXT("%s"), *Reason.ToString()); -} diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/Commandlets/Implementations/AyonGenerateProjectCommandlet.cpp b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/Commandlets/Implementations/AyonGenerateProjectCommandlet.cpp deleted file mode 100644 index 0328d3b7e6..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/Commandlets/Implementations/AyonGenerateProjectCommandlet.cpp +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#include "Commandlets/Implementations/AyonGenerateProjectCommandlet.h" - -#include "Editor.h" -#include "GameProjectUtils.h" -#include "AyonConstants.h" -#include "Commandlets/AyonActionResult.h" -#include "ProjectDescriptor.h" - -int32 UAyonGenerateProjectCommandlet::Main(const FString& CommandLineParams) -{ - //Parses command line parameters & creates structure FProjectInformation - const FAyonGenerateProjectParams ParsedParams = FAyonGenerateProjectParams(CommandLineParams); - ProjectInformation = ParsedParams.GenerateUEProjectInformation(); - - //Creates .uproject & other UE files - EVALUATE_AYON_ACTION_RESULT(TryCreateProject()); - - //Loads created .uproject - EVALUATE_AYON_ACTION_RESULT(TryLoadProjectDescriptor()); - - //Adds needed plugin to .uproject - AttachPluginsToProjectDescriptor(); - - //Saves .uproject - EVALUATE_AYON_ACTION_RESULT(TrySave()); - - //When we are here, there should not be problems in generating Unreal Project for Ayon - return 0; -} - - -FAyonGenerateProjectParams::FAyonGenerateProjectParams(): FAyonGenerateProjectParams("") -{ -} - -FAyonGenerateProjectParams::FAyonGenerateProjectParams(const FString& CommandLineParams): CommandLineParams( - CommandLineParams) -{ - UCommandlet::ParseCommandLine(*CommandLineParams, Tokens, Switches); -} - -FProjectInformation FAyonGenerateProjectParams::GenerateUEProjectInformation() const -{ - FProjectInformation ProjectInformation = FProjectInformation(); - ProjectInformation.ProjectFilename = GetProjectFileName(); - - ProjectInformation.bShouldGenerateCode = IsSwitchPresent("GenerateCode"); - - return ProjectInformation; -} - -FString FAyonGenerateProjectParams::TryGetToken(const int32 Index) const -{ - return Tokens.IsValidIndex(Index) ? Tokens[Index] : ""; -} - -FString FAyonGenerateProjectParams::GetProjectFileName() const -{ - return TryGetToken(0); -} - -bool FAyonGenerateProjectParams::IsSwitchPresent(const FString& Switch) const -{ - return INDEX_NONE != Switches.IndexOfByPredicate([&Switch](const FString& Item) -> bool - { - return Item.Equals(Switch); - } - ); -} - - -UAyonGenerateProjectCommandlet::UAyonGenerateProjectCommandlet() -{ - LogToConsole = true; -} - -FAyon_ActionResult UAyonGenerateProjectCommandlet::TryCreateProject() const -{ - FText FailReason; - FText FailLog; - TArray OutCreatedFiles; - - if (!GameProjectUtils::CreateProject(ProjectInformation, FailReason, FailLog, &OutCreatedFiles)) - return FAyon_ActionResult(EAyon_ActionResult::ProjectNotCreated, FailReason); - return FAyon_ActionResult(); -} - -FAyon_ActionResult UAyonGenerateProjectCommandlet::TryLoadProjectDescriptor() -{ - FText FailReason; - const bool bLoaded = ProjectDescriptor.Load(ProjectInformation.ProjectFilename, FailReason); - - return FAyon_ActionResult(bLoaded ? EAyon_ActionResult::Ok : EAyon_ActionResult::ProjectNotLoaded, FailReason); -} - -void UAyonGenerateProjectCommandlet::AttachPluginsToProjectDescriptor() -{ - FPluginReferenceDescriptor AyonPluginDescriptor; - AyonPluginDescriptor.bEnabled = true; - AyonPluginDescriptor.Name = AyonConstants::Ayon_PluginName; - ProjectDescriptor.Plugins.Add(AyonPluginDescriptor); - - FPluginReferenceDescriptor PythonPluginDescriptor; - PythonPluginDescriptor.bEnabled = true; - PythonPluginDescriptor.Name = AyonConstants::PythonScript_PluginName; - ProjectDescriptor.Plugins.Add(PythonPluginDescriptor); - - FPluginReferenceDescriptor SequencerScriptingPluginDescriptor; - SequencerScriptingPluginDescriptor.bEnabled = true; - SequencerScriptingPluginDescriptor.Name = AyonConstants::SequencerScripting_PluginName; - ProjectDescriptor.Plugins.Add(SequencerScriptingPluginDescriptor); - - FPluginReferenceDescriptor MovieRenderPipelinePluginDescriptor; - MovieRenderPipelinePluginDescriptor.bEnabled = true; - MovieRenderPipelinePluginDescriptor.Name = AyonConstants::MovieRenderPipeline_PluginName; - ProjectDescriptor.Plugins.Add(MovieRenderPipelinePluginDescriptor); - - FPluginReferenceDescriptor EditorScriptingPluginDescriptor; - EditorScriptingPluginDescriptor.bEnabled = true; - EditorScriptingPluginDescriptor.Name = AyonConstants::EditorScriptingUtils_PluginName; - ProjectDescriptor.Plugins.Add(EditorScriptingPluginDescriptor); -} - -FAyon_ActionResult UAyonGenerateProjectCommandlet::TrySave() -{ - FText FailReason; - const bool bSaved = ProjectDescriptor.Save(ProjectInformation.ProjectFilename, FailReason); - - return FAyon_ActionResult(bSaved ? EAyon_ActionResult::Ok : EAyon_ActionResult::ProjectNotSaved, FailReason); -} - -FAyonGenerateProjectParams UAyonGenerateProjectCommandlet::ParseParameters(const FString& Params) const -{ - FAyonGenerateProjectParams ParamsResult; - - TArray Tokens, Switches; - ParseCommandLine(*Params, Tokens, Switches); - - return ParamsResult; -} diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/OpenPypePublishInstance.cpp b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/OpenPypePublishInstance.cpp deleted file mode 100644 index 320285591e..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/OpenPypePublishInstance.cpp +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -// Deprecation warning: this is left here just for backwards compatibility -// and will be removed in next versions of Ayon. -#pragma once - -#include "OpenPypePublishInstance.h" -#include "AssetRegistryModule.h" -#include "AyonLib.h" -#include "AyonSettings.h" -#include "Framework/Notifications/NotificationManager.h" -#include "Widgets/Notifications/SNotificationList.h" - -//Moves all the invalid pointers to the end to prepare them for the shrinking -#define REMOVE_INVALID_ENTRIES(VAR) VAR.CompactStable(); \ - VAR.Shrink(); - -UOpenPypePublishInstance::UOpenPypePublishInstance(const FObjectInitializer& ObjectInitializer) - : UPrimaryDataAsset(ObjectInitializer) -{ - const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked< - FAssetRegistryModule>("AssetRegistry"); - - const FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked( - "PropertyEditor"); - - FString Left, Right; - GetPathName().Split("/" + GetName(), &Left, &Right); - - FARFilter Filter; - Filter.PackagePaths.Emplace(FName(Left)); - - TArray FoundAssets; - AssetRegistryModule.GetRegistry().GetAssets(Filter, FoundAssets); - - for (const FAssetData& AssetData : FoundAssets) - OnAssetCreated(AssetData); - - REMOVE_INVALID_ENTRIES(AssetDataInternal) - REMOVE_INVALID_ENTRIES(AssetDataExternal) - - AssetRegistryModule.Get().OnAssetAdded().AddUObject(this, &UOpenPypePublishInstance::OnAssetCreated); - AssetRegistryModule.Get().OnAssetRemoved().AddUObject(this, &UOpenPypePublishInstance::OnAssetRemoved); - AssetRegistryModule.Get().OnAssetUpdated().AddUObject(this, &UOpenPypePublishInstance::OnAssetUpdated); - -#ifdef WITH_EDITOR - ColorOpenPypeDirs(); -#endif - -} - -void UOpenPypePublishInstance::OnAssetCreated(const FAssetData& InAssetData) -{ - TArray split; - - UObject* Asset = InAssetData.GetAsset(); - - if (!IsValid(Asset)) - { - UE_LOG(LogAssetData, Warning, TEXT("Asset \"%s\" is not valid! Skipping the addition."), - *InAssetData.ObjectPath.ToString()); - return; - } - - const bool result = IsUnderSameDir(Asset) && Cast(Asset) == nullptr; - - if (result) - { - if (AssetDataInternal.Emplace(Asset).IsValidId()) - { - UE_LOG(LogTemp, Log, TEXT("Added an Asset to PublishInstance - Publish Instance: %s, Asset %s"), - *this->GetName(), *Asset->GetName()); - } - } -} - -void UOpenPypePublishInstance::OnAssetRemoved(const FAssetData& InAssetData) -{ - if (Cast(InAssetData.GetAsset()) == nullptr) - { - if (AssetDataInternal.Contains(nullptr)) - { - AssetDataInternal.Remove(nullptr); - REMOVE_INVALID_ENTRIES(AssetDataInternal) - } - else - { - AssetDataExternal.Remove(nullptr); - REMOVE_INVALID_ENTRIES(AssetDataExternal) - } - } -} - -void UOpenPypePublishInstance::OnAssetUpdated(const FAssetData& InAssetData) -{ - REMOVE_INVALID_ENTRIES(AssetDataInternal); - REMOVE_INVALID_ENTRIES(AssetDataExternal); -} - -bool UOpenPypePublishInstance::IsUnderSameDir(const UObject* InAsset) const -{ - FString ThisLeft, ThisRight; - this->GetPathName().Split(this->GetName(), &ThisLeft, &ThisRight); - - return InAsset->GetPathName().StartsWith(ThisLeft); -} - -#ifdef WITH_EDITOR - -void UOpenPypePublishInstance::ColorOpenPypeDirs() -{ - FString PathName = this->GetPathName(); - - //Check whether the path contains the defined OpenPype folder - if (!PathName.Contains(TEXT("OpenPype"))) return; - - //Get the base path for open pype - FString PathLeft, PathRight; - PathName.Split(FString("OpenPype"), &PathLeft, &PathRight); - - if (PathLeft.IsEmpty() || PathRight.IsEmpty()) - { - UE_LOG(LogAssetData, Error, TEXT("Failed to retrieve the base OpenPype directory!")) - return; - } - - PathName.RemoveFromEnd(PathRight, ESearchCase::CaseSensitive); - - //Get the current settings - const UAyonSettings* Settings = GetMutableDefault(); - - //Color the base folder - UAyonLib::SetFolderColor(PathName, Settings->GetFolderFColor(), false); - - //Get Sub paths, iterate through them and color them according to the folder color in UAyonSettings - const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked( - "AssetRegistry"); - - TArray PathList; - - AssetRegistryModule.Get().GetSubPaths(PathName, PathList, true); - - if (PathList.Num() > 0) - { - for (const FString& Path : PathList) - { - UAyonLib::SetFolderColor(Path, Settings->GetFolderFColor(), false); - } - } -} - -void UOpenPypePublishInstance::SendNotification(const FString& Text) const -{ - FNotificationInfo Info{FText::FromString(Text)}; - - Info.bFireAndForget = true; - Info.bUseLargeFont = false; - Info.bUseThrobber = false; - Info.bUseSuccessFailIcons = false; - Info.ExpireDuration = 4.f; - Info.FadeOutDuration = 2.f; - - FSlateNotificationManager::Get().AddNotification(Info); - - UE_LOG(LogAssetData, Warning, - TEXT( - "Removed duplicated asset from the AssetsDataExternal in Container \"%s\", Asset is already included in the AssetDataInternal!" - ), *GetName() - ) -} - - -void UOpenPypePublishInstance::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) -{ - Super::PostEditChangeProperty(PropertyChangedEvent); - - if (PropertyChangedEvent.ChangeType == EPropertyChangeType::ValueSet && - PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED( - UOpenPypePublishInstance, AssetDataExternal)) - { - // Check for duplicated assets - for (const auto& Asset : AssetDataInternal) - { - if (AssetDataExternal.Contains(Asset)) - { - AssetDataExternal.Remove(Asset); - return SendNotification( - "You are not allowed to add assets into AssetDataExternal which are already included in AssetDataInternal!"); - } - } - - // Check if no UOpenPypePublishInstance type assets are included - for (const auto& Asset : AssetDataExternal) - { - if (Cast(Asset.Get()) != nullptr) - { - AssetDataExternal.Remove(Asset); - return SendNotification("You are not allowed to add publish instances!"); - } - } - } -} - -#endif diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/Ayon.h b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/Ayon.h deleted file mode 100644 index d11af70058..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/Ayon.h +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. - -#pragma once - - -class FAyonModule : public IModuleInterface -{ -public: - virtual void StartupModule() override; - virtual void ShutdownModule() override; - -private: - void RegisterSettings(); - bool HandleSettingsSaved(); - - void AddMenuEntry(FMenuBuilder& MenuBuilder); - void AddToobarEntry(FToolBarBuilder& ToolbarBuilder); - void MenuPopup(); - void MenuDialog(); -}; diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonAssetContainer.h b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonAssetContainer.h deleted file mode 100644 index cc17b3960a..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonAssetContainer.h +++ /dev/null @@ -1,39 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/NoExportTypes.h" -#include "Engine/AssetUserData.h" -#include "AssetData.h" -#include "AyonAssetContainer.generated.h" - -/** - * - */ -UCLASS(Blueprintable) -class AYON_API UAyonAssetContainer : public UAssetUserData -{ - GENERATED_BODY() - -public: - - UAyonAssetContainer(const FObjectInitializer& ObjectInitalizer); - // ~UAyonAssetContainer(); - - UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Assets") - TArray assets; - - // There seems to be no reflection option to expose array of FAssetData - /* - UPROPERTY(Transient, BlueprintReadOnly, Category = "Python", meta=(DisplayName="Assets Data")) - TArray assetsData; - */ -private: - TArray assetsData; - void OnAssetAdded(const FAssetData& AssetData); - void OnAssetRemoved(const FAssetData& AssetData); - void OnAssetRenamed(const FAssetData& AssetData, const FString& str); -}; - - diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonAssetContainerFactory.h b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonAssetContainerFactory.h deleted file mode 100644 index 7c35897911..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonAssetContainerFactory.h +++ /dev/null @@ -1,21 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#pragma once - -#include "CoreMinimal.h" -#include "Factories/Factory.h" -#include "AyonAssetContainerFactory.generated.h" - -/** - * - */ -UCLASS() -class AYON_API UAyonAssetContainerFactory : public UFactory -{ - GENERATED_BODY() - -public: - UAyonAssetContainerFactory(const FObjectInitializer& ObjectInitializer); - virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; - virtual bool ShouldShowInNewMenu() const override; -}; \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonConstants.h b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonConstants.h deleted file mode 100644 index 6a02b5682f..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonConstants.h +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#pragma once - -#include "CoreMinimal.h" - -namespace AyonConstants -{ - const FString Ayon_PluginName = "Ayon"; - const FString PythonScript_PluginName = "PythonScriptPlugin"; - const FString SequencerScripting_PluginName = "SequencerScripting"; - const FString MovieRenderPipeline_PluginName = "MovieRenderPipeline"; - const FString EditorScriptingUtils_PluginName = "EditorScriptingUtilities"; -} - - diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonLib.h b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonLib.h deleted file mode 100644 index da83b448fb..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonLib.h +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#pragma once - -#include "AyonLib.generated.h" - - -UCLASS(Blueprintable) -class AYON_API UAyonLib : public UBlueprintFunctionLibrary -{ - - GENERATED_BODY() - -public: - UFUNCTION(BlueprintCallable, Category = Python) - static bool SetFolderColor(const FString& FolderPath, const FLinearColor& FolderColor,const bool& bForceAdd); - - UFUNCTION(BlueprintCallable, Category = Python) - static TArray GetAllProperties(UClass* cls); -}; \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonPublishInstance.h b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonPublishInstance.h deleted file mode 100644 index 0a0628c3ec..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonPublishInstance.h +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -// Deprecation warning: this is left here just for backwards compatibility -// and will be removed in next versions of Ayon. -#pragma once - -#include "AyonPublishInstance.generated.h" - - -UCLASS(Blueprintable) -class AYON_API UAyonPublishInstance : public UPrimaryDataAsset -{ - GENERATED_UCLASS_BODY() - -public: - /** - * Retrieves all the assets which are monitored by the Publish Instance (Monitors assets in the directory which is - * placed in) - * - * @return - Set of UObjects. Careful! They are returning raw pointers. Seems like an issue in UE5 - */ - UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") - TSet GetInternalAssets() const - { - //For some reason it can only return Raw Pointers? Seems like an issue which they haven't fixed. - TSet ResultSet; - - for (const auto& Asset : AssetDataInternal) - ResultSet.Add(Asset.LoadSynchronous()); - - return ResultSet; - } - - /** - * Retrieves all the assets which have been added manually by the Publish Instance - * - * @return - TSet of assets (UObjects). Careful! They are returning raw pointers. Seems like an issue in UE5 - */ - UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") - TSet GetExternalAssets() const - { - //For some reason it can only return Raw Pointers? Seems like an issue which they haven't fixed. - TSet ResultSet; - - for (const auto& Asset : AssetDataExternal) - ResultSet.Add(Asset.LoadSynchronous()); - - return ResultSet; - } - - /** - * Function for returning all the assets in the container combined. - * - * @return Returns all the internal and externally added assets into one set (TSet of UObjects). Careful! They are - * returning raw pointers. Seems like an issue in UE5 - * - * @attention If the bAddExternalAssets variable is false, external assets won't be included! - */ - UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") - TSet GetAllAssets() const - { - const TSet>& IteratedSet = bAddExternalAssets - ? AssetDataInternal.Union(AssetDataExternal) - : AssetDataInternal; - - //Create a new TSet only with raw pointers. - TSet ResultSet; - - for (auto& Asset : IteratedSet) - ResultSet.Add(Asset.LoadSynchronous()); - - return ResultSet; - } - -private: - UPROPERTY(VisibleAnywhere, Category="Assets") - TSet> AssetDataInternal; - - /** - * This property allows exposing the array to include other assets from any other directory than what it's currently - * monitoring. NOTE: that these assets have to be added manually! They are not automatically registered or added! - */ - UPROPERTY(EditAnywhere, Category = "Assets") - bool bAddExternalAssets = false; - - UPROPERTY(EditAnywhere, meta=(EditCondition="bAddExternalAssets"), Category="Assets") - TSet> AssetDataExternal; - - - void OnAssetCreated(const FAssetData& InAssetData); - void OnAssetRemoved(const FAssetData& InAssetData); - void OnAssetUpdated(const FAssetData& InAssetData); - - bool IsUnderSameDir(const UObject* InAsset) const; - -#ifdef WITH_EDITOR - - void ColorAyonDirs(); - - void SendNotification(const FString& Text) const; - virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; - -#endif -}; diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonPublishInstanceFactory.h b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonPublishInstanceFactory.h deleted file mode 100644 index 3cef8e76b2..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonPublishInstanceFactory.h +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -// Deprecation warning: this is left here just for backwards compatibility -// and will be removed in next versions of Ayon. -#pragma once - -#include "CoreMinimal.h" -#include "Factories/Factory.h" -#include "AyonPublishInstanceFactory.generated.h" - -/** - * - */ -UCLASS() -class AYON_API UAyonPublishInstanceFactory : public UFactory -{ - GENERATED_BODY() - -public: - UAyonPublishInstanceFactory(const FObjectInitializer& ObjectInitializer); - virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; - virtual bool ShouldShowInNewMenu() const override; -}; diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonPythonBridge.h b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonPythonBridge.h deleted file mode 100644 index 3c429fd7d3..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonPythonBridge.h +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#pragma once -#include "AyonPythonBridge.generated.h" - -UCLASS(Blueprintable) -class UAyonPythonBridge : public UObject -{ - GENERATED_BODY() - -public: - UFUNCTION(BlueprintCallable, Category = Python) - static UAyonPythonBridge* Get(); - - UFUNCTION(BlueprintImplementableEvent, Category = Python) - void RunInPython_Popup() const; - - UFUNCTION(BlueprintImplementableEvent, Category = Python) - void RunInPython_Dialog() const; - -}; diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonSettings.h b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonSettings.h deleted file mode 100644 index 7a93f107c5..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonSettings.h +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "AyonSettings.generated.h" - -#define AYON_SETTINGS_FILEPATH IPluginManager::Get().FindPlugin("Ayon")->GetBaseDir() / TEXT("Config") / TEXT("DefaultAyonSettings.ini") - -UCLASS(Config=AyonSettings, DefaultConfig) -class AYON_API UAyonSettings : public UObject -{ - GENERATED_UCLASS_BODY() - - UFUNCTION(BlueprintCallable, BlueprintPure, Category = Settings) - FColor GetFolderFColor() const - { - return FolderColor; - } - - UFUNCTION(BlueprintCallable, BlueprintPure, Category = Settings) - FLinearColor GetFolderFLinearColor() const - { - return FLinearColor(FolderColor); - } - -protected: - - UPROPERTY(config, EditAnywhere, Category = Folders) - FColor FolderColor = FColor(25,45,223); -}; diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonStyle.h b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonStyle.h deleted file mode 100644 index 188e4a510c..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonStyle.h +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#pragma once -#include "CoreMinimal.h" - -class FSlateStyleSet; -class ISlateStyle; - - -class FAyonStyle -{ -public: - static void Initialize(); - static void Shutdown(); - static const ISlateStyle& Get(); - static FName GetStyleSetName(); - static FName GetContextName(); - - static void SetIcon(const FString& StyleName, const FString& ResourcePath); - -private: - static TUniquePtr< FSlateStyleSet > Create(); - static TUniquePtr< FSlateStyleSet > AyonStyleInstance; -}; \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/Commandlets/AyonActionResult.h b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/Commandlets/AyonActionResult.h deleted file mode 100644 index 4694055164..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/Commandlets/AyonActionResult.h +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "AyonActionResult.generated.h" - -/** - * @brief This macro returns error code when is problem or does nothing when there is no problem. - * @param ActionResult FAyon_ActionResult structure - */ -#define EVALUATE_AYON_ACTION_RESULT(ActionResult) \ - if(ActionResult.IsProblem()) \ - return ActionResult.GetStatus(); - -/** -* @brief This enum values are humanly readable mapping of error codes. -* Here should be all error codes to be possible find what went wrong. -* TODO: In the future should exists an web document where is mapped error code & what problem occured & how to repair it... -*/ -UENUM() -namespace EAyon_ActionResult -{ - enum Type - { - Ok, - ProjectNotCreated, - ProjectNotLoaded, - ProjectNotSaved, - //....Here insert another values - - //Do not remove! - //Usable for looping through enum values - __Last UMETA(Hidden) - }; -} - - -/** - * @brief This struct holds action result enum and optionally reason of fail - */ -USTRUCT() -struct FAyon_ActionResult -{ - GENERATED_BODY() - -public: - /** @brief Default constructor usable when there is no problem */ - FAyon_ActionResult(); - - /** - * @brief This constructor initializes variables & attempts to log when is error - * @param InEnum Status - */ - FAyon_ActionResult(const EAyon_ActionResult::Type& InEnum); - - /** - * @brief This constructor initializes variables & attempts to log when is error - * @param InEnum Status - * @param InReason Reason of potential fail - */ - FAyon_ActionResult(const EAyon_ActionResult::Type& InEnum, const FText& InReason); - -private: - /** @brief Action status */ - EAyon_ActionResult::Type Status; - - /** @brief Optional reason of fail */ - FText Reason; - -public: - /** - * @brief Checks if there is problematic state - * @return true when status is not equal to EAyon_ActionResult::Ok - */ - bool IsProblem() const; - EAyon_ActionResult::Type& GetStatus(); - FText& GetReason(); - -private: - void TryLog() const; -}; - diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/Commandlets/Implementations/AyonGenerateProjectCommandlet.h b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/Commandlets/Implementations/AyonGenerateProjectCommandlet.h deleted file mode 100644 index cabd524b8c..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/Commandlets/Implementations/AyonGenerateProjectCommandlet.h +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#pragma once - -#include "GameProjectUtils.h" -#include "Commandlets/AyonActionResult.h" -#include "ProjectDescriptor.h" -#include "Commandlets/Commandlet.h" -#include "AyonGenerateProjectCommandlet.generated.h" - -struct FProjectDescriptor; -struct FProjectInformation; - -/** -* @brief Structure which parses command line parameters and generates FProjectInformation -*/ -USTRUCT() -struct FAyonGenerateProjectParams -{ - GENERATED_BODY() - -private: - FString CommandLineParams; - TArray Tokens; - TArray Switches; - -public: - FAyonGenerateProjectParams(); - FAyonGenerateProjectParams(const FString& CommandLineParams); - - FProjectInformation GenerateUEProjectInformation() const; - -private: - FString TryGetToken(const int32 Index) const; - FString GetProjectFileName() const; - - bool IsSwitchPresent(const FString& Switch) const; -}; - -UCLASS() -class AYON_API UAyonGenerateProjectCommandlet : public UCommandlet -{ - GENERATED_BODY() - -private: - FProjectInformation ProjectInformation; - FProjectDescriptor ProjectDescriptor; - -public: - UAyonGenerateProjectCommandlet(); - - virtual int32 Main(const FString& CommandLineParams) override; - -private: - FAyonGenerateProjectParams ParseParameters(const FString& Params) const; - FAyon_ActionResult TryCreateProject() const; - FAyon_ActionResult TryLoadProjectDescriptor(); - void AttachPluginsToProjectDescriptor(); - FAyon_ActionResult TrySave(); -}; - diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/Logging/Ayon_Log.h b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/Logging/Ayon_Log.h deleted file mode 100644 index 21571afd02..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/Logging/Ayon_Log.h +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#pragma once - -DEFINE_LOG_CATEGORY_STATIC(LogCommandletOPGenerateProject, Log, All); diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/OpenPypePublishInstance.h b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/OpenPypePublishInstance.h deleted file mode 100644 index 4a7a6a3a9f..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/OpenPypePublishInstance.h +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -// Deprecation warning: this is left here just for backwards compatibility -// and will be removed in next versions of Ayon. -#pragma once - -#include "OpenPypePublishInstance.generated.h" - - -UCLASS(Blueprintable) -class AYON_API UOpenPypePublishInstance : public UPrimaryDataAsset -{ - GENERATED_UCLASS_BODY() - -public: - /** - * Retrieves all the assets which are monitored by the Publish Instance (Monitors assets in the directory which is - * placed in) - * - * @return - Set of UObjects. Careful! They are returning raw pointers. Seems like an issue in UE5 - */ - UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") - TSet GetInternalAssets() const - { - //For some reason it can only return Raw Pointers? Seems like an issue which they haven't fixed. - TSet ResultSet; - - for (const auto& Asset : AssetDataInternal) - ResultSet.Add(Asset.LoadSynchronous()); - - return ResultSet; - } - - /** - * Retrieves all the assets which have been added manually by the Publish Instance - * - * @return - TSet of assets (UObjects). Careful! They are returning raw pointers. Seems like an issue in UE5 - */ - UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") - TSet GetExternalAssets() const - { - //For some reason it can only return Raw Pointers? Seems like an issue which they haven't fixed. - TSet ResultSet; - - for (const auto& Asset : AssetDataExternal) - ResultSet.Add(Asset.LoadSynchronous()); - - return ResultSet; - } - - /** - * Function for returning all the assets in the container combined. - * - * @return Returns all the internal and externally added assets into one set (TSet of UObjects). Careful! They are - * returning raw pointers. Seems like an issue in UE5 - * - * @attention If the bAddExternalAssets variable is false, external assets won't be included! - */ - UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") - TSet GetAllAssets() const - { - const TSet>& IteratedSet = bAddExternalAssets - ? AssetDataInternal.Union(AssetDataExternal) - : AssetDataInternal; - - //Create a new TSet only with raw pointers. - TSet ResultSet; - - for (auto& Asset : IteratedSet) - ResultSet.Add(Asset.LoadSynchronous()); - - return ResultSet; - } - -private: - UPROPERTY(VisibleAnywhere, Category="Assets") - TSet> AssetDataInternal; - - /** - * This property allows exposing the array to include other assets from any other directory than what it's currently - * monitoring. NOTE: that these assets have to be added manually! They are not automatically registered or added! - */ - UPROPERTY(EditAnywhere, Category = "Assets") - bool bAddExternalAssets = false; - - UPROPERTY(EditAnywhere, meta=(EditCondition="bAddExternalAssets"), Category="Assets") - TSet> AssetDataExternal; - - - void OnAssetCreated(const FAssetData& InAssetData); - void OnAssetRemoved(const FAssetData& InAssetData); - void OnAssetUpdated(const FAssetData& InAssetData); - - bool IsUnderSameDir(const UObject* InAsset) const; - -#ifdef WITH_EDITOR - - void ColorOpenPypeDirs(); - - void SendNotification(const FString& Text) const; - virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; - -#endif -}; diff --git a/openpype/hosts/unreal/integration/UE_4.27/BuildPlugin_4-27.bat b/openpype/hosts/unreal/integration/UE_4.27/BuildPlugin_4-27.bat deleted file mode 100644 index 96cdb96f8a..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/BuildPlugin_4-27.bat +++ /dev/null @@ -1 +0,0 @@ -D:\UE4\UE_4.27\Engine\Build\BatchFiles\RunUAT.bat BuildPlugin -plugin="D:\OpenPype\openpype\hosts\unreal\integration\UE_4.27\Ayon\Ayon.uplugin" -Package="D:\BuiltPlugins\4.27" \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_4.27/BuildPlugin_4-27_Window.bat b/openpype/hosts/unreal/integration/UE_4.27/BuildPlugin_4-27_Window.bat deleted file mode 100644 index 1343843a82..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/BuildPlugin_4-27_Window.bat +++ /dev/null @@ -1 +0,0 @@ -cmd /k "BuildPlugin_4-27.bat" \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_4.27/CommandletProject/.gitignore b/openpype/hosts/unreal/integration/UE_4.27/CommandletProject/.gitignore deleted file mode 100644 index e74e6886b7..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/CommandletProject/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -/Saved -/DerivedDataCache -/Intermediate -/Content -/Config -/Binaries -/.idea -/.vs \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_4.27/CommandletProject/CommandletProject.uproject b/openpype/hosts/unreal/integration/UE_4.27/CommandletProject/CommandletProject.uproject deleted file mode 100644 index ea7bf21dc4..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.27/CommandletProject/CommandletProject.uproject +++ /dev/null @@ -1,12 +0,0 @@ -{ - "FileVersion": 3, - "EngineAssociation": "4.27", - "Category": "", - "Description": "", - "Plugins": [ - { - "Name": "Ayon", - "Enabled": true - } - ] -} \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/.gitignore b/openpype/hosts/unreal/integration/UE_5.0/Ayon/.gitignore deleted file mode 100644 index b32a6f55e5..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Ayon/.gitignore +++ /dev/null @@ -1,35 +0,0 @@ -# Prerequisites -*.d - -# Compiled Object files -*.slo -*.lo -*.o -*.obj - -# Precompiled Headers -*.gch -*.pch - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Fortran module files -*.mod -*.smod - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables -*.exe -*.out -*.app - -/Binaries -/Intermediate diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Ayon.uplugin b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Ayon.uplugin deleted file mode 100644 index 70ed8f6b9a..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Ayon.uplugin +++ /dev/null @@ -1,24 +0,0 @@ -{ - "FileVersion": 3, - "Version": 1, - "VersionName": "1.0", - "FriendlyName": "Ayon", - "Description": "Ayon Integration", - "Category": "Ayon.Integration", - "CreatedBy": "Ondrej Samohel", - "CreatedByURL": "https://ayon.ynput.io", - "DocsURL": "https://ayon.ynput.io/docs/artist_hosts_unreal", - "MarketplaceURL": "", - "SupportURL": "https://ynput.io/", - "CanContainContent": true, - "EngineVersion": "5.0", - "IsExperimentalVersion": false, - "Installed": true, - "Modules": [ - { - "Name": "Ayon", - "Type": "Editor", - "LoadingPhase": "Default" - } - ] -} diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Config/DefaultAyonSettings.ini b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Config/DefaultAyonSettings.ini deleted file mode 100644 index 9ad7f55201..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Config/DefaultAyonSettings.ini +++ /dev/null @@ -1,2 +0,0 @@ -[/Script/Ayon.AyonSettings] -FolderColor=(R=91,G=197,B=220,A=255) \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Config/FilterPlugin.ini b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Config/FilterPlugin.ini deleted file mode 100644 index ccebca2f32..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Config/FilterPlugin.ini +++ /dev/null @@ -1,8 +0,0 @@ -[FilterPlugin] -; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and -; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively. -; -; Examples: -; /README.txt -; /Extras/... -; /Binaries/ThirdParty/*.dll diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Content/Python/init_unreal.py b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Content/Python/init_unreal.py deleted file mode 100644 index c0b1d0ce5d..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Content/Python/init_unreal.py +++ /dev/null @@ -1,30 +0,0 @@ -import unreal - -ayon_detected = True -try: - from openpype.pipeline import install_host - from openpype.hosts.unreal.api import UnrealHost - - ayon_host = UnrealHost() -except ImportError as exc: - ayon_host = None - ayon_detected = False - unreal.log_error(f"Ayon: cannot load Ayon integration [ {exc} ]") - -if ayon_detected: - install_host(ayon_host) - - -@unreal.uclass() -class AyonIntegration(unreal.AyonPythonBridge): - @unreal.ufunction(override=True) - def RunInPython_Popup(self): - unreal.log_warning("Ayon: showing tools popup") - if ayon_detected: - ayon_host.show_tools_popup() - - @unreal.ufunction(override=True) - def RunInPython_Dialog(self): - unreal.log_warning("Ayon: showing tools dialog") - if ayon_detected: - ayon_host.show_tools_dialog() diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/README.md b/openpype/hosts/unreal/integration/UE_5.0/Ayon/README.md deleted file mode 100644 index 865c8cafea..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Ayon/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Ayon Unreal Integration plugin - UE 5.0 - -This is plugin for Unreal Editor, creating menu for [Ayon](https://github.com/ynput/OpenPype) tools to run. diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Resources/ayon128.png b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Resources/ayon128.png deleted file mode 100644 index 799d849aa3163ecb16be39c641a6ac30324906b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2358 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 D;gw7~ diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Resources/ayon512.png b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Resources/ayon512.png deleted file mode 100644 index 990d5917e232a0644820428fb2790943de5ffaa4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16705 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 - FAyonCommands::Get().AyonTools, - FExecuteAction::CreateRaw(this, &FAyonModule::MenuPopup), - FCanExecuteAction()); - PluginCommands->MapAction( - FAyonCommands::Get().AyonToolsDialog, - FExecuteAction::CreateRaw(this, &FAyonModule::MenuDialog), - FCanExecuteAction()); - - UToolMenus::RegisterStartupCallback( - FSimpleMulticastDelegate::FDelegate::CreateRaw(this, &FAyonModule::RegisterMenus)); - - RegisterSettings(); -} - -void FAyonModule::ShutdownModule() -{ - UToolMenus::UnRegisterStartupCallback(this); - - UToolMenus::UnregisterOwner(this); - - FAyonStyle::Shutdown(); - - FAyonCommands::Unregister(); -} - - -void FAyonModule::RegisterSettings() -{ - ISettingsModule& SettingsModule = FModuleManager::LoadModuleChecked("Settings"); - - // Create the new category - // TODO: After the movement of the plugin from the game to editor, it might be necessary to move this! - ISettingsContainerPtr SettingsContainer = SettingsModule.GetContainer("Project"); - - UAyonSettings* Settings = GetMutableDefault(); - - // Register the settings - ISettingsSectionPtr SettingsSection = SettingsModule.RegisterSettings("Project", "Ayon", "General", - LOCTEXT("RuntimeGeneralSettingsName", - "General"), - LOCTEXT("RuntimeGeneralSettingsDescription", - "Base configuration for Open Pype Module"), - Settings - ); - - // Register the save handler to your settings, you might want to use it to - // validate those or just act to settings changes. - if (SettingsSection.IsValid()) - { - SettingsSection->OnModified().BindRaw(this, &FAyonModule::HandleSettingsSaved); - } -} - -bool FAyonModule::HandleSettingsSaved() -{ - UAyonSettings* Settings = GetMutableDefault(); - bool ResaveSettings = false; - - // You can put any validation code in here and resave the settings in case an invalid - // value has been entered - - if (ResaveSettings) - { - Settings->SaveConfig(); - } - - return true; -} - -void FAyonModule::RegisterMenus() -{ - // Owner will be used for cleanup in call to UToolMenus::UnregisterOwner - FToolMenuOwnerScoped OwnerScoped(this); - - { - UToolMenu* Menu = UToolMenus::Get()->ExtendMenu("LevelEditor.MainMenu.Tools"); - { - // FToolMenuSection& Section = Menu->FindOrAddSection("Ayon"); - FToolMenuSection& Section = Menu->AddSection( - "Ayon", - TAttribute(FText::FromString("Ayon")), - FToolMenuInsert("Programming", EToolMenuInsertType::Before) - ); - Section.AddMenuEntryWithCommandList(FAyonCommands::Get().AyonTools, PluginCommands); - Section.AddMenuEntryWithCommandList(FAyonCommands::Get().AyonToolsDialog, PluginCommands); - } - UToolMenu* ToolbarMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.LevelEditorToolBar.PlayToolBar"); - { - FToolMenuSection& Section = ToolbarMenu->FindOrAddSection("PluginTools"); - { - FToolMenuEntry& Entry = Section.AddEntry( - FToolMenuEntry::InitToolBarButton(FAyonCommands::Get().AyonTools)); - Entry.SetCommandList(PluginCommands); - } - } - } -} - - -void FAyonModule::MenuPopup() -{ - UAyonPythonBridge* bridge = UAyonPythonBridge::Get(); - bridge->RunInPython_Popup(); -} - -void FAyonModule::MenuDialog() -{ - UAyonPythonBridge* bridge = UAyonPythonBridge::Get(); - bridge->RunInPython_Dialog(); -} - -IMPLEMENT_MODULE(FAyonModule, Ayon) diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonAssetContainer.cpp b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonAssetContainer.cpp deleted file mode 100644 index 869aa45256..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonAssetContainer.cpp +++ /dev/null @@ -1,113 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#include "AyonAssetContainer.h" -#include "AssetRegistry/AssetRegistryModule.h" -#include "Misc/PackageName.h" -#include "Containers/UnrealString.h" - -UAyonAssetContainer::UAyonAssetContainer(const FObjectInitializer& ObjectInitializer) -: UAssetUserData(ObjectInitializer) -{ - FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); - FString path = UAyonAssetContainer::GetPathName(); - UE_LOG(LogTemp, Warning, TEXT("UAyonAssetContainer %s"), *path); - FARFilter Filter; - Filter.PackagePaths.Add(FName(*path)); - - AssetRegistryModule.Get().OnAssetAdded().AddUObject(this, &UAyonAssetContainer::OnAssetAdded); - AssetRegistryModule.Get().OnAssetRemoved().AddUObject(this, &UAyonAssetContainer::OnAssetRemoved); - AssetRegistryModule.Get().OnAssetRenamed().AddUObject(this, &UAyonAssetContainer::OnAssetRenamed); -} - -void UAyonAssetContainer::OnAssetAdded(const FAssetData& AssetData) -{ - TArray split; - - // get directory of current container - FString selfFullPath = UAyonAssetContainer::GetPathName(); - FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath); - - // get asset path and class - FString assetPath = AssetData.GetFullName(); - FString assetFName = AssetData.ObjectPath.ToString(); - UE_LOG(LogTemp, Log, TEXT("asset name %s"), *assetFName); - // split path - assetPath.ParseIntoArray(split, TEXT(" "), true); - - FString assetDir = FPackageName::GetLongPackagePath(*split[1]); - - // take interest only in paths starting with path of current container - if (assetDir.StartsWith(*selfDir)) - { - // exclude self - if (assetFName != "AssetContainer") - { - assets.Add(assetPath); - assetsData.Add(AssetData); - UE_LOG(LogTemp, Log, TEXT("%s: asset added to %s"), *selfFullPath, *selfDir); - } - } -} - -void UAyonAssetContainer::OnAssetRemoved(const FAssetData& AssetData) -{ - TArray split; - - // get directory of current container - FString selfFullPath = UAyonAssetContainer::GetPathName(); - FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath); - - // get asset path and class - FString assetPath = AssetData.GetFullName(); - FString assetFName = AssetData.ObjectPath.ToString(); - - // split path - assetPath.ParseIntoArray(split, TEXT(" "), true); - - FString assetDir = FPackageName::GetLongPackagePath(*split[1]); - - // take interest only in paths starting with path of current container - FString path = UAyonAssetContainer::GetPathName(); - FString lpp = FPackageName::GetLongPackagePath(*path); - - if (assetDir.StartsWith(*selfDir)) - { - // exclude self - if (assetFName != "AssetContainer") - { - // UE_LOG(LogTemp, Warning, TEXT("%s: asset removed"), *lpp); - assets.Remove(assetPath); - assetsData.Remove(AssetData); - } - } -} - -void UAyonAssetContainer::OnAssetRenamed(const FAssetData& AssetData, const FString& str) -{ - TArray split; - - // get directory of current container - FString selfFullPath = UAyonAssetContainer::GetPathName(); - FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath); - - // get asset path and class - FString assetPath = AssetData.GetFullName(); - FString assetFName = AssetData.ObjectPath.ToString(); - - // split path - assetPath.ParseIntoArray(split, TEXT(" "), true); - - FString assetDir = FPackageName::GetLongPackagePath(*split[1]); - if (assetDir.StartsWith(*selfDir)) - { - // exclude self - if (assetFName != "AssetContainer") - { - - assets.Remove(str); - assets.Add(assetPath); - assetsData.Remove(AssetData); - // UE_LOG(LogTemp, Warning, TEXT("%s: asset renamed %s"), *lpp, *str); - } - } -} diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonAssetContainerFactory.cpp b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonAssetContainerFactory.cpp deleted file mode 100644 index 086fc1036e..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonAssetContainerFactory.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include "AyonAssetContainerFactory.h" -#include "AyonAssetContainer.h" - -UAyonAssetContainerFactory::UAyonAssetContainerFactory(const FObjectInitializer& ObjectInitializer) - : UFactory(ObjectInitializer) -{ - SupportedClass = UAyonAssetContainer::StaticClass(); - bCreateNew = false; - bEditorImport = true; -} - -UObject* UAyonAssetContainerFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) -{ - UAyonAssetContainer* AssetContainer = NewObject(InParent, Class, Name, Flags); - return AssetContainer; -} - -bool UAyonAssetContainerFactory::ShouldShowInNewMenu() const { - return false; -} diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonCommands.cpp b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonCommands.cpp deleted file mode 100644 index 566ee1dcd1..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonCommands.cpp +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. - -#include "AyonCommands.h" - -#define LOCTEXT_NAMESPACE "FAyonModule" - -void FAyonCommands::RegisterCommands() -{ - UI_COMMAND(AyonTools, "Ayon Tools", "Pipeline tools", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(AyonToolsDialog, "Ayon Tools Dialog", "Pipeline tools dialog", EUserInterfaceActionType::Button, FInputChord()); -} - -#undef LOCTEXT_NAMESPACE diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonLib.cpp b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonLib.cpp deleted file mode 100644 index 7cfa0c9c30..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonLib.cpp +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#include "AyonLib.h" - -#include "AssetViewUtils.h" -#include "UObject/UnrealType.h" - -/** - * Sets color on folder icon on given path - * @param InPath - path to folder - * @param InFolderColor - color of the folder - * @warning This color will appear only after Editor restart. Is there a better way? - */ - -bool UAyonLib::SetFolderColor(const FString& FolderPath, const FLinearColor& FolderColor, const bool& bForceAdd) -{ - if (AssetViewUtils::DoesFolderExist(FolderPath)) - { - const TSharedPtr LinearColor = MakeShared(FolderColor); - - AssetViewUtils::SaveColor(FolderPath, LinearColor, true); - UE_LOG(LogAssetData, Display, TEXT("A color {%s} has been set to folder \"%s\""), *LinearColor->ToString(), - *FolderPath) - return true; - } - - UE_LOG(LogAssetData, Display, TEXT("Setting a color {%s} to folder \"%s\" has failed! Directory doesn't exist!"), - *FolderColor.ToString(), *FolderPath) - return false; -} - -/** - * Returns all poperties on given object - * @param cls - class - * @return TArray of properties - */ -TArray UAyonLib::GetAllProperties(UClass* cls) -{ - TArray Ret; - if (cls != nullptr) - { - for (TFieldIterator It(cls); It; ++It) - { - FProperty* Property = *It; - if (Property->HasAnyPropertyFlags(EPropertyFlags::CPF_Edit)) - { - Ret.Add(Property->GetName()); - } - } - } - return Ret; -} diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonPublishInstance.cpp b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonPublishInstance.cpp deleted file mode 100644 index 8d34090a15..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonPublishInstance.cpp +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -// Deprecation warning: this is left here just for backwards compatibility -// and will be removed in next versions of Ayon. -#pragma once - -#include "AyonPublishInstance.h" -#include "AssetRegistry/AssetRegistryModule.h" -#include "AssetToolsModule.h" -#include "Framework/Notifications/NotificationManager.h" -#include "AyonLib.h" -#include "AyonSettings.h" -#include "Widgets/Notifications/SNotificationList.h" - - -//Moves all the invalid pointers to the end to prepare them for the shrinking -#define REMOVE_INVALID_ENTRIES(VAR) VAR.CompactStable(); \ - VAR.Shrink(); - -UAyonPublishInstance::UAyonPublishInstance(const FObjectInitializer& ObjectInitializer) - : UPrimaryDataAsset(ObjectInitializer) -{ - const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked< - FAssetRegistryModule>("AssetRegistry"); - - const FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked( - "PropertyEditor"); - - FString Left, Right; - GetPathName().Split("/" + GetName(), &Left, &Right); - - FARFilter Filter; - Filter.PackagePaths.Emplace(FName(Left)); - - TArray FoundAssets; - AssetRegistryModule.GetRegistry().GetAssets(Filter, FoundAssets); - - for (const FAssetData& AssetData : FoundAssets) - OnAssetCreated(AssetData); - - REMOVE_INVALID_ENTRIES(AssetDataInternal) - REMOVE_INVALID_ENTRIES(AssetDataExternal) - - AssetRegistryModule.Get().OnAssetAdded().AddUObject(this, &UAyonPublishInstance::OnAssetCreated); - AssetRegistryModule.Get().OnAssetRemoved().AddUObject(this, &UAyonPublishInstance::OnAssetRemoved); - AssetRegistryModule.Get().OnAssetUpdated().AddUObject(this, &UAyonPublishInstance::OnAssetUpdated); - -#ifdef WITH_EDITOR - ColorAyonDirs(); -#endif -} - -void UAyonPublishInstance::OnAssetCreated(const FAssetData& InAssetData) -{ - TArray split; - - UObject* Asset = InAssetData.GetAsset(); - - if (!IsValid(Asset)) - { - UE_LOG(LogAssetData, Warning, TEXT("Asset \"%s\" is not valid! Skipping the addition."), - *InAssetData.ObjectPath.ToString()); - return; - } - - const bool result = IsUnderSameDir(Asset) && Cast(Asset) == nullptr; - - if (result) - { - if (AssetDataInternal.Emplace(Asset).IsValidId()) - { - UE_LOG(LogTemp, Log, TEXT("Added an Asset to PublishInstance - Publish Instance: %s, Asset %s"), - *this->GetName(), *Asset->GetName()); - } - } -} - -void UAyonPublishInstance::OnAssetRemoved(const FAssetData& InAssetData) -{ - if (Cast(InAssetData.GetAsset()) == nullptr) - { - if (AssetDataInternal.Contains(nullptr)) - { - AssetDataInternal.Remove(nullptr); - REMOVE_INVALID_ENTRIES(AssetDataInternal) - } - else - { - AssetDataExternal.Remove(nullptr); - REMOVE_INVALID_ENTRIES(AssetDataExternal) - } - } -} - -void UAyonPublishInstance::OnAssetUpdated(const FAssetData& InAssetData) -{ - REMOVE_INVALID_ENTRIES(AssetDataInternal); - REMOVE_INVALID_ENTRIES(AssetDataExternal); -} - -bool UAyonPublishInstance::IsUnderSameDir(const UObject* InAsset) const -{ - FString ThisLeft, ThisRight; - this->GetPathName().Split(this->GetName(), &ThisLeft, &ThisRight); - - return InAsset->GetPathName().StartsWith(ThisLeft); -} - -#ifdef WITH_EDITOR - -void UAyonPublishInstance::ColorAyonDirs() -{ - FString PathName = this->GetPathName(); - - //Check whether the path contains the defined Ayon folder - if (!PathName.Contains(TEXT("Ayon"))) return; - - //Get the base path for open pype - FString PathLeft, PathRight; - PathName.Split(FString("Ayon"), &PathLeft, &PathRight); - - if (PathLeft.IsEmpty() || PathRight.IsEmpty()) - { - UE_LOG(LogAssetData, Error, TEXT("Failed to retrieve the base Ayon directory!")) - return; - } - - PathName.RemoveFromEnd(PathRight, ESearchCase::CaseSensitive); - - //Get the current settings - const UAyonSettings* Settings = GetMutableDefault(); - - //Color the base folder - UAyonLib::SetFolderColor(PathName, Settings->GetFolderFColor(), false); - - //Get Sub paths, iterate through them and color them according to the folder color in UAyonSettings - const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked( - "AssetRegistry"); - - TArray PathList; - - AssetRegistryModule.Get().GetSubPaths(PathName, PathList, true); - - if (PathList.Num() > 0) - { - for (const FString& Path : PathList) - { - UAyonLib::SetFolderColor(Path, Settings->GetFolderFColor(), false); - } - } -} - -void UAyonPublishInstance::SendNotification(const FString& Text) const -{ - FNotificationInfo Info{FText::FromString(Text)}; - - Info.bFireAndForget = true; - Info.bUseLargeFont = false; - Info.bUseThrobber = false; - Info.bUseSuccessFailIcons = false; - Info.ExpireDuration = 4.f; - Info.FadeOutDuration = 2.f; - - FSlateNotificationManager::Get().AddNotification(Info); - - UE_LOG(LogAssetData, Warning, - TEXT( - "Removed duplicated asset from the AssetsDataExternal in Container \"%s\", Asset is already included in the AssetDataInternal!" - ), *GetName() - ) -} - - -void UAyonPublishInstance::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) -{ - Super::PostEditChangeProperty(PropertyChangedEvent); - - if (PropertyChangedEvent.ChangeType == EPropertyChangeType::ValueSet && - PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED( - UAyonPublishInstance, AssetDataExternal)) - { - // Check for duplicated assets - for (const auto& Asset : AssetDataInternal) - { - if (AssetDataExternal.Contains(Asset)) - { - AssetDataExternal.Remove(Asset); - return SendNotification( - "You are not allowed to add assets into AssetDataExternal which are already included in AssetDataInternal!"); - } - } - - // Check if no UAyonPublishInstance type assets are included - for (const auto& Asset : AssetDataExternal) - { - if (Cast(Asset.Get()) != nullptr) - { - AssetDataExternal.Remove(Asset); - return SendNotification("You are not allowed to add publish instances!"); - } - } - } -} - -#endif diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonPublishInstanceFactory.cpp b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonPublishInstanceFactory.cpp deleted file mode 100644 index f79c428a6d..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonPublishInstanceFactory.cpp +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -// Deprecation warning: this is left here just for backwards compatibility -// and will be removed in next versions of Ayon. -#include "AyonPublishInstanceFactory.h" -#include "AyonPublishInstance.h" - -UAyonPublishInstanceFactory::UAyonPublishInstanceFactory(const FObjectInitializer& ObjectInitializer) - : UFactory(ObjectInitializer) -{ - SupportedClass = UAyonPublishInstance::StaticClass(); - bCreateNew = false; - bEditorImport = true; -} - -UObject* UAyonPublishInstanceFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) -{ - check(InClass->IsChildOf(UAyonPublishInstance::StaticClass())); - return NewObject(InParent, InClass, InName, Flags); -} - -bool UAyonPublishInstanceFactory::ShouldShowInNewMenu() const { - return false; -} diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonPythonBridge.cpp b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonPythonBridge.cpp deleted file mode 100644 index 0ed4b2f704..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonPythonBridge.cpp +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#include "AyonPythonBridge.h" - -UAyonPythonBridge* UAyonPythonBridge::Get() -{ - TArray AyonPythonBridgeClasses; - GetDerivedClasses(UAyonPythonBridge::StaticClass(), AyonPythonBridgeClasses); - int32 NumClasses = AyonPythonBridgeClasses.Num(); - if (NumClasses > 0) - { - return Cast(AyonPythonBridgeClasses[NumClasses - 1]->GetDefaultObject()); - } - return nullptr; -}; \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonSettings.cpp b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonSettings.cpp deleted file mode 100644 index da388fbc8f..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonSettings.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. - -#include "AyonSettings.h" - -#include "Interfaces/IPluginManager.h" -#include "UObject/UObjectGlobals.h" - -/** - * Mainly is used for initializing default values if the DefaultAyonSettings.ini file does not exist in the saved config - */ -UAyonSettings::UAyonSettings(const FObjectInitializer& ObjectInitializer) -{ - - const FString ConfigFilePath = AYON_SETTINGS_FILEPATH; - - // This has to be probably in the future set using the UE Reflection system - FColor Color; - GConfig->GetColor(TEXT("/Script/Ayon.AyonSettings"), TEXT("FolderColor"), Color, ConfigFilePath); - - FolderColor = Color; -} \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonStyle.cpp b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonStyle.cpp deleted file mode 100644 index d88df78735..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonStyle.cpp +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. - -#include "AyonStyle.h" -#include "Framework/Application/SlateApplication.h" -#include "Styling/SlateStyleRegistry.h" -#include "Slate/SlateGameResources.h" -#include "Interfaces/IPluginManager.h" -#include "Styling/SlateStyleMacros.h" - -#define RootToContentDir Style->RootToContentDir - -TSharedPtr FAyonStyle::AyonStyleInstance = nullptr; - -void FAyonStyle::Initialize() -{ - if (!AyonStyleInstance.IsValid()) - { - AyonStyleInstance = Create(); - FSlateStyleRegistry::RegisterSlateStyle(*AyonStyleInstance); - } -} - -void FAyonStyle::Shutdown() -{ - FSlateStyleRegistry::UnRegisterSlateStyle(*AyonStyleInstance); - ensure(AyonStyleInstance.IsUnique()); - AyonStyleInstance.Reset(); -} - -FName FAyonStyle::GetStyleSetName() -{ - static FName StyleSetName(TEXT("AyonStyle")); - return StyleSetName; -} - -const FVector2D Icon16x16(16.0f, 16.0f); -const FVector2D Icon20x20(20.0f, 20.0f); -const FVector2D Icon40x40(40.0f, 40.0f); - -TSharedRef< FSlateStyleSet > FAyonStyle::Create() -{ - TSharedRef< FSlateStyleSet > Style = MakeShareable(new FSlateStyleSet("AyonStyle")); - Style->SetContentRoot(IPluginManager::Get().FindPlugin("Ayon")->GetBaseDir() / TEXT("Resources")); - - Style->Set("Ayon.AyonTools", new IMAGE_BRUSH(TEXT("ayon40"), Icon40x40)); - Style->Set("Ayon.AyonToolsDialog", new IMAGE_BRUSH(TEXT("ayon40"), Icon40x40)); - - return Style; -} - -void FAyonStyle::ReloadTextures() -{ - if (FSlateApplication::IsInitialized()) - { - FSlateApplication::Get().GetRenderer()->ReloadTextureResources(); - } -} - -const ISlateStyle& FAyonStyle::Get() -{ - return *AyonStyleInstance; -} diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/Commandlets/AyonActionResult.cpp b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/Commandlets/AyonActionResult.cpp deleted file mode 100644 index 2a137e3ed7..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/Commandlets/AyonActionResult.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. - -#include "Commandlets/AyonActionResult.h" -#include "Logging/Ayon_Log.h" - -EAyon_ActionResult::Type& FAyon_ActionResult::GetStatus() -{ - return Status; -} - -FText& FAyon_ActionResult::GetReason() -{ - return Reason; -} - -FAyon_ActionResult::FAyon_ActionResult():Status(EAyon_ActionResult::Type::Ok) -{ - -} - -FAyon_ActionResult::FAyon_ActionResult(const EAyon_ActionResult::Type& InEnum):Status(InEnum) -{ - TryLog(); -} - -FAyon_ActionResult::FAyon_ActionResult(const EAyon_ActionResult::Type& InEnum, const FText& InReason):Status(InEnum), Reason(InReason) -{ - TryLog(); -}; - -bool FAyon_ActionResult::IsProblem() const -{ - return Status != EAyon_ActionResult::Ok; -} - -void FAyon_ActionResult::TryLog() const -{ - if(IsProblem()) - UE_LOG(LogCommandletAyonGenerateProject, Error, TEXT("%s"), *Reason.ToString()); -} diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/Commandlets/Implementations/AyonGenerateProjectCommandlet.cpp b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/Commandlets/Implementations/AyonGenerateProjectCommandlet.cpp deleted file mode 100644 index ed876c8128..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/Commandlets/Implementations/AyonGenerateProjectCommandlet.cpp +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#include "Commandlets/Implementations/AyonGenerateProjectCommandlet.h" - -#include "GameProjectUtils.h" -#include "AyonConstants.h" -#include "Commandlets/AyonActionResult.h" -#include "ProjectDescriptor.h" - -int32 UAyonGenerateProjectCommandlet::Main(const FString& CommandLineParams) -{ - //Parses command line parameters & creates structure FProjectInformation - const FAyonGenerateProjectParams ParsedParams = FAyonGenerateProjectParams(CommandLineParams); - ProjectInformation = ParsedParams.GenerateUEProjectInformation(); - - //Creates .uproject & other UE files - EVALUATE_Ayon_ACTION_RESULT(TryCreateProject()); - - //Loads created .uproject - EVALUATE_Ayon_ACTION_RESULT(TryLoadProjectDescriptor()); - - //Adds needed plugin to .uproject - AttachPluginsToProjectDescriptor(); - - //Saves .uproject - EVALUATE_Ayon_ACTION_RESULT(TrySave()); - - //When we are here, there should not be problems in generating Unreal Project for Ayon - return 0; -} - - -FAyonGenerateProjectParams::FAyonGenerateProjectParams(): FAyonGenerateProjectParams("") -{ -} - -FAyonGenerateProjectParams::FAyonGenerateProjectParams(const FString& CommandLineParams): CommandLineParams( - CommandLineParams) -{ - UCommandlet::ParseCommandLine(*CommandLineParams, Tokens, Switches); -} - -FProjectInformation FAyonGenerateProjectParams::GenerateUEProjectInformation() const -{ - FProjectInformation ProjectInformation = FProjectInformation(); - ProjectInformation.ProjectFilename = GetProjectFileName(); - - ProjectInformation.bShouldGenerateCode = IsSwitchPresent("GenerateCode"); - - return ProjectInformation; -} - -FString FAyonGenerateProjectParams::TryGetToken(const int32 Index) const -{ - return Tokens.IsValidIndex(Index) ? Tokens[Index] : ""; -} - -FString FAyonGenerateProjectParams::GetProjectFileName() const -{ - return TryGetToken(0); -} - -bool FAyonGenerateProjectParams::IsSwitchPresent(const FString& Switch) const -{ - return INDEX_NONE != Switches.IndexOfByPredicate([&Switch](const FString& Item) -> bool - { - return Item.Equals(Switch); - } - ); -} - - -UAyonGenerateProjectCommandlet::UAyonGenerateProjectCommandlet() -{ - LogToConsole = true; -} - -FAyon_ActionResult UAyonGenerateProjectCommandlet::TryCreateProject() const -{ - FText FailReason; - FText FailLog; - TArray OutCreatedFiles; - - if (!GameProjectUtils::CreateProject(ProjectInformation, FailReason, FailLog, &OutCreatedFiles)) - return FAyon_ActionResult(EAyon_ActionResult::ProjectNotCreated, FailReason); - return FAyon_ActionResult(); -} - -FAyon_ActionResult UAyonGenerateProjectCommandlet::TryLoadProjectDescriptor() -{ - FText FailReason; - const bool bLoaded = ProjectDescriptor.Load(ProjectInformation.ProjectFilename, FailReason); - - return FAyon_ActionResult(bLoaded ? EAyon_ActionResult::Ok : EAyon_ActionResult::ProjectNotLoaded, FailReason); -} - -void UAyonGenerateProjectCommandlet::AttachPluginsToProjectDescriptor() -{ - FPluginReferenceDescriptor AyonPluginDescriptor; - AyonPluginDescriptor.bEnabled = true; - AyonPluginDescriptor.Name = AyonConstants::Ayon_PluginName; - ProjectDescriptor.Plugins.Add(AyonPluginDescriptor); - - FPluginReferenceDescriptor PythonPluginDescriptor; - PythonPluginDescriptor.bEnabled = true; - PythonPluginDescriptor.Name = AyonConstants::PythonScript_PluginName; - ProjectDescriptor.Plugins.Add(PythonPluginDescriptor); - - FPluginReferenceDescriptor SequencerScriptingPluginDescriptor; - SequencerScriptingPluginDescriptor.bEnabled = true; - SequencerScriptingPluginDescriptor.Name = AyonConstants::SequencerScripting_PluginName; - ProjectDescriptor.Plugins.Add(SequencerScriptingPluginDescriptor); - - FPluginReferenceDescriptor MovieRenderPipelinePluginDescriptor; - MovieRenderPipelinePluginDescriptor.bEnabled = true; - MovieRenderPipelinePluginDescriptor.Name = AyonConstants::MovieRenderPipeline_PluginName; - ProjectDescriptor.Plugins.Add(MovieRenderPipelinePluginDescriptor); - - FPluginReferenceDescriptor EditorScriptingPluginDescriptor; - EditorScriptingPluginDescriptor.bEnabled = true; - EditorScriptingPluginDescriptor.Name = AyonConstants::EditorScriptingUtils_PluginName; - ProjectDescriptor.Plugins.Add(EditorScriptingPluginDescriptor); -} - -FAyon_ActionResult UAyonGenerateProjectCommandlet::TrySave() -{ - FText FailReason; - const bool bSaved = ProjectDescriptor.Save(ProjectInformation.ProjectFilename, FailReason); - - return FAyon_ActionResult(bSaved ? EAyon_ActionResult::Ok : EAyon_ActionResult::ProjectNotSaved, FailReason); -} - -FAyonGenerateProjectParams UAyonGenerateProjectCommandlet::ParseParameters(const FString& Params) const -{ - FAyonGenerateProjectParams ParamsResult; - - TArray Tokens, Switches; - ParseCommandLine(*Params, Tokens, Switches); - - return ParamsResult; -} diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/OpenPypePublishInstance.cpp b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/OpenPypePublishInstance.cpp deleted file mode 100644 index 7a65fd0c98..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/OpenPypePublishInstance.cpp +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -// Deprecation warning: this is left here just for backwards compatibility -// and will be removed in next versions of Ayon. -#pragma once - -#include "OpenPypePublishInstance.h" -#include "AssetRegistry/AssetRegistryModule.h" -#include "AssetToolsModule.h" -#include "Framework/Notifications/NotificationManager.h" -#include "AyonLib.h" -#include "AyonSettings.h" -#include "Widgets/Notifications/SNotificationList.h" - - -//Moves all the invalid pointers to the end to prepare them for the shrinking -#define REMOVE_INVALID_ENTRIES(VAR) VAR.CompactStable(); \ - VAR.Shrink(); - -UOpenPypePublishInstance::UOpenPypePublishInstance(const FObjectInitializer& ObjectInitializer) - : UPrimaryDataAsset(ObjectInitializer) -{ - const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked< - FAssetRegistryModule>("AssetRegistry"); - - const FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked( - "PropertyEditor"); - - FString Left, Right; - GetPathName().Split("/" + GetName(), &Left, &Right); - - FARFilter Filter; - Filter.PackagePaths.Emplace(FName(Left)); - - TArray FoundAssets; - AssetRegistryModule.GetRegistry().GetAssets(Filter, FoundAssets); - - for (const FAssetData& AssetData : FoundAssets) - OnAssetCreated(AssetData); - - REMOVE_INVALID_ENTRIES(AssetDataInternal) - REMOVE_INVALID_ENTRIES(AssetDataExternal) - - AssetRegistryModule.Get().OnAssetAdded().AddUObject(this, &UOpenPypePublishInstance::OnAssetCreated); - AssetRegistryModule.Get().OnAssetRemoved().AddUObject(this, &UOpenPypePublishInstance::OnAssetRemoved); - AssetRegistryModule.Get().OnAssetUpdated().AddUObject(this, &UOpenPypePublishInstance::OnAssetUpdated); - -#ifdef WITH_EDITOR - ColorOpenPypeDirs(); -#endif -} - -void UOpenPypePublishInstance::OnAssetCreated(const FAssetData& InAssetData) -{ - TArray split; - - UObject* Asset = InAssetData.GetAsset(); - - if (!IsValid(Asset)) - { - UE_LOG(LogAssetData, Warning, TEXT("Asset \"%s\" is not valid! Skipping the addition."), - *InAssetData.ObjectPath.ToString()); - return; - } - - const bool result = IsUnderSameDir(Asset) && Cast(Asset) == nullptr; - - if (result) - { - if (AssetDataInternal.Emplace(Asset).IsValidId()) - { - UE_LOG(LogTemp, Log, TEXT("Added an Asset to PublishInstance - Publish Instance: %s, Asset %s"), - *this->GetName(), *Asset->GetName()); - } - } -} - -void UOpenPypePublishInstance::OnAssetRemoved(const FAssetData& InAssetData) -{ - if (Cast(InAssetData.GetAsset()) == nullptr) - { - if (AssetDataInternal.Contains(nullptr)) - { - AssetDataInternal.Remove(nullptr); - REMOVE_INVALID_ENTRIES(AssetDataInternal) - } - else - { - AssetDataExternal.Remove(nullptr); - REMOVE_INVALID_ENTRIES(AssetDataExternal) - } - } -} - -void UOpenPypePublishInstance::OnAssetUpdated(const FAssetData& InAssetData) -{ - REMOVE_INVALID_ENTRIES(AssetDataInternal); - REMOVE_INVALID_ENTRIES(AssetDataExternal); -} - -bool UOpenPypePublishInstance::IsUnderSameDir(const UObject* InAsset) const -{ - FString ThisLeft, ThisRight; - this->GetPathName().Split(this->GetName(), &ThisLeft, &ThisRight); - - return InAsset->GetPathName().StartsWith(ThisLeft); -} - -#ifdef WITH_EDITOR - -void UOpenPypePublishInstance::ColorOpenPypeDirs() -{ - FString PathName = this->GetPathName(); - - //Check whether the path contains the defined OpenPype folder - if (!PathName.Contains(TEXT("OpenPype"))) return; - - //Get the base path for open pype - FString PathLeft, PathRight; - PathName.Split(FString("OpenPype"), &PathLeft, &PathRight); - - if (PathLeft.IsEmpty() || PathRight.IsEmpty()) - { - UE_LOG(LogAssetData, Error, TEXT("Failed to retrieve the base OpenPype directory!")) - return; - } - - PathName.RemoveFromEnd(PathRight, ESearchCase::CaseSensitive); - - //Get the current settings - const UAyonSettings* Settings = GetMutableDefault(); - - //Color the base folder - UAyonLib::SetFolderColor(PathName, Settings->GetFolderFColor(), false); - - //Get Sub paths, iterate through them and color them according to the folder color in UOpenPypeSettings - const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked( - "AssetRegistry"); - - TArray PathList; - - AssetRegistryModule.Get().GetSubPaths(PathName, PathList, true); - - if (PathList.Num() > 0) - { - for (const FString& Path : PathList) - { - UAyonLib::SetFolderColor(Path, Settings->GetFolderFColor(), false); - } - } -} - -void UOpenPypePublishInstance::SendNotification(const FString& Text) const -{ - FNotificationInfo Info{FText::FromString(Text)}; - - Info.bFireAndForget = true; - Info.bUseLargeFont = false; - Info.bUseThrobber = false; - Info.bUseSuccessFailIcons = false; - Info.ExpireDuration = 4.f; - Info.FadeOutDuration = 2.f; - - FSlateNotificationManager::Get().AddNotification(Info); - - UE_LOG(LogAssetData, Warning, - TEXT( - "Removed duplicated asset from the AssetsDataExternal in Container \"%s\", Asset is already included in the AssetDataInternal!" - ), *GetName() - ) -} - - -void UOpenPypePublishInstance::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) -{ - Super::PostEditChangeProperty(PropertyChangedEvent); - - if (PropertyChangedEvent.ChangeType == EPropertyChangeType::ValueSet && - PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED( - UOpenPypePublishInstance, AssetDataExternal)) - { - // Check for duplicated assets - for (const auto& Asset : AssetDataInternal) - { - if (AssetDataExternal.Contains(Asset)) - { - AssetDataExternal.Remove(Asset); - return SendNotification( - "You are not allowed to add assets into AssetDataExternal which are already included in AssetDataInternal!"); - } - } - - // Check if no UOpenPypePublishInstance type assets are included - for (const auto& Asset : AssetDataExternal) - { - if (Cast(Asset.Get()) != nullptr) - { - AssetDataExternal.Remove(Asset); - return SendNotification("You are not allowed to add publish instances!"); - } - } - } -} - -#endif diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/Ayon.h b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/Ayon.h deleted file mode 100644 index bb25430411..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/Ayon.h +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. - -#pragma once - -#include "CoreMinimal.h" - - -class FAyonModule : public IModuleInterface -{ -public: - virtual void StartupModule() override; - virtual void ShutdownModule() override; - -private: - void RegisterMenus(); - void RegisterSettings(); - bool HandleSettingsSaved(); - - void MenuPopup(); - void MenuDialog(); - -private: - TSharedPtr PluginCommands; -}; diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonAssetContainer.h b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonAssetContainer.h deleted file mode 100644 index d40642b149..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonAssetContainer.h +++ /dev/null @@ -1,34 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/NoExportTypes.h" -#include "Engine/AssetUserData.h" -#include "AssetRegistry/AssetData.h" -#include "AyonAssetContainer.generated.h" - -UCLASS(Blueprintable) -class AYON_API UAyonAssetContainer : public UAssetUserData -{ - GENERATED_BODY() - -public: - - UAyonAssetContainer(const FObjectInitializer& ObjectInitalizer); - // ~UAyonAssetContainer(); - - UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Assets") - TArray assets; - - // There seems to be no reflection option to expose array of FAssetData - /* - UPROPERTY(Transient, BlueprintReadOnly, Category = "Python", meta=(DisplayName="Assets Data")) - TArray assetsData; - */ -private: - TArray assetsData; - void OnAssetAdded(const FAssetData& AssetData); - void OnAssetRemoved(const FAssetData& AssetData); - void OnAssetRenamed(const FAssetData& AssetData, const FString& str); -}; diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonAssetContainerFactory.h b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonAssetContainerFactory.h deleted file mode 100644 index da424cde2e..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonAssetContainerFactory.h +++ /dev/null @@ -1,18 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#pragma once - -#include "CoreMinimal.h" -#include "Factories/Factory.h" -#include "AyonAssetContainerFactory.generated.h" - -UCLASS() -class AYON_API UAyonAssetContainerFactory : public UFactory -{ - GENERATED_BODY() - -public: - UAyonAssetContainerFactory(const FObjectInitializer& ObjectInitializer); - virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; - virtual bool ShouldShowInNewMenu() const override; -}; diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonCommands.h b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonCommands.h deleted file mode 100644 index 9c40dc8241..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonCommands.h +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "Framework/Commands/Commands.h" -#include "AyonStyle.h" - -class FAyonCommands : public TCommands -{ -public: - - FAyonCommands() - : TCommands(TEXT("Ayon"), NSLOCTEXT("Contexts", "Ayon", "Ayon Tools"), NAME_None, FAyonStyle::GetStyleSetName()) - { - } - - // TCommands<> interface - virtual void RegisterCommands() override; - -public: - TSharedPtr< FUICommandInfo > AyonTools; - TSharedPtr< FUICommandInfo > AyonToolsDialog; -}; diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonConstants.h b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonConstants.h deleted file mode 100644 index 5fe7c14360..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonConstants.h +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#pragma once - -namespace AyonConstants -{ - const FString Ayon_PluginName = "Ayon"; - const FString PythonScript_PluginName = "PythonScriptPlugin"; - const FString SequencerScripting_PluginName = "SequencerScripting"; - const FString MovieRenderPipeline_PluginName = "MovieRenderPipeline"; - const FString EditorScriptingUtils_PluginName = "EditorScriptingUtilities"; -} - - diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonLib.h b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonLib.h deleted file mode 100644 index da83b448fb..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonLib.h +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#pragma once - -#include "AyonLib.generated.h" - - -UCLASS(Blueprintable) -class AYON_API UAyonLib : public UBlueprintFunctionLibrary -{ - - GENERATED_BODY() - -public: - UFUNCTION(BlueprintCallable, Category = Python) - static bool SetFolderColor(const FString& FolderPath, const FLinearColor& FolderColor,const bool& bForceAdd); - - UFUNCTION(BlueprintCallable, Category = Python) - static TArray GetAllProperties(UClass* cls); -}; \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonPublishInstance.h b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonPublishInstance.h deleted file mode 100644 index c89388036f..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonPublishInstance.h +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -// Deprecation warning: this is left here just for backwards compatibility -// and will be removed in next versions of Ayon. -#pragma once - -#include "AyonPublishInstance.generated.h" - - -UCLASS(Blueprintable) -class AYON_API UAyonPublishInstance : public UPrimaryDataAsset -{ - GENERATED_UCLASS_BODY() - -public: - /** - /** - * Retrieves all the assets which are monitored by the Publish Instance (Monitors assets in the directory which is - * placed in) - * - * @return - Set of UObjects. Careful! They are returning raw pointers. Seems like an issue in UE5 - */ - UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") - TSet GetInternalAssets() const - { - //For some reason it can only return Raw Pointers? Seems like an issue which they haven't fixed. - TSet ResultSet; - - for (const auto& Asset : AssetDataInternal) - ResultSet.Add(Asset.LoadSynchronous()); - - return ResultSet; - } - - /** - * Retrieves all the assets which have been added manually by the Publish Instance - * - * @return - TSet of assets (UObjects). Careful! They are returning raw pointers. Seems like an issue in UE5 - */ - UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") - TSet GetExternalAssets() const - { - //For some reason it can only return Raw Pointers? Seems like an issue which they haven't fixed. - TSet ResultSet; - - for (const auto& Asset : AssetDataExternal) - ResultSet.Add(Asset.LoadSynchronous()); - - return ResultSet; - } - - /** - * Function for returning all the assets in the container combined. - * - * @return Returns all the internal and externally added assets into one set (TSet of UObjects). Careful! They are - * returning raw pointers. Seems like an issue in UE5 - * - * @attention If the bAddExternalAssets variable is false, external assets won't be included! - */ - UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") - TSet GetAllAssets() const - { - const TSet>& IteratedSet = bAddExternalAssets - ? AssetDataInternal.Union(AssetDataExternal) - : AssetDataInternal; - - //Create a new TSet only with raw pointers. - TSet ResultSet; - - for (auto& Asset : IteratedSet) - ResultSet.Add(Asset.LoadSynchronous()); - - return ResultSet; - } - -private: - UPROPERTY(VisibleAnywhere, Category="Assets") - TSet> AssetDataInternal; - - /** - * This property allows exposing the array to include other assets from any other directory than what it's currently - * monitoring. NOTE: that these assets have to be added manually! They are not automatically registered or added! - */ - UPROPERTY(EditAnywhere, Category = "Assets") - bool bAddExternalAssets = false; - - UPROPERTY(EditAnywhere, meta=(EditCondition="bAddExternalAssets"), Category="Assets") - TSet> AssetDataExternal; - - - void OnAssetCreated(const FAssetData& InAssetData); - void OnAssetRemoved(const FAssetData& InAssetData); - void OnAssetUpdated(const FAssetData& InAssetData); - - bool IsUnderSameDir(const UObject* InAsset) const; - -#ifdef WITH_EDITOR - - void ColorAyonDirs(); - - void SendNotification(const FString& Text) const; - virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; - -#endif -}; diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonPublishInstanceFactory.h b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonPublishInstanceFactory.h deleted file mode 100644 index 3cef8e76b2..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonPublishInstanceFactory.h +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -// Deprecation warning: this is left here just for backwards compatibility -// and will be removed in next versions of Ayon. -#pragma once - -#include "CoreMinimal.h" -#include "Factories/Factory.h" -#include "AyonPublishInstanceFactory.generated.h" - -/** - * - */ -UCLASS() -class AYON_API UAyonPublishInstanceFactory : public UFactory -{ - GENERATED_BODY() - -public: - UAyonPublishInstanceFactory(const FObjectInitializer& ObjectInitializer); - virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; - virtual bool ShouldShowInNewMenu() const override; -}; diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonPythonBridge.h b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonPythonBridge.h deleted file mode 100644 index 3c429fd7d3..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonPythonBridge.h +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#pragma once -#include "AyonPythonBridge.generated.h" - -UCLASS(Blueprintable) -class UAyonPythonBridge : public UObject -{ - GENERATED_BODY() - -public: - UFUNCTION(BlueprintCallable, Category = Python) - static UAyonPythonBridge* Get(); - - UFUNCTION(BlueprintImplementableEvent, Category = Python) - void RunInPython_Popup() const; - - UFUNCTION(BlueprintImplementableEvent, Category = Python) - void RunInPython_Dialog() const; - -}; diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonSettings.h b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonSettings.h deleted file mode 100644 index 4f12d1a5f2..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonSettings.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/Object.h" -#include "AyonSettings.generated.h" - -#define AYON_SETTINGS_FILEPATH IPluginManager::Get().FindPlugin("Ayon")->GetBaseDir() / TEXT("Config") / TEXT("DefaultAyonSettings.ini") - -UCLASS(Config=AyonSettings, DefaultConfig) -class AYON_API UAyonSettings : public UObject -{ - GENERATED_UCLASS_BODY() - - UFUNCTION(BlueprintCallable, BlueprintPure, Category = Settings) - FColor GetFolderFColor() const - { - return FolderColor; - } - - UFUNCTION(BlueprintCallable, BlueprintPure, Category = Settings) - FLinearColor GetFolderFLinearColor() const - { - return FLinearColor(FolderColor); - } - -protected: - - UPROPERTY(config, EditAnywhere, Category = Folders) - FColor FolderColor = FColor(25,45,223); -}; \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonStyle.h b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonStyle.h deleted file mode 100644 index 58f6af656e..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonStyle.h +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#pragma once -#include "CoreMinimal.h" -#include "Styling/SlateStyle.h" - -class FAyonStyle -{ -public: - static void Initialize(); - static void Shutdown(); - static void ReloadTextures(); - static const ISlateStyle& Get(); - static FName GetStyleSetName(); - - -private: - static TSharedRef< class FSlateStyleSet > Create(); - static TSharedPtr< class FSlateStyleSet > AyonStyleInstance; -}; \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/Commandlets/AyonActionResult.h b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/Commandlets/AyonActionResult.h deleted file mode 100644 index bb995ec452..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/Commandlets/AyonActionResult.h +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "AyonActionResult.generated.h" - -/** - * @brief This macro returns error code when is problem or does nothing when there is no problem. - * @param ActionResult FAyon_ActionResult structure - */ -#define EVALUATE_Ayon_ACTION_RESULT(ActionResult) \ - if(ActionResult.IsProblem()) \ - return ActionResult.GetStatus(); - -/** -* @brief This enum values are humanly readable mapping of error codes. -* Here should be all error codes to be possible find what went wrong. -* TODO: In the future should exists an web document where is mapped error code & what problem occured & how to repair it... -*/ -UENUM() -namespace EAyon_ActionResult -{ - enum Type - { - Ok, - ProjectNotCreated, - ProjectNotLoaded, - ProjectNotSaved, - //....Here insert another values - - //Do not remove! - //Usable for looping through enum values - __Last UMETA(Hidden) - }; -} - - -/** - * @brief This struct holds action result enum and optionally reason of fail - */ -USTRUCT() -struct FAyon_ActionResult -{ - GENERATED_BODY() - -public: - /** @brief Default constructor usable when there is no problem */ - FAyon_ActionResult(); - - /** - * @brief This constructor initializes variables & attempts to log when is error - * @param InEnum Status - */ - FAyon_ActionResult(const EAyon_ActionResult::Type& InEnum); - - /** - * @brief This constructor initializes variables & attempts to log when is error - * @param InEnum Status - * @param InReason Reason of potential fail - */ - FAyon_ActionResult(const EAyon_ActionResult::Type& InEnum, const FText& InReason); - -private: - /** @brief Action status */ - EAyon_ActionResult::Type Status; - - /** @brief Optional reason of fail */ - FText Reason; - -public: - /** - * @brief Checks if there is problematic state - * @return true when status is not equal to EAyon_ActionResult::Ok - */ - bool IsProblem() const; - EAyon_ActionResult::Type& GetStatus(); - FText& GetReason(); - -private: - void TryLog() const; -}; - diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/Commandlets/Implementations/AyonGenerateProjectCommandlet.h b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/Commandlets/Implementations/AyonGenerateProjectCommandlet.h deleted file mode 100644 index da8e9af661..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/Commandlets/Implementations/AyonGenerateProjectCommandlet.h +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#pragma once - - -#include "GameProjectUtils.h" -#include "Commandlets/AyonActionResult.h" -#include "ProjectDescriptor.h" -#include "Commandlets/Commandlet.h" -#include "AyonGenerateProjectCommandlet.generated.h" - -struct FProjectDescriptor; -struct FProjectInformation; - -/** -* @brief Structure which parses command line parameters and generates FProjectInformation -*/ -USTRUCT() -struct FAyonGenerateProjectParams -{ - GENERATED_BODY() - -private: - FString CommandLineParams; - TArray Tokens; - TArray Switches; - -public: - FAyonGenerateProjectParams(); - FAyonGenerateProjectParams(const FString& CommandLineParams); - - FProjectInformation GenerateUEProjectInformation() const; - -private: - FString TryGetToken(const int32 Index) const; - FString GetProjectFileName() const; - - bool IsSwitchPresent(const FString& Switch) const; -}; - -UCLASS() -class AYON_API UAyonGenerateProjectCommandlet : public UCommandlet -{ - GENERATED_BODY() - -private: - FProjectInformation ProjectInformation; - FProjectDescriptor ProjectDescriptor; - -public: - UAyonGenerateProjectCommandlet(); - - virtual int32 Main(const FString& CommandLineParams) override; - -private: - FAyonGenerateProjectParams ParseParameters(const FString& Params) const; - FAyon_ActionResult TryCreateProject() const; - FAyon_ActionResult TryLoadProjectDescriptor(); - void AttachPluginsToProjectDescriptor(); - FAyon_ActionResult TrySave(); -}; - diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/Logging/Ayon_Log.h b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/Logging/Ayon_Log.h deleted file mode 100644 index 25b33a63e8..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/Logging/Ayon_Log.h +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#pragma once - -DEFINE_LOG_CATEGORY_STATIC(LogCommandletAyonGenerateProject, Log, All); \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/OpenPypePublishInstance.h b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/OpenPypePublishInstance.h deleted file mode 100644 index 9c0c4a69e5..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/OpenPypePublishInstance.h +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -// Deprecation warning: this is left here just for backwards compatibility -// and will be removed in next versions of Ayon. -#pragma once - -#include "OpenPypePublishInstance.generated.h" - - -UCLASS(Blueprintable) -class AYON_API UOpenPypePublishInstance : public UPrimaryDataAsset -{ - GENERATED_UCLASS_BODY() - -public: - /** - /** - * Retrieves all the assets which are monitored by the Publish Instance (Monitors assets in the directory which is - * placed in) - * - * @return - Set of UObjects. Careful! They are returning raw pointers. Seems like an issue in UE5 - */ - UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") - TSet GetInternalAssets() const - { - //For some reason it can only return Raw Pointers? Seems like an issue which they haven't fixed. - TSet ResultSet; - - for (const auto& Asset : AssetDataInternal) - ResultSet.Add(Asset.LoadSynchronous()); - - return ResultSet; - } - - /** - * Retrieves all the assets which have been added manually by the Publish Instance - * - * @return - TSet of assets (UObjects). Careful! They are returning raw pointers. Seems like an issue in UE5 - */ - UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") - TSet GetExternalAssets() const - { - //For some reason it can only return Raw Pointers? Seems like an issue which they haven't fixed. - TSet ResultSet; - - for (const auto& Asset : AssetDataExternal) - ResultSet.Add(Asset.LoadSynchronous()); - - return ResultSet; - } - - /** - * Function for returning all the assets in the container combined. - * - * @return Returns all the internal and externally added assets into one set (TSet of UObjects). Careful! They are - * returning raw pointers. Seems like an issue in UE5 - * - * @attention If the bAddExternalAssets variable is false, external assets won't be included! - */ - UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") - TSet GetAllAssets() const - { - const TSet>& IteratedSet = bAddExternalAssets - ? AssetDataInternal.Union(AssetDataExternal) - : AssetDataInternal; - - //Create a new TSet only with raw pointers. - TSet ResultSet; - - for (auto& Asset : IteratedSet) - ResultSet.Add(Asset.LoadSynchronous()); - - return ResultSet; - } - -private: - UPROPERTY(VisibleAnywhere, Category="Assets") - TSet> AssetDataInternal; - - /** - * This property allows exposing the array to include other assets from any other directory than what it's currently - * monitoring. NOTE: that these assets have to be added manually! They are not automatically registered or added! - */ - UPROPERTY(EditAnywhere, Category = "Assets") - bool bAddExternalAssets = false; - - UPROPERTY(EditAnywhere, meta=(EditCondition="bAddExternalAssets"), Category="Assets") - TSet> AssetDataExternal; - - - void OnAssetCreated(const FAssetData& InAssetData); - void OnAssetRemoved(const FAssetData& InAssetData); - void OnAssetUpdated(const FAssetData& InAssetData); - - bool IsUnderSameDir(const UObject* InAsset) const; - -#ifdef WITH_EDITOR - - void ColorOpenPypeDirs(); - - void SendNotification(const FString& Text) const; - virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; - -#endif -}; diff --git a/openpype/hosts/unreal/integration/UE_5.0/BuildPlugin_5-0.bat b/openpype/hosts/unreal/integration/UE_5.0/BuildPlugin_5-0.bat deleted file mode 100644 index 473c248cbe..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/BuildPlugin_5-0.bat +++ /dev/null @@ -1 +0,0 @@ -"C:\Program Files\Epic Games\UE_5.0\Engine\Build\BatchFiles\RunUAT.bat" BuildPlugin -plugin="D:\OpenPype\openpype\hosts\unreal\integration\UE_5.0\Ayon\Ayon.uplugin" -Package="D:\BuiltPlugins\5.0" \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.0/BuildPlugin_5-0_Window.bat b/openpype/hosts/unreal/integration/UE_5.0/BuildPlugin_5-0_Window.bat deleted file mode 100644 index b96de6d6c9..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/BuildPlugin_5-0_Window.bat +++ /dev/null @@ -1 +0,0 @@ -cmd /k "BuildPlugin_5-0.bat" \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/.gitignore b/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/.gitignore deleted file mode 100644 index 80814ef0a6..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/.gitignore +++ /dev/null @@ -1,41 +0,0 @@ -# Prerequisites -*.d - -# Compiled Object files -*.slo -*.lo -*.o -*.obj - -# Precompiled Headers -*.gch -*.pch - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Fortran module files -*.mod -*.smod - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables -*.exe -*.out -*.app - -/Saved -/DerivedDataCache -/Intermediate -/Binaries -/Content -/Config -/.idea -/.vs \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/CommandletProject.uproject b/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/CommandletProject.uproject deleted file mode 100644 index 9cf75ebaf2..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/CommandletProject.uproject +++ /dev/null @@ -1,20 +0,0 @@ -{ - "FileVersion": 3, - "EngineAssociation": "5.0", - "Category": "", - "Description": "", - "Plugins": [ - { - "Name": "ModelingToolsEditorMode", - "Enabled": true, - "TargetAllowList": [ - "Editor" - ] - }, - { - "Name": "Ayon", - "Enabled": true, - "Type": "Editor" - } - ] -} \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/.gitignore b/openpype/hosts/unreal/integration/UE_5.1/Ayon/.gitignore deleted file mode 100644 index b32a6f55e5..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/Ayon/.gitignore +++ /dev/null @@ -1,35 +0,0 @@ -# Prerequisites -*.d - -# Compiled Object files -*.slo -*.lo -*.o -*.obj - -# Precompiled Headers -*.gch -*.pch - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Fortran module files -*.mod -*.smod - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables -*.exe -*.out -*.app - -/Binaries -/Intermediate diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Ayon.uplugin b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Ayon.uplugin deleted file mode 100644 index 70ed8f6b9a..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Ayon.uplugin +++ /dev/null @@ -1,24 +0,0 @@ -{ - "FileVersion": 3, - "Version": 1, - "VersionName": "1.0", - "FriendlyName": "Ayon", - "Description": "Ayon Integration", - "Category": "Ayon.Integration", - "CreatedBy": "Ondrej Samohel", - "CreatedByURL": "https://ayon.ynput.io", - "DocsURL": "https://ayon.ynput.io/docs/artist_hosts_unreal", - "MarketplaceURL": "", - "SupportURL": "https://ynput.io/", - "CanContainContent": true, - "EngineVersion": "5.0", - "IsExperimentalVersion": false, - "Installed": true, - "Modules": [ - { - "Name": "Ayon", - "Type": "Editor", - "LoadingPhase": "Default" - } - ] -} diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Config/DefaultAyonSettings.ini b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Config/DefaultAyonSettings.ini deleted file mode 100644 index 9ad7f55201..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Config/DefaultAyonSettings.ini +++ /dev/null @@ -1,2 +0,0 @@ -[/Script/Ayon.AyonSettings] -FolderColor=(R=91,G=197,B=220,A=255) \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Config/FilterPlugin.ini b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Config/FilterPlugin.ini deleted file mode 100644 index ccebca2f32..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Config/FilterPlugin.ini +++ /dev/null @@ -1,8 +0,0 @@ -[FilterPlugin] -; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and -; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively. -; -; Examples: -; /README.txt -; /Extras/... -; /Binaries/ThirdParty/*.dll diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Content/Python/init_unreal.py b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Content/Python/init_unreal.py deleted file mode 100644 index c0b1d0ce5d..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Content/Python/init_unreal.py +++ /dev/null @@ -1,30 +0,0 @@ -import unreal - -ayon_detected = True -try: - from openpype.pipeline import install_host - from openpype.hosts.unreal.api import UnrealHost - - ayon_host = UnrealHost() -except ImportError as exc: - ayon_host = None - ayon_detected = False - unreal.log_error(f"Ayon: cannot load Ayon integration [ {exc} ]") - -if ayon_detected: - install_host(ayon_host) - - -@unreal.uclass() -class AyonIntegration(unreal.AyonPythonBridge): - @unreal.ufunction(override=True) - def RunInPython_Popup(self): - unreal.log_warning("Ayon: showing tools popup") - if ayon_detected: - ayon_host.show_tools_popup() - - @unreal.ufunction(override=True) - def RunInPython_Dialog(self): - unreal.log_warning("Ayon: showing tools dialog") - if ayon_detected: - ayon_host.show_tools_dialog() diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/README.md b/openpype/hosts/unreal/integration/UE_5.1/Ayon/README.md deleted file mode 100644 index 417d490548..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/Ayon/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Ayon Unreal Integration plugin - UE 5.1 - -This is plugin for Unreal Editor, creating menu for [Ayon](https://github.com/ynput/OpenPype) tools to run. diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Resources/ayon128.png b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Resources/ayon128.png deleted file mode 100644 index 799d849aa3163ecb16be39c641a6ac30324906b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2358 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 D;gw7~ diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Resources/ayon512.png b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Resources/ayon512.png deleted file mode 100644 index 990d5917e232a0644820428fb2790943de5ffaa4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16705 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 - FAyonCommands::Get().AyonTools, - FExecuteAction::CreateRaw(this, &FAyonModule::MenuPopup), - FCanExecuteAction()); - PluginCommands->MapAction( - FAyonCommands::Get().AyonToolsDialog, - FExecuteAction::CreateRaw(this, &FAyonModule::MenuDialog), - FCanExecuteAction()); - - UToolMenus::RegisterStartupCallback( - FSimpleMulticastDelegate::FDelegate::CreateRaw(this, &FAyonModule::RegisterMenus)); - - RegisterSettings(); -} - -void FAyonModule::ShutdownModule() -{ - UToolMenus::UnRegisterStartupCallback(this); - - UToolMenus::UnregisterOwner(this); - - FAyonStyle::Shutdown(); - - FAyonCommands::Unregister(); -} - - -void FAyonModule::RegisterSettings() -{ - ISettingsModule& SettingsModule = FModuleManager::LoadModuleChecked("Settings"); - - // Create the new category - // TODO: After the movement of the plugin from the game to editor, it might be necessary to move this! - ISettingsContainerPtr SettingsContainer = SettingsModule.GetContainer("Project"); - - UAyonSettings* Settings = GetMutableDefault(); - - // Register the settings - ISettingsSectionPtr SettingsSection = SettingsModule.RegisterSettings("Project", "Ayon", "General", - LOCTEXT("RuntimeGeneralSettingsName", - "General"), - LOCTEXT("RuntimeGeneralSettingsDescription", - "Base configuration for Open Pype Module"), - Settings - ); - - // Register the save handler to your settings, you might want to use it to - // validate those or just act to settings changes. - if (SettingsSection.IsValid()) - { - SettingsSection->OnModified().BindRaw(this, &FAyonModule::HandleSettingsSaved); - } -} - -bool FAyonModule::HandleSettingsSaved() -{ - UAyonSettings* Settings = GetMutableDefault(); - bool ResaveSettings = false; - - // You can put any validation code in here and resave the settings in case an invalid - // value has been entered - - if (ResaveSettings) - { - Settings->SaveConfig(); - } - - return true; -} - -void FAyonModule::RegisterMenus() -{ - // Owner will be used for cleanup in call to UToolMenus::UnregisterOwner - FToolMenuOwnerScoped OwnerScoped(this); - - { - UToolMenu* Menu = UToolMenus::Get()->ExtendMenu("LevelEditor.MainMenu.Tools"); - { - // FToolMenuSection& Section = Menu->FindOrAddSection("Ayon"); - FToolMenuSection& Section = Menu->AddSection( - "Ayon", - TAttribute(FText::FromString("Ayon")), - FToolMenuInsert("Programming", EToolMenuInsertType::Before) - ); - Section.AddMenuEntryWithCommandList(FAyonCommands::Get().AyonTools, PluginCommands); - Section.AddMenuEntryWithCommandList(FAyonCommands::Get().AyonToolsDialog, PluginCommands); - } - UToolMenu* ToolbarMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.LevelEditorToolBar.PlayToolBar"); - { - FToolMenuSection& Section = ToolbarMenu->FindOrAddSection("PluginTools"); - { - FToolMenuEntry& Entry = Section.AddEntry( - FToolMenuEntry::InitToolBarButton(FAyonCommands::Get().AyonTools)); - Entry.SetCommandList(PluginCommands); - } - } - } -} - - -void FAyonModule::MenuPopup() -{ - UAyonPythonBridge* bridge = UAyonPythonBridge::Get(); - bridge->RunInPython_Popup(); -} - -void FAyonModule::MenuDialog() -{ - UAyonPythonBridge* bridge = UAyonPythonBridge::Get(); - bridge->RunInPython_Dialog(); -} - -IMPLEMENT_MODULE(FAyonModule, Ayon) diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonAssetContainer.cpp b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonAssetContainer.cpp deleted file mode 100644 index 3022757dc8..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonAssetContainer.cpp +++ /dev/null @@ -1,113 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#include "AyonAssetContainer.h" -#include "AssetRegistry/AssetRegistryModule.h" -#include "Misc/PackageName.h" -#include "Containers/UnrealString.h" - -UAyonAssetContainer::UAyonAssetContainer(const FObjectInitializer& ObjectInitializer) -: UAssetUserData(ObjectInitializer) -{ - FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); - FString path = UAyonAssetContainer::GetPathName(); - UE_LOG(LogTemp, Warning, TEXT("UAyonAssetContainer %s"), *path); - FARFilter Filter; - Filter.PackagePaths.Add(FName(*path)); - - AssetRegistryModule.Get().OnAssetAdded().AddUObject(this, &UAyonAssetContainer::OnAssetAdded); - AssetRegistryModule.Get().OnAssetRemoved().AddUObject(this, &UAyonAssetContainer::OnAssetRemoved); - AssetRegistryModule.Get().OnAssetRenamed().AddUObject(this, &UAyonAssetContainer::OnAssetRenamed); -} - -void UAyonAssetContainer::OnAssetAdded(const FAssetData& AssetData) -{ - TArray split; - - // get directory of current container - FString selfFullPath = UAyonAssetContainer::GetPathName(); - FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath); - - // get asset path and class - FString assetPath = AssetData.GetFullName(); - FString assetFName = AssetData.GetObjectPathString(); - UE_LOG(LogTemp, Log, TEXT("asset name %s"), *assetFName); - // split path - assetPath.ParseIntoArray(split, TEXT(" "), true); - - FString assetDir = FPackageName::GetLongPackagePath(*split[1]); - - // take interest only in paths starting with path of current container - if (assetDir.StartsWith(*selfDir)) - { - // exclude self - if (assetFName != "AssetContainer") - { - assets.Add(assetPath); - assetsData.Add(AssetData); - UE_LOG(LogTemp, Log, TEXT("%s: asset added to %s"), *selfFullPath, *selfDir); - } - } -} - -void UAyonAssetContainer::OnAssetRemoved(const FAssetData& AssetData) -{ - TArray split; - - // get directory of current container - FString selfFullPath = UAyonAssetContainer::GetPathName(); - FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath); - - // get asset path and class - FString assetPath = AssetData.GetFullName(); - FString assetFName = AssetData.GetObjectPathString(); - - // split path - assetPath.ParseIntoArray(split, TEXT(" "), true); - - FString assetDir = FPackageName::GetLongPackagePath(*split[1]); - - // take interest only in paths starting with path of current container - FString path = UAyonAssetContainer::GetPathName(); - FString lpp = FPackageName::GetLongPackagePath(*path); - - if (assetDir.StartsWith(*selfDir)) - { - // exclude self - if (assetFName != "AssetContainer") - { - // UE_LOG(LogTemp, Warning, TEXT("%s: asset removed"), *lpp); - assets.Remove(assetPath); - assetsData.Remove(AssetData); - } - } -} - -void UAyonAssetContainer::OnAssetRenamed(const FAssetData& AssetData, const FString& str) -{ - TArray split; - - // get directory of current container - FString selfFullPath = UAyonAssetContainer::GetPathName(); - FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath); - - // get asset path and class - FString assetPath = AssetData.GetFullName(); - FString assetFName = AssetData.GetObjectPathString(); - - // split path - assetPath.ParseIntoArray(split, TEXT(" "), true); - - FString assetDir = FPackageName::GetLongPackagePath(*split[1]); - if (assetDir.StartsWith(*selfDir)) - { - // exclude self - if (assetFName != "AssetContainer") - { - - assets.Remove(str); - assets.Add(assetPath); - assetsData.Remove(AssetData); - // UE_LOG(LogTemp, Warning, TEXT("%s: asset renamed %s"), *lpp, *str); - } - } -} diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonAssetContainerFactory.cpp b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonAssetContainerFactory.cpp deleted file mode 100644 index 086fc1036e..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonAssetContainerFactory.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include "AyonAssetContainerFactory.h" -#include "AyonAssetContainer.h" - -UAyonAssetContainerFactory::UAyonAssetContainerFactory(const FObjectInitializer& ObjectInitializer) - : UFactory(ObjectInitializer) -{ - SupportedClass = UAyonAssetContainer::StaticClass(); - bCreateNew = false; - bEditorImport = true; -} - -UObject* UAyonAssetContainerFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) -{ - UAyonAssetContainer* AssetContainer = NewObject(InParent, Class, Name, Flags); - return AssetContainer; -} - -bool UAyonAssetContainerFactory::ShouldShowInNewMenu() const { - return false; -} diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonCommands.cpp b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonCommands.cpp deleted file mode 100644 index 566ee1dcd1..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonCommands.cpp +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. - -#include "AyonCommands.h" - -#define LOCTEXT_NAMESPACE "FAyonModule" - -void FAyonCommands::RegisterCommands() -{ - UI_COMMAND(AyonTools, "Ayon Tools", "Pipeline tools", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(AyonToolsDialog, "Ayon Tools Dialog", "Pipeline tools dialog", EUserInterfaceActionType::Button, FInputChord()); -} - -#undef LOCTEXT_NAMESPACE diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonLib.cpp b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonLib.cpp deleted file mode 100644 index 7cfa0c9c30..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonLib.cpp +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#include "AyonLib.h" - -#include "AssetViewUtils.h" -#include "UObject/UnrealType.h" - -/** - * Sets color on folder icon on given path - * @param InPath - path to folder - * @param InFolderColor - color of the folder - * @warning This color will appear only after Editor restart. Is there a better way? - */ - -bool UAyonLib::SetFolderColor(const FString& FolderPath, const FLinearColor& FolderColor, const bool& bForceAdd) -{ - if (AssetViewUtils::DoesFolderExist(FolderPath)) - { - const TSharedPtr LinearColor = MakeShared(FolderColor); - - AssetViewUtils::SaveColor(FolderPath, LinearColor, true); - UE_LOG(LogAssetData, Display, TEXT("A color {%s} has been set to folder \"%s\""), *LinearColor->ToString(), - *FolderPath) - return true; - } - - UE_LOG(LogAssetData, Display, TEXT("Setting a color {%s} to folder \"%s\" has failed! Directory doesn't exist!"), - *FolderColor.ToString(), *FolderPath) - return false; -} - -/** - * Returns all poperties on given object - * @param cls - class - * @return TArray of properties - */ -TArray UAyonLib::GetAllProperties(UClass* cls) -{ - TArray Ret; - if (cls != nullptr) - { - for (TFieldIterator It(cls); It; ++It) - { - FProperty* Property = *It; - if (Property->HasAnyPropertyFlags(EPropertyFlags::CPF_Edit)) - { - Ret.Add(Property->GetName()); - } - } - } - return Ret; -} diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonPublishInstance.cpp b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonPublishInstance.cpp deleted file mode 100644 index d1b47a19d4..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonPublishInstance.cpp +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -// Deprecation warning: this is left here just for backwards compatibility -// and will be removed in next versions of Ayon. -#pragma once - -#include "AyonPublishInstance.h" -#include "AssetRegistry/AssetRegistryModule.h" -#include "AssetToolsModule.h" -#include "Framework/Notifications/NotificationManager.h" -#include "AyonLib.h" -#include "AyonSettings.h" -#include "Widgets/Notifications/SNotificationList.h" - - -//Moves all the invalid pointers to the end to prepare them for the shrinking -#define REMOVE_INVALID_ENTRIES(VAR) VAR.CompactStable(); \ - VAR.Shrink(); - -UAyonPublishInstance::UAyonPublishInstance(const FObjectInitializer& ObjectInitializer) - : UPrimaryDataAsset(ObjectInitializer) -{ - const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked< - FAssetRegistryModule>("AssetRegistry"); - - const FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked( - "PropertyEditor"); - - FString Left, Right; - GetPathName().Split("/" + GetName(), &Left, &Right); - - FARFilter Filter; - Filter.PackagePaths.Emplace(FName(Left)); - - TArray FoundAssets; - AssetRegistryModule.GetRegistry().GetAssets(Filter, FoundAssets); - - for (const FAssetData& AssetData : FoundAssets) - OnAssetCreated(AssetData); - - REMOVE_INVALID_ENTRIES(AssetDataInternal) - REMOVE_INVALID_ENTRIES(AssetDataExternal) - - AssetRegistryModule.Get().OnAssetAdded().AddUObject(this, &UAyonPublishInstance::OnAssetCreated); - AssetRegistryModule.Get().OnAssetRemoved().AddUObject(this, &UAyonPublishInstance::OnAssetRemoved); - AssetRegistryModule.Get().OnAssetUpdated().AddUObject(this, &UAyonPublishInstance::OnAssetUpdated); - -#ifdef WITH_EDITOR - ColorAyonDirs(); -#endif -} - -void UAyonPublishInstance::OnAssetCreated(const FAssetData& InAssetData) -{ - TArray split; - - UObject* Asset = InAssetData.GetAsset(); - - if (!IsValid(Asset)) - { - UE_LOG(LogAssetData, Warning, TEXT("Asset \"%s\" is not valid! Skipping the addition."), - *InAssetData.GetSoftObjectPath().ToString()); - return; - } - - const bool result = IsUnderSameDir(Asset) && Cast(Asset) == nullptr; - - if (result) - { - if (AssetDataInternal.Emplace(Asset).IsValidId()) - { - UE_LOG(LogTemp, Log, TEXT("Added an Asset to PublishInstance - Publish Instance: %s, Asset %s"), - *this->GetName(), *Asset->GetName()); - } - } -} - -void UAyonPublishInstance::OnAssetRemoved(const FAssetData& InAssetData) -{ - if (Cast(InAssetData.GetAsset()) == nullptr) - { - if (AssetDataInternal.Contains(nullptr)) - { - AssetDataInternal.Remove(nullptr); - REMOVE_INVALID_ENTRIES(AssetDataInternal) - } - else - { - AssetDataExternal.Remove(nullptr); - REMOVE_INVALID_ENTRIES(AssetDataExternal) - } - } -} - -void UAyonPublishInstance::OnAssetUpdated(const FAssetData& InAssetData) -{ - REMOVE_INVALID_ENTRIES(AssetDataInternal); - REMOVE_INVALID_ENTRIES(AssetDataExternal); -} - -bool UAyonPublishInstance::IsUnderSameDir(const UObject* InAsset) const -{ - FString ThisLeft, ThisRight; - this->GetPathName().Split(this->GetName(), &ThisLeft, &ThisRight); - - return InAsset->GetPathName().StartsWith(ThisLeft); -} - -#ifdef WITH_EDITOR - -void UAyonPublishInstance::ColorAyonDirs() -{ - FString PathName = this->GetPathName(); - - //Check whether the path contains the defined Ayon folder - if (!PathName.Contains(TEXT("Ayon"))) return; - - //Get the base path for open pype - FString PathLeft, PathRight; - PathName.Split(FString("Ayon"), &PathLeft, &PathRight); - - if (PathLeft.IsEmpty() || PathRight.IsEmpty()) - { - UE_LOG(LogAssetData, Error, TEXT("Failed to retrieve the base Ayon directory!")) - return; - } - - PathName.RemoveFromEnd(PathRight, ESearchCase::CaseSensitive); - - //Get the current settings - const UAyonSettings* Settings = GetMutableDefault(); - - //Color the base folder - UAyonLib::SetFolderColor(PathName, Settings->GetFolderFColor(), false); - - //Get Sub paths, iterate through them and color them according to the folder color in UAyonSettings - const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked( - "AssetRegistry"); - - TArray PathList; - - AssetRegistryModule.Get().GetSubPaths(PathName, PathList, true); - - if (PathList.Num() > 0) - { - for (const FString& Path : PathList) - { - UAyonLib::SetFolderColor(Path, Settings->GetFolderFColor(), false); - } - } -} - -void UAyonPublishInstance::SendNotification(const FString& Text) const -{ - FNotificationInfo Info{FText::FromString(Text)}; - - Info.bFireAndForget = true; - Info.bUseLargeFont = false; - Info.bUseThrobber = false; - Info.bUseSuccessFailIcons = false; - Info.ExpireDuration = 4.f; - Info.FadeOutDuration = 2.f; - - FSlateNotificationManager::Get().AddNotification(Info); - - UE_LOG(LogAssetData, Warning, - TEXT( - "Removed duplicated asset from the AssetsDataExternal in Container \"%s\", Asset is already included in the AssetDataInternal!" - ), *GetName() - ) -} - - -void UAyonPublishInstance::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) -{ - Super::PostEditChangeProperty(PropertyChangedEvent); - - if (PropertyChangedEvent.ChangeType == EPropertyChangeType::ValueSet && - PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED( - UAyonPublishInstance, AssetDataExternal)) - { - // Check for duplicated assets - for (const auto& Asset : AssetDataInternal) - { - if (AssetDataExternal.Contains(Asset)) - { - AssetDataExternal.Remove(Asset); - return SendNotification( - "You are not allowed to add assets into AssetDataExternal which are already included in AssetDataInternal!"); - } - } - - // Check if no UAyonPublishInstance type assets are included - for (const auto& Asset : AssetDataExternal) - { - if (Cast(Asset.Get()) != nullptr) - { - AssetDataExternal.Remove(Asset); - return SendNotification("You are not allowed to add publish instances!"); - } - } - } -} - -#endif diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonPublishInstanceFactory.cpp b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonPublishInstanceFactory.cpp deleted file mode 100644 index f79c428a6d..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonPublishInstanceFactory.cpp +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -// Deprecation warning: this is left here just for backwards compatibility -// and will be removed in next versions of Ayon. -#include "AyonPublishInstanceFactory.h" -#include "AyonPublishInstance.h" - -UAyonPublishInstanceFactory::UAyonPublishInstanceFactory(const FObjectInitializer& ObjectInitializer) - : UFactory(ObjectInitializer) -{ - SupportedClass = UAyonPublishInstance::StaticClass(); - bCreateNew = false; - bEditorImport = true; -} - -UObject* UAyonPublishInstanceFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) -{ - check(InClass->IsChildOf(UAyonPublishInstance::StaticClass())); - return NewObject(InParent, InClass, InName, Flags); -} - -bool UAyonPublishInstanceFactory::ShouldShowInNewMenu() const { - return false; -} diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonPythonBridge.cpp b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonPythonBridge.cpp deleted file mode 100644 index 0ed4b2f704..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonPythonBridge.cpp +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#include "AyonPythonBridge.h" - -UAyonPythonBridge* UAyonPythonBridge::Get() -{ - TArray AyonPythonBridgeClasses; - GetDerivedClasses(UAyonPythonBridge::StaticClass(), AyonPythonBridgeClasses); - int32 NumClasses = AyonPythonBridgeClasses.Num(); - if (NumClasses > 0) - { - return Cast(AyonPythonBridgeClasses[NumClasses - 1]->GetDefaultObject()); - } - return nullptr; -}; \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonSettings.cpp b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonSettings.cpp deleted file mode 100644 index da388fbc8f..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonSettings.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. - -#include "AyonSettings.h" - -#include "Interfaces/IPluginManager.h" -#include "UObject/UObjectGlobals.h" - -/** - * Mainly is used for initializing default values if the DefaultAyonSettings.ini file does not exist in the saved config - */ -UAyonSettings::UAyonSettings(const FObjectInitializer& ObjectInitializer) -{ - - const FString ConfigFilePath = AYON_SETTINGS_FILEPATH; - - // This has to be probably in the future set using the UE Reflection system - FColor Color; - GConfig->GetColor(TEXT("/Script/Ayon.AyonSettings"), TEXT("FolderColor"), Color, ConfigFilePath); - - FolderColor = Color; -} \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonStyle.cpp b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonStyle.cpp deleted file mode 100644 index d88df78735..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonStyle.cpp +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. - -#include "AyonStyle.h" -#include "Framework/Application/SlateApplication.h" -#include "Styling/SlateStyleRegistry.h" -#include "Slate/SlateGameResources.h" -#include "Interfaces/IPluginManager.h" -#include "Styling/SlateStyleMacros.h" - -#define RootToContentDir Style->RootToContentDir - -TSharedPtr FAyonStyle::AyonStyleInstance = nullptr; - -void FAyonStyle::Initialize() -{ - if (!AyonStyleInstance.IsValid()) - { - AyonStyleInstance = Create(); - FSlateStyleRegistry::RegisterSlateStyle(*AyonStyleInstance); - } -} - -void FAyonStyle::Shutdown() -{ - FSlateStyleRegistry::UnRegisterSlateStyle(*AyonStyleInstance); - ensure(AyonStyleInstance.IsUnique()); - AyonStyleInstance.Reset(); -} - -FName FAyonStyle::GetStyleSetName() -{ - static FName StyleSetName(TEXT("AyonStyle")); - return StyleSetName; -} - -const FVector2D Icon16x16(16.0f, 16.0f); -const FVector2D Icon20x20(20.0f, 20.0f); -const FVector2D Icon40x40(40.0f, 40.0f); - -TSharedRef< FSlateStyleSet > FAyonStyle::Create() -{ - TSharedRef< FSlateStyleSet > Style = MakeShareable(new FSlateStyleSet("AyonStyle")); - Style->SetContentRoot(IPluginManager::Get().FindPlugin("Ayon")->GetBaseDir() / TEXT("Resources")); - - Style->Set("Ayon.AyonTools", new IMAGE_BRUSH(TEXT("ayon40"), Icon40x40)); - Style->Set("Ayon.AyonToolsDialog", new IMAGE_BRUSH(TEXT("ayon40"), Icon40x40)); - - return Style; -} - -void FAyonStyle::ReloadTextures() -{ - if (FSlateApplication::IsInitialized()) - { - FSlateApplication::Get().GetRenderer()->ReloadTextureResources(); - } -} - -const ISlateStyle& FAyonStyle::Get() -{ - return *AyonStyleInstance; -} diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/Commandlets/AyonActionResult.cpp b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/Commandlets/AyonActionResult.cpp deleted file mode 100644 index 2a137e3ed7..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/Commandlets/AyonActionResult.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. - -#include "Commandlets/AyonActionResult.h" -#include "Logging/Ayon_Log.h" - -EAyon_ActionResult::Type& FAyon_ActionResult::GetStatus() -{ - return Status; -} - -FText& FAyon_ActionResult::GetReason() -{ - return Reason; -} - -FAyon_ActionResult::FAyon_ActionResult():Status(EAyon_ActionResult::Type::Ok) -{ - -} - -FAyon_ActionResult::FAyon_ActionResult(const EAyon_ActionResult::Type& InEnum):Status(InEnum) -{ - TryLog(); -} - -FAyon_ActionResult::FAyon_ActionResult(const EAyon_ActionResult::Type& InEnum, const FText& InReason):Status(InEnum), Reason(InReason) -{ - TryLog(); -}; - -bool FAyon_ActionResult::IsProblem() const -{ - return Status != EAyon_ActionResult::Ok; -} - -void FAyon_ActionResult::TryLog() const -{ - if(IsProblem()) - UE_LOG(LogCommandletAyonGenerateProject, Error, TEXT("%s"), *Reason.ToString()); -} diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/Commandlets/Implementations/AyonGenerateProjectCommandlet.cpp b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/Commandlets/Implementations/AyonGenerateProjectCommandlet.cpp deleted file mode 100644 index ed876c8128..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/Commandlets/Implementations/AyonGenerateProjectCommandlet.cpp +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#include "Commandlets/Implementations/AyonGenerateProjectCommandlet.h" - -#include "GameProjectUtils.h" -#include "AyonConstants.h" -#include "Commandlets/AyonActionResult.h" -#include "ProjectDescriptor.h" - -int32 UAyonGenerateProjectCommandlet::Main(const FString& CommandLineParams) -{ - //Parses command line parameters & creates structure FProjectInformation - const FAyonGenerateProjectParams ParsedParams = FAyonGenerateProjectParams(CommandLineParams); - ProjectInformation = ParsedParams.GenerateUEProjectInformation(); - - //Creates .uproject & other UE files - EVALUATE_Ayon_ACTION_RESULT(TryCreateProject()); - - //Loads created .uproject - EVALUATE_Ayon_ACTION_RESULT(TryLoadProjectDescriptor()); - - //Adds needed plugin to .uproject - AttachPluginsToProjectDescriptor(); - - //Saves .uproject - EVALUATE_Ayon_ACTION_RESULT(TrySave()); - - //When we are here, there should not be problems in generating Unreal Project for Ayon - return 0; -} - - -FAyonGenerateProjectParams::FAyonGenerateProjectParams(): FAyonGenerateProjectParams("") -{ -} - -FAyonGenerateProjectParams::FAyonGenerateProjectParams(const FString& CommandLineParams): CommandLineParams( - CommandLineParams) -{ - UCommandlet::ParseCommandLine(*CommandLineParams, Tokens, Switches); -} - -FProjectInformation FAyonGenerateProjectParams::GenerateUEProjectInformation() const -{ - FProjectInformation ProjectInformation = FProjectInformation(); - ProjectInformation.ProjectFilename = GetProjectFileName(); - - ProjectInformation.bShouldGenerateCode = IsSwitchPresent("GenerateCode"); - - return ProjectInformation; -} - -FString FAyonGenerateProjectParams::TryGetToken(const int32 Index) const -{ - return Tokens.IsValidIndex(Index) ? Tokens[Index] : ""; -} - -FString FAyonGenerateProjectParams::GetProjectFileName() const -{ - return TryGetToken(0); -} - -bool FAyonGenerateProjectParams::IsSwitchPresent(const FString& Switch) const -{ - return INDEX_NONE != Switches.IndexOfByPredicate([&Switch](const FString& Item) -> bool - { - return Item.Equals(Switch); - } - ); -} - - -UAyonGenerateProjectCommandlet::UAyonGenerateProjectCommandlet() -{ - LogToConsole = true; -} - -FAyon_ActionResult UAyonGenerateProjectCommandlet::TryCreateProject() const -{ - FText FailReason; - FText FailLog; - TArray OutCreatedFiles; - - if (!GameProjectUtils::CreateProject(ProjectInformation, FailReason, FailLog, &OutCreatedFiles)) - return FAyon_ActionResult(EAyon_ActionResult::ProjectNotCreated, FailReason); - return FAyon_ActionResult(); -} - -FAyon_ActionResult UAyonGenerateProjectCommandlet::TryLoadProjectDescriptor() -{ - FText FailReason; - const bool bLoaded = ProjectDescriptor.Load(ProjectInformation.ProjectFilename, FailReason); - - return FAyon_ActionResult(bLoaded ? EAyon_ActionResult::Ok : EAyon_ActionResult::ProjectNotLoaded, FailReason); -} - -void UAyonGenerateProjectCommandlet::AttachPluginsToProjectDescriptor() -{ - FPluginReferenceDescriptor AyonPluginDescriptor; - AyonPluginDescriptor.bEnabled = true; - AyonPluginDescriptor.Name = AyonConstants::Ayon_PluginName; - ProjectDescriptor.Plugins.Add(AyonPluginDescriptor); - - FPluginReferenceDescriptor PythonPluginDescriptor; - PythonPluginDescriptor.bEnabled = true; - PythonPluginDescriptor.Name = AyonConstants::PythonScript_PluginName; - ProjectDescriptor.Plugins.Add(PythonPluginDescriptor); - - FPluginReferenceDescriptor SequencerScriptingPluginDescriptor; - SequencerScriptingPluginDescriptor.bEnabled = true; - SequencerScriptingPluginDescriptor.Name = AyonConstants::SequencerScripting_PluginName; - ProjectDescriptor.Plugins.Add(SequencerScriptingPluginDescriptor); - - FPluginReferenceDescriptor MovieRenderPipelinePluginDescriptor; - MovieRenderPipelinePluginDescriptor.bEnabled = true; - MovieRenderPipelinePluginDescriptor.Name = AyonConstants::MovieRenderPipeline_PluginName; - ProjectDescriptor.Plugins.Add(MovieRenderPipelinePluginDescriptor); - - FPluginReferenceDescriptor EditorScriptingPluginDescriptor; - EditorScriptingPluginDescriptor.bEnabled = true; - EditorScriptingPluginDescriptor.Name = AyonConstants::EditorScriptingUtils_PluginName; - ProjectDescriptor.Plugins.Add(EditorScriptingPluginDescriptor); -} - -FAyon_ActionResult UAyonGenerateProjectCommandlet::TrySave() -{ - FText FailReason; - const bool bSaved = ProjectDescriptor.Save(ProjectInformation.ProjectFilename, FailReason); - - return FAyon_ActionResult(bSaved ? EAyon_ActionResult::Ok : EAyon_ActionResult::ProjectNotSaved, FailReason); -} - -FAyonGenerateProjectParams UAyonGenerateProjectCommandlet::ParseParameters(const FString& Params) const -{ - FAyonGenerateProjectParams ParamsResult; - - TArray Tokens, Switches; - ParseCommandLine(*Params, Tokens, Switches); - - return ParamsResult; -} diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/OpenPypePublishInstance.cpp b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/OpenPypePublishInstance.cpp deleted file mode 100644 index 02a8ac800a..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/OpenPypePublishInstance.cpp +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -// Deprecation warning: this is left here just for backwards compatibility -// and will be removed in next versions of Ayon. -#pragma once - -#include "OpenPypePublishInstance.h" -#include "AssetRegistry/AssetRegistryModule.h" -#include "AssetToolsModule.h" -#include "Framework/Notifications/NotificationManager.h" -#include "AyonLib.h" -#include "AyonSettings.h" -#include "Widgets/Notifications/SNotificationList.h" - - -//Moves all the invalid pointers to the end to prepare them for the shrinking -#define REMOVE_INVALID_ENTRIES(VAR) VAR.CompactStable(); \ - VAR.Shrink(); - -UOpenPypePublishInstance::UOpenPypePublishInstance(const FObjectInitializer& ObjectInitializer) - : UPrimaryDataAsset(ObjectInitializer) -{ - const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked< - FAssetRegistryModule>("AssetRegistry"); - - const FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked( - "PropertyEditor"); - - FString Left, Right; - GetPathName().Split("/" + GetName(), &Left, &Right); - - FARFilter Filter; - Filter.PackagePaths.Emplace(FName(Left)); - - TArray FoundAssets; - AssetRegistryModule.GetRegistry().GetAssets(Filter, FoundAssets); - - for (const FAssetData& AssetData : FoundAssets) - OnAssetCreated(AssetData); - - REMOVE_INVALID_ENTRIES(AssetDataInternal) - REMOVE_INVALID_ENTRIES(AssetDataExternal) - - AssetRegistryModule.Get().OnAssetAdded().AddUObject(this, &UOpenPypePublishInstance::OnAssetCreated); - AssetRegistryModule.Get().OnAssetRemoved().AddUObject(this, &UOpenPypePublishInstance::OnAssetRemoved); - AssetRegistryModule.Get().OnAssetUpdated().AddUObject(this, &UOpenPypePublishInstance::OnAssetUpdated); - -#ifdef WITH_EDITOR - ColorOpenPypeDirs(); -#endif -} - -void UOpenPypePublishInstance::OnAssetCreated(const FAssetData& InAssetData) -{ - TArray split; - - UObject* Asset = InAssetData.GetAsset(); - - if (!IsValid(Asset)) - { - UE_LOG(LogAssetData, Warning, TEXT("Asset \"%s\" is not valid! Skipping the addition."), - *InAssetData.GetSoftObjectPath().ToString()); - return; - } - - const bool result = IsUnderSameDir(Asset) && Cast(Asset) == nullptr; - - if (result) - { - if (AssetDataInternal.Emplace(Asset).IsValidId()) - { - UE_LOG(LogTemp, Log, TEXT("Added an Asset to PublishInstance - Publish Instance: %s, Asset %s"), - *this->GetName(), *Asset->GetName()); - } - } -} - -void UOpenPypePublishInstance::OnAssetRemoved(const FAssetData& InAssetData) -{ - if (Cast(InAssetData.GetAsset()) == nullptr) - { - if (AssetDataInternal.Contains(nullptr)) - { - AssetDataInternal.Remove(nullptr); - REMOVE_INVALID_ENTRIES(AssetDataInternal) - } - else - { - AssetDataExternal.Remove(nullptr); - REMOVE_INVALID_ENTRIES(AssetDataExternal) - } - } -} - -void UOpenPypePublishInstance::OnAssetUpdated(const FAssetData& InAssetData) -{ - REMOVE_INVALID_ENTRIES(AssetDataInternal); - REMOVE_INVALID_ENTRIES(AssetDataExternal); -} - -bool UOpenPypePublishInstance::IsUnderSameDir(const UObject* InAsset) const -{ - FString ThisLeft, ThisRight; - this->GetPathName().Split(this->GetName(), &ThisLeft, &ThisRight); - - return InAsset->GetPathName().StartsWith(ThisLeft); -} - -#ifdef WITH_EDITOR - -void UOpenPypePublishInstance::ColorOpenPypeDirs() -{ - FString PathName = this->GetPathName(); - - //Check whether the path contains the defined OpenPype folder - if (!PathName.Contains(TEXT("OpenPype"))) return; - - //Get the base path for open pype - FString PathLeft, PathRight; - PathName.Split(FString("OpenPype"), &PathLeft, &PathRight); - - if (PathLeft.IsEmpty() || PathRight.IsEmpty()) - { - UE_LOG(LogAssetData, Error, TEXT("Failed to retrieve the base OpenPype directory!")) - return; - } - - PathName.RemoveFromEnd(PathRight, ESearchCase::CaseSensitive); - - //Get the current settings - const UAyonSettings* Settings = GetMutableDefault(); - - //Color the base folder - UAyonLib::SetFolderColor(PathName, Settings->GetFolderFColor(), false); - - //Get Sub paths, iterate through them and color them according to the folder color in UOpenPypeSettings - const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked( - "AssetRegistry"); - - TArray PathList; - - AssetRegistryModule.Get().GetSubPaths(PathName, PathList, true); - - if (PathList.Num() > 0) - { - for (const FString& Path : PathList) - { - UAyonLib::SetFolderColor(Path, Settings->GetFolderFColor(), false); - } - } -} - -void UOpenPypePublishInstance::SendNotification(const FString& Text) const -{ - FNotificationInfo Info{FText::FromString(Text)}; - - Info.bFireAndForget = true; - Info.bUseLargeFont = false; - Info.bUseThrobber = false; - Info.bUseSuccessFailIcons = false; - Info.ExpireDuration = 4.f; - Info.FadeOutDuration = 2.f; - - FSlateNotificationManager::Get().AddNotification(Info); - - UE_LOG(LogAssetData, Warning, - TEXT( - "Removed duplicated asset from the AssetsDataExternal in Container \"%s\", Asset is already included in the AssetDataInternal!" - ), *GetName() - ) -} - - -void UOpenPypePublishInstance::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) -{ - Super::PostEditChangeProperty(PropertyChangedEvent); - - if (PropertyChangedEvent.ChangeType == EPropertyChangeType::ValueSet && - PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED( - UOpenPypePublishInstance, AssetDataExternal)) - { - // Check for duplicated assets - for (const auto& Asset : AssetDataInternal) - { - if (AssetDataExternal.Contains(Asset)) - { - AssetDataExternal.Remove(Asset); - return SendNotification( - "You are not allowed to add assets into AssetDataExternal which are already included in AssetDataInternal!"); - } - } - - // Check if no UOpenPypePublishInstance type assets are included - for (const auto& Asset : AssetDataExternal) - { - if (Cast(Asset.Get()) != nullptr) - { - AssetDataExternal.Remove(Asset); - return SendNotification("You are not allowed to add publish instances!"); - } - } - } -} - -#endif diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/Ayon.h b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/Ayon.h deleted file mode 100644 index bb25430411..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/Ayon.h +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. - -#pragma once - -#include "CoreMinimal.h" - - -class FAyonModule : public IModuleInterface -{ -public: - virtual void StartupModule() override; - virtual void ShutdownModule() override; - -private: - void RegisterMenus(); - void RegisterSettings(); - bool HandleSettingsSaved(); - - void MenuPopup(); - void MenuDialog(); - -private: - TSharedPtr PluginCommands; -}; diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonAssetContainer.h b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonAssetContainer.h deleted file mode 100644 index d40642b149..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonAssetContainer.h +++ /dev/null @@ -1,34 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/NoExportTypes.h" -#include "Engine/AssetUserData.h" -#include "AssetRegistry/AssetData.h" -#include "AyonAssetContainer.generated.h" - -UCLASS(Blueprintable) -class AYON_API UAyonAssetContainer : public UAssetUserData -{ - GENERATED_BODY() - -public: - - UAyonAssetContainer(const FObjectInitializer& ObjectInitalizer); - // ~UAyonAssetContainer(); - - UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Assets") - TArray assets; - - // There seems to be no reflection option to expose array of FAssetData - /* - UPROPERTY(Transient, BlueprintReadOnly, Category = "Python", meta=(DisplayName="Assets Data")) - TArray assetsData; - */ -private: - TArray assetsData; - void OnAssetAdded(const FAssetData& AssetData); - void OnAssetRemoved(const FAssetData& AssetData); - void OnAssetRenamed(const FAssetData& AssetData, const FString& str); -}; diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonAssetContainerFactory.h b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonAssetContainerFactory.h deleted file mode 100644 index da424cde2e..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonAssetContainerFactory.h +++ /dev/null @@ -1,18 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#pragma once - -#include "CoreMinimal.h" -#include "Factories/Factory.h" -#include "AyonAssetContainerFactory.generated.h" - -UCLASS() -class AYON_API UAyonAssetContainerFactory : public UFactory -{ - GENERATED_BODY() - -public: - UAyonAssetContainerFactory(const FObjectInitializer& ObjectInitializer); - virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; - virtual bool ShouldShowInNewMenu() const override; -}; diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonCommands.h b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonCommands.h deleted file mode 100644 index 9c40dc8241..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonCommands.h +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "Framework/Commands/Commands.h" -#include "AyonStyle.h" - -class FAyonCommands : public TCommands -{ -public: - - FAyonCommands() - : TCommands(TEXT("Ayon"), NSLOCTEXT("Contexts", "Ayon", "Ayon Tools"), NAME_None, FAyonStyle::GetStyleSetName()) - { - } - - // TCommands<> interface - virtual void RegisterCommands() override; - -public: - TSharedPtr< FUICommandInfo > AyonTools; - TSharedPtr< FUICommandInfo > AyonToolsDialog; -}; diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonConstants.h b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonConstants.h deleted file mode 100644 index 5fe7c14360..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonConstants.h +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#pragma once - -namespace AyonConstants -{ - const FString Ayon_PluginName = "Ayon"; - const FString PythonScript_PluginName = "PythonScriptPlugin"; - const FString SequencerScripting_PluginName = "SequencerScripting"; - const FString MovieRenderPipeline_PluginName = "MovieRenderPipeline"; - const FString EditorScriptingUtils_PluginName = "EditorScriptingUtilities"; -} - - diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonLib.h b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonLib.h deleted file mode 100644 index da83b448fb..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonLib.h +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#pragma once - -#include "AyonLib.generated.h" - - -UCLASS(Blueprintable) -class AYON_API UAyonLib : public UBlueprintFunctionLibrary -{ - - GENERATED_BODY() - -public: - UFUNCTION(BlueprintCallable, Category = Python) - static bool SetFolderColor(const FString& FolderPath, const FLinearColor& FolderColor,const bool& bForceAdd); - - UFUNCTION(BlueprintCallable, Category = Python) - static TArray GetAllProperties(UClass* cls); -}; \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonPublishInstance.h b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonPublishInstance.h deleted file mode 100644 index c89388036f..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonPublishInstance.h +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -// Deprecation warning: this is left here just for backwards compatibility -// and will be removed in next versions of Ayon. -#pragma once - -#include "AyonPublishInstance.generated.h" - - -UCLASS(Blueprintable) -class AYON_API UAyonPublishInstance : public UPrimaryDataAsset -{ - GENERATED_UCLASS_BODY() - -public: - /** - /** - * Retrieves all the assets which are monitored by the Publish Instance (Monitors assets in the directory which is - * placed in) - * - * @return - Set of UObjects. Careful! They are returning raw pointers. Seems like an issue in UE5 - */ - UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") - TSet GetInternalAssets() const - { - //For some reason it can only return Raw Pointers? Seems like an issue which they haven't fixed. - TSet ResultSet; - - for (const auto& Asset : AssetDataInternal) - ResultSet.Add(Asset.LoadSynchronous()); - - return ResultSet; - } - - /** - * Retrieves all the assets which have been added manually by the Publish Instance - * - * @return - TSet of assets (UObjects). Careful! They are returning raw pointers. Seems like an issue in UE5 - */ - UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") - TSet GetExternalAssets() const - { - //For some reason it can only return Raw Pointers? Seems like an issue which they haven't fixed. - TSet ResultSet; - - for (const auto& Asset : AssetDataExternal) - ResultSet.Add(Asset.LoadSynchronous()); - - return ResultSet; - } - - /** - * Function for returning all the assets in the container combined. - * - * @return Returns all the internal and externally added assets into one set (TSet of UObjects). Careful! They are - * returning raw pointers. Seems like an issue in UE5 - * - * @attention If the bAddExternalAssets variable is false, external assets won't be included! - */ - UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") - TSet GetAllAssets() const - { - const TSet>& IteratedSet = bAddExternalAssets - ? AssetDataInternal.Union(AssetDataExternal) - : AssetDataInternal; - - //Create a new TSet only with raw pointers. - TSet ResultSet; - - for (auto& Asset : IteratedSet) - ResultSet.Add(Asset.LoadSynchronous()); - - return ResultSet; - } - -private: - UPROPERTY(VisibleAnywhere, Category="Assets") - TSet> AssetDataInternal; - - /** - * This property allows exposing the array to include other assets from any other directory than what it's currently - * monitoring. NOTE: that these assets have to be added manually! They are not automatically registered or added! - */ - UPROPERTY(EditAnywhere, Category = "Assets") - bool bAddExternalAssets = false; - - UPROPERTY(EditAnywhere, meta=(EditCondition="bAddExternalAssets"), Category="Assets") - TSet> AssetDataExternal; - - - void OnAssetCreated(const FAssetData& InAssetData); - void OnAssetRemoved(const FAssetData& InAssetData); - void OnAssetUpdated(const FAssetData& InAssetData); - - bool IsUnderSameDir(const UObject* InAsset) const; - -#ifdef WITH_EDITOR - - void ColorAyonDirs(); - - void SendNotification(const FString& Text) const; - virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; - -#endif -}; diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonPublishInstanceFactory.h b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonPublishInstanceFactory.h deleted file mode 100644 index 3cef8e76b2..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonPublishInstanceFactory.h +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -// Deprecation warning: this is left here just for backwards compatibility -// and will be removed in next versions of Ayon. -#pragma once - -#include "CoreMinimal.h" -#include "Factories/Factory.h" -#include "AyonPublishInstanceFactory.generated.h" - -/** - * - */ -UCLASS() -class AYON_API UAyonPublishInstanceFactory : public UFactory -{ - GENERATED_BODY() - -public: - UAyonPublishInstanceFactory(const FObjectInitializer& ObjectInitializer); - virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; - virtual bool ShouldShowInNewMenu() const override; -}; diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonPythonBridge.h b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonPythonBridge.h deleted file mode 100644 index 3c429fd7d3..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonPythonBridge.h +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#pragma once -#include "AyonPythonBridge.generated.h" - -UCLASS(Blueprintable) -class UAyonPythonBridge : public UObject -{ - GENERATED_BODY() - -public: - UFUNCTION(BlueprintCallable, Category = Python) - static UAyonPythonBridge* Get(); - - UFUNCTION(BlueprintImplementableEvent, Category = Python) - void RunInPython_Popup() const; - - UFUNCTION(BlueprintImplementableEvent, Category = Python) - void RunInPython_Dialog() const; - -}; diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonSettings.h b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonSettings.h deleted file mode 100644 index 4f12d1a5f2..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonSettings.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/Object.h" -#include "AyonSettings.generated.h" - -#define AYON_SETTINGS_FILEPATH IPluginManager::Get().FindPlugin("Ayon")->GetBaseDir() / TEXT("Config") / TEXT("DefaultAyonSettings.ini") - -UCLASS(Config=AyonSettings, DefaultConfig) -class AYON_API UAyonSettings : public UObject -{ - GENERATED_UCLASS_BODY() - - UFUNCTION(BlueprintCallable, BlueprintPure, Category = Settings) - FColor GetFolderFColor() const - { - return FolderColor; - } - - UFUNCTION(BlueprintCallable, BlueprintPure, Category = Settings) - FLinearColor GetFolderFLinearColor() const - { - return FLinearColor(FolderColor); - } - -protected: - - UPROPERTY(config, EditAnywhere, Category = Folders) - FColor FolderColor = FColor(25,45,223); -}; \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonStyle.h b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonStyle.h deleted file mode 100644 index 58f6af656e..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonStyle.h +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#pragma once -#include "CoreMinimal.h" -#include "Styling/SlateStyle.h" - -class FAyonStyle -{ -public: - static void Initialize(); - static void Shutdown(); - static void ReloadTextures(); - static const ISlateStyle& Get(); - static FName GetStyleSetName(); - - -private: - static TSharedRef< class FSlateStyleSet > Create(); - static TSharedPtr< class FSlateStyleSet > AyonStyleInstance; -}; \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/Commandlets/AyonActionResult.h b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/Commandlets/AyonActionResult.h deleted file mode 100644 index bb995ec452..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/Commandlets/AyonActionResult.h +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "AyonActionResult.generated.h" - -/** - * @brief This macro returns error code when is problem or does nothing when there is no problem. - * @param ActionResult FAyon_ActionResult structure - */ -#define EVALUATE_Ayon_ACTION_RESULT(ActionResult) \ - if(ActionResult.IsProblem()) \ - return ActionResult.GetStatus(); - -/** -* @brief This enum values are humanly readable mapping of error codes. -* Here should be all error codes to be possible find what went wrong. -* TODO: In the future should exists an web document where is mapped error code & what problem occured & how to repair it... -*/ -UENUM() -namespace EAyon_ActionResult -{ - enum Type - { - Ok, - ProjectNotCreated, - ProjectNotLoaded, - ProjectNotSaved, - //....Here insert another values - - //Do not remove! - //Usable for looping through enum values - __Last UMETA(Hidden) - }; -} - - -/** - * @brief This struct holds action result enum and optionally reason of fail - */ -USTRUCT() -struct FAyon_ActionResult -{ - GENERATED_BODY() - -public: - /** @brief Default constructor usable when there is no problem */ - FAyon_ActionResult(); - - /** - * @brief This constructor initializes variables & attempts to log when is error - * @param InEnum Status - */ - FAyon_ActionResult(const EAyon_ActionResult::Type& InEnum); - - /** - * @brief This constructor initializes variables & attempts to log when is error - * @param InEnum Status - * @param InReason Reason of potential fail - */ - FAyon_ActionResult(const EAyon_ActionResult::Type& InEnum, const FText& InReason); - -private: - /** @brief Action status */ - EAyon_ActionResult::Type Status; - - /** @brief Optional reason of fail */ - FText Reason; - -public: - /** - * @brief Checks if there is problematic state - * @return true when status is not equal to EAyon_ActionResult::Ok - */ - bool IsProblem() const; - EAyon_ActionResult::Type& GetStatus(); - FText& GetReason(); - -private: - void TryLog() const; -}; - diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/Commandlets/Implementations/AyonGenerateProjectCommandlet.h b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/Commandlets/Implementations/AyonGenerateProjectCommandlet.h deleted file mode 100644 index da8e9af661..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/Commandlets/Implementations/AyonGenerateProjectCommandlet.h +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#pragma once - - -#include "GameProjectUtils.h" -#include "Commandlets/AyonActionResult.h" -#include "ProjectDescriptor.h" -#include "Commandlets/Commandlet.h" -#include "AyonGenerateProjectCommandlet.generated.h" - -struct FProjectDescriptor; -struct FProjectInformation; - -/** -* @brief Structure which parses command line parameters and generates FProjectInformation -*/ -USTRUCT() -struct FAyonGenerateProjectParams -{ - GENERATED_BODY() - -private: - FString CommandLineParams; - TArray Tokens; - TArray Switches; - -public: - FAyonGenerateProjectParams(); - FAyonGenerateProjectParams(const FString& CommandLineParams); - - FProjectInformation GenerateUEProjectInformation() const; - -private: - FString TryGetToken(const int32 Index) const; - FString GetProjectFileName() const; - - bool IsSwitchPresent(const FString& Switch) const; -}; - -UCLASS() -class AYON_API UAyonGenerateProjectCommandlet : public UCommandlet -{ - GENERATED_BODY() - -private: - FProjectInformation ProjectInformation; - FProjectDescriptor ProjectDescriptor; - -public: - UAyonGenerateProjectCommandlet(); - - virtual int32 Main(const FString& CommandLineParams) override; - -private: - FAyonGenerateProjectParams ParseParameters(const FString& Params) const; - FAyon_ActionResult TryCreateProject() const; - FAyon_ActionResult TryLoadProjectDescriptor(); - void AttachPluginsToProjectDescriptor(); - FAyon_ActionResult TrySave(); -}; - diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/Logging/Ayon_Log.h b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/Logging/Ayon_Log.h deleted file mode 100644 index 25b33a63e8..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/Logging/Ayon_Log.h +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#pragma once - -DEFINE_LOG_CATEGORY_STATIC(LogCommandletAyonGenerateProject, Log, All); \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/OpenPypePublishInstance.h b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/OpenPypePublishInstance.h deleted file mode 100644 index 9c0c4a69e5..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/OpenPypePublishInstance.h +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -// Deprecation warning: this is left here just for backwards compatibility -// and will be removed in next versions of Ayon. -#pragma once - -#include "OpenPypePublishInstance.generated.h" - - -UCLASS(Blueprintable) -class AYON_API UOpenPypePublishInstance : public UPrimaryDataAsset -{ - GENERATED_UCLASS_BODY() - -public: - /** - /** - * Retrieves all the assets which are monitored by the Publish Instance (Monitors assets in the directory which is - * placed in) - * - * @return - Set of UObjects. Careful! They are returning raw pointers. Seems like an issue in UE5 - */ - UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") - TSet GetInternalAssets() const - { - //For some reason it can only return Raw Pointers? Seems like an issue which they haven't fixed. - TSet ResultSet; - - for (const auto& Asset : AssetDataInternal) - ResultSet.Add(Asset.LoadSynchronous()); - - return ResultSet; - } - - /** - * Retrieves all the assets which have been added manually by the Publish Instance - * - * @return - TSet of assets (UObjects). Careful! They are returning raw pointers. Seems like an issue in UE5 - */ - UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") - TSet GetExternalAssets() const - { - //For some reason it can only return Raw Pointers? Seems like an issue which they haven't fixed. - TSet ResultSet; - - for (const auto& Asset : AssetDataExternal) - ResultSet.Add(Asset.LoadSynchronous()); - - return ResultSet; - } - - /** - * Function for returning all the assets in the container combined. - * - * @return Returns all the internal and externally added assets into one set (TSet of UObjects). Careful! They are - * returning raw pointers. Seems like an issue in UE5 - * - * @attention If the bAddExternalAssets variable is false, external assets won't be included! - */ - UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") - TSet GetAllAssets() const - { - const TSet>& IteratedSet = bAddExternalAssets - ? AssetDataInternal.Union(AssetDataExternal) - : AssetDataInternal; - - //Create a new TSet only with raw pointers. - TSet ResultSet; - - for (auto& Asset : IteratedSet) - ResultSet.Add(Asset.LoadSynchronous()); - - return ResultSet; - } - -private: - UPROPERTY(VisibleAnywhere, Category="Assets") - TSet> AssetDataInternal; - - /** - * This property allows exposing the array to include other assets from any other directory than what it's currently - * monitoring. NOTE: that these assets have to be added manually! They are not automatically registered or added! - */ - UPROPERTY(EditAnywhere, Category = "Assets") - bool bAddExternalAssets = false; - - UPROPERTY(EditAnywhere, meta=(EditCondition="bAddExternalAssets"), Category="Assets") - TSet> AssetDataExternal; - - - void OnAssetCreated(const FAssetData& InAssetData); - void OnAssetRemoved(const FAssetData& InAssetData); - void OnAssetUpdated(const FAssetData& InAssetData); - - bool IsUnderSameDir(const UObject* InAsset) const; - -#ifdef WITH_EDITOR - - void ColorOpenPypeDirs(); - - void SendNotification(const FString& Text) const; - virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; - -#endif -}; diff --git a/openpype/hosts/unreal/integration/UE_5.1/BuildPlugin_5-1.bat b/openpype/hosts/unreal/integration/UE_5.1/BuildPlugin_5-1.bat deleted file mode 100644 index 3cc82d54af..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/BuildPlugin_5-1.bat +++ /dev/null @@ -1 +0,0 @@ -"D:\UE_5.1\Engine\Build\BatchFiles\RunUAT.bat" BuildPlugin -plugin="D:\OpenPype\openpype\hosts\unreal\integration\UE_5.1\Ayon\Ayon.uplugin" -Package="D:\BuiltPlugins\5.1" \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.1/BuildPlugin_5-1_Window.bat b/openpype/hosts/unreal/integration/UE_5.1/BuildPlugin_5-1_Window.bat deleted file mode 100644 index e10f2c7add..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/BuildPlugin_5-1_Window.bat +++ /dev/null @@ -1 +0,0 @@ -cmd /k "BuildPlugin_5-1.bat" \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.1/CommandletProject/.gitignore b/openpype/hosts/unreal/integration/UE_5.1/CommandletProject/.gitignore deleted file mode 100644 index 80814ef0a6..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/CommandletProject/.gitignore +++ /dev/null @@ -1,41 +0,0 @@ -# Prerequisites -*.d - -# Compiled Object files -*.slo -*.lo -*.o -*.obj - -# Precompiled Headers -*.gch -*.pch - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Fortran module files -*.mod -*.smod - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables -*.exe -*.out -*.app - -/Saved -/DerivedDataCache -/Intermediate -/Binaries -/Content -/Config -/.idea -/.vs \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.1/CommandletProject/CommandletProject.uproject b/openpype/hosts/unreal/integration/UE_5.1/CommandletProject/CommandletProject.uproject deleted file mode 100644 index fe83346624..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.1/CommandletProject/CommandletProject.uproject +++ /dev/null @@ -1,20 +0,0 @@ -{ - "FileVersion": 3, - "EngineAssociation": "5.1", - "Category": "", - "Description": "", - "Plugins": [ - { - "Name": "ModelingToolsEditorMode", - "Enabled": true, - "TargetAllowList": [ - "Editor" - ] - }, - { - "Name": "Ayon", - "Enabled": true, - "Type": "Editor" - } - ] -} \ No newline at end of file From bc1ce951035917705669b1a66188dfe0109469e9 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 17 May 2023 12:22:21 +0200 Subject: [PATCH 107/130] =?UTF-8?q?=F0=9F=A7=B1=20add=20unreal=20plugin=20?= =?UTF-8?q?repo=20as=20submodule?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitmodules | 5 ++++- openpype/hosts/unreal/integration | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) create mode 160000 openpype/hosts/unreal/integration diff --git a/.gitmodules b/.gitmodules index fe93791c4e..4de92471f7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,4 +4,7 @@ [submodule "tools/modules/powershell/PSWriteColor"] path = tools/modules/powershell/PSWriteColor - url = https://github.com/EvotecIT/PSWriteColor.git \ No newline at end of file + url = https://github.com/EvotecIT/PSWriteColor.git +[submodule "openpype/hosts/unreal/integration"] + path = openpype/hosts/unreal/integration + url = https://github.com/ynput/ayon-unreal-plugin.git diff --git a/openpype/hosts/unreal/integration b/openpype/hosts/unreal/integration new file mode 160000 index 0000000000..ff15c70077 --- /dev/null +++ b/openpype/hosts/unreal/integration @@ -0,0 +1 @@ +Subproject commit ff15c700771e719cc5f3d561ac5d6f7590623986 From da29890d9be2634d92115c6e960ba066e4e48954 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Wed, 17 May 2023 13:48:34 +0200 Subject: [PATCH 108/130] Update openpype/hosts/fusion/plugins/publish/extract_render_local.py Co-authored-by: Roy Nieterau --- openpype/hosts/fusion/plugins/publish/extract_render_local.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/plugins/publish/extract_render_local.py b/openpype/hosts/fusion/plugins/publish/extract_render_local.py index f093f7793f..f801f30577 100644 --- a/openpype/hosts/fusion/plugins/publish/extract_render_local.py +++ b/openpype/hosts/fusion/plugins/publish/extract_render_local.py @@ -83,7 +83,7 @@ class FusionRenderLocal( # Only active instances instance.data.get("publish", True) and # Only render.local instances - "render.local" in instance.data.get("families") + "render.local" in instance.data.get("families", []) ] if key not in context.data: From 6b09504eadb131dfcb78834c6b67ae90102fd8ca Mon Sep 17 00:00:00 2001 From: Zipodod <49460980+Zipodod@users.noreply.github.com> Date: Wed, 17 May 2023 08:14:26 -0400 Subject: [PATCH 109/130] Bugfix/frame variable fix (#4978) * Fix variable name on Max reset frame range * Fix variable name on Maya collect animation * Fix variable name on Nuke reset frame range * Fix lines over max width * Fix error on variable rename * Fix line over max width --------- Co-authored-by: jbeaulieu --- openpype/hosts/max/api/lib.py | 12 ++++++++---- openpype/hosts/maya/api/lib.py | 10 ++++++---- openpype/hosts/nuke/api/lib.py | 8 ++++---- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 1310097f29..d9213863b1 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -245,11 +245,15 @@ def reset_frame_range(fps: bool = True): fps_number = float(data_fps["data"]["fps"]) rt.frameRate = fps_number frame_range = get_frame_range() - frame_start = frame_range["frameStart"] - int(frame_range["handleStart"]) - frame_end = frame_range["frameEnd"] + int(frame_range["handleEnd"]) - frange_cmd = f"animationRange = interval {frame_start} {frame_end}" + frame_start_handle = frame_range["frameStart"] - int( + frame_range["handleStart"] + ) + frame_end_handle = frame_range["frameEnd"] + int(frame_range["handleEnd"]) + frange_cmd = ( + f"animationRange = interval {frame_start_handle} {frame_end_handle}" + ) rt.execute(frange_cmd) - set_render_frame_range(frame_start, frame_end) + set_render_frame_range(frame_start_handle, frame_end_handle) def set_context_setting(): diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index f814187cc1..0bcadbe76e 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -316,11 +316,13 @@ def collect_animation_data(fps=False): # get scene values as defaults frame_start = cmds.playbackOptions(query=True, minTime=True) frame_end = cmds.playbackOptions(query=True, maxTime=True) - handle_start = cmds.playbackOptions(query=True, animationStartTime=True) - handle_end = cmds.playbackOptions(query=True, animationEndTime=True) + frame_start_handle = cmds.playbackOptions( + query=True, animationStartTime=True + ) + frame_end_handle = cmds.playbackOptions(query=True, animationEndTime=True) - handle_start = frame_start - handle_start - handle_end = handle_end - frame_end + handle_start = frame_start - frame_start_handle + handle_end = frame_end_handle - frame_end # build attributes data = OrderedDict() diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 64fa32a383..a439142051 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2239,13 +2239,13 @@ class WorkfileSettings(object): handle_end = data["handleEnd"] fps = float(data["fps"]) - frame_start = int(data["frameStart"]) - handle_start - frame_end = int(data["frameEnd"]) + handle_end + frame_start_handle = int(data["frameStart"]) - handle_start + frame_end_handle = int(data["frameEnd"]) + handle_end self._root_node["lock_range"].setValue(False) self._root_node["fps"].setValue(fps) - self._root_node["first_frame"].setValue(frame_start) - self._root_node["last_frame"].setValue(frame_end) + self._root_node["first_frame"].setValue(frame_start_handle) + self._root_node["last_frame"].setValue(frame_end_handle) self._root_node["lock_range"].setValue(True) # setting active viewers From 0f80ad01ec243e08e6d7824772861a2b655c64f6 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 17 May 2023 14:16:49 +0200 Subject: [PATCH 110/130] adding deadline settings including Pools --- .../plugins/publish/submit_fusion_deadline.py | 9 ++-- .../defaults/project_settings/deadline.json | 9 ++++ .../schema_project_deadline.json | 44 +++++++++++++++++++ 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py b/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py index d51299506c..717391100d 100644 --- a/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py @@ -38,10 +38,6 @@ class FusionSubmitDeadline( chunk_size = 1 concurrent_tasks = 1 group = "" - department = "" - limit_groups = {} - env_allowed_keys = [] - env_search_replace_values = {} @classmethod def get_attribute_defs(cls): @@ -173,8 +169,9 @@ class FusionSubmitDeadline( # User, as seen in Monitor "UserName": deadline_user, - # Use a default submission pool for Fusion - "Pool": "fusion", + "Pool": instance.data.get("primaryPool"), + "SecondaryPool": instance.data.get("secondaryPool"), + "Group": self.group, "Plugin": "Fusion", "Frames": "{start}-{end}".format( diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index 3f114025f3..1b8c8397d7 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -45,6 +45,15 @@ "chunk_size": 10, "group": "none" }, + "FusionSubmitDeadline": { + "enabled": true, + "optional": false, + "active": true, + "priority": 50, + "chunk_size": 10, + "concurrent_tasks": 1, + "group": "" + }, "NukeSubmitDeadline": { "enabled": true, "optional": false, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json index d8b5e4dc1f..6d59b5a92b 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json @@ -248,6 +248,50 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "key": "FusionSubmitDeadline", + "label": "Fusion submit to Deadline", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "boolean", + "key": "active", + "label": "Active" + }, + { + "type": "number", + "key": "priority", + "label": "Priority" + }, + { + "type": "number", + "key": "chunk_size", + "label": "Frame per Task" + }, + { + "type": "number", + "key": "concurrent_tasks", + "label": "Number of concurrent tasks" + }, + { + "type": "text", + "key": "group", + "label": "Group Name" + } + ] + }, { "type": "dict", "collapsible": true, From a6059afe869aed7996d44699f87ef30e338da4ae Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 17 May 2023 14:37:37 +0200 Subject: [PATCH 111/130] pr comments also renamed start_handle as it is easily confusable with handles --- .../fusion/plugins/create/create_saver.py | 2 +- .../plugins/publish/collect_instances.py | 41 +++++++++++-------- .../fusion/plugins/publish/collect_render.py | 2 +- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/openpype/hosts/fusion/plugins/create/create_saver.py b/openpype/hosts/fusion/plugins/create/create_saver.py index d5e77730c8..28917cb27d 100644 --- a/openpype/hosts/fusion/plugins/create/create_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_saver.py @@ -233,7 +233,7 @@ class CreateSaver(NewCreator): def _get_frame_range_enum(self): frame_range_options = { "asset_db": "From asset database", - "viewer_render_range": "From viewer render in/out", + "render_range": "From viewer render in/out", "comp_range": "From composition timeline" } diff --git a/openpype/hosts/fusion/plugins/publish/collect_instances.py b/openpype/hosts/fusion/plugins/publish/collect_instances.py index 59ff52f5b2..98ea0a34e4 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_instances.py +++ b/openpype/hosts/fusion/plugins/publish/collect_instances.py @@ -20,25 +20,28 @@ class CollectInstanceData(pyblish.api.InstancePlugin): # Include creator attributes directly as instance data creator_attributes = instance.data["creator_attributes"] - frame_range_source = creator_attributes.get("frame_range_source") instance.data.update(creator_attributes) - # get asset frame ranges - start = context.data["frameStart"] - end = context.data["frameEnd"] - handle_start = context.data["handleStart"] - handle_end = context.data["handleEnd"] - start_handle = start - handle_start - end_handle = end + handle_end + frame_range_source = creator_attributes.get("frame_range_source") + instance.data["frame_range_source"] = frame_range_source - if frame_range_source == "viewer_render_range": + if frame_range_source == "asset_db": + # get asset frame ranges + start = context.data["frameStart"] + end = context.data["frameEnd"] + handle_start = context.data["handleStart"] + handle_end = context.data["handleEnd"] + start_with_handle = start - handle_start + end_with_handle = end + handle_end + + if frame_range_source == "render_range": # set comp render frame ranges start = context.data["renderFrameStart"] end = context.data["renderFrameEnd"] handle_start = 0 handle_end = 0 - start_handle = start - end_handle = end + start_with_handle = start + end_with_handle = end if frame_range_source == "comp_range": comp_start = context.data["compFrameStart"] @@ -50,14 +53,16 @@ class CollectInstanceData(pyblish.api.InstancePlugin): end = render_end handle_start = render_start - comp_start handle_end = comp_end - render_end - start_handle = comp_start - end_handle = comp_end + start_with_handle = comp_start + end_with_handle = comp_end # Include start and end render frame in label subset = instance.data["subset"] - label = "{subset} ({start}-{end})".format(subset=subset, - start=int(start), - end=int(end)) + label = "{subset} ({start}-{end})".format( + subset=subset, + start=int(start), + end=int(end) + ) instance.data.update({ "label": label, @@ -65,8 +70,8 @@ class CollectInstanceData(pyblish.api.InstancePlugin): # todo: Allow custom frame range per instance "frameStart": start, "frameEnd": end, - "frameStartHandle": start_handle, - "frameEndHandle": end_handle, + "frameStartHandle": start_with_handle, + "frameEndHandle": end_with_handle, "handleStart": handle_start, "handleEnd": handle_end, "fps": context.data["fps"], diff --git a/openpype/hosts/fusion/plugins/publish/collect_render.py b/openpype/hosts/fusion/plugins/publish/collect_render.py index 6956b566ad..dd6d9c2567 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_render.py +++ b/openpype/hosts/fusion/plugins/publish/collect_render.py @@ -84,7 +84,7 @@ class CollectFusionRender( handleStart=inst.data["handleStart"], handleEnd=inst.data["handleEnd"], ignoreFrameHandleCheck=( - not inst.data.get("viewer_render_range")), + inst.data["frame_range_source"] == "render_range"), frameStep=1, fps=comp_frame_format_prefs.get("Rate"), app_version=comp.GetApp().Version, From 497a97b70d1eef294e0b7d2ea14365f5add380b4 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 17 May 2023 14:40:47 +0200 Subject: [PATCH 112/130] pr comment --- 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 28917cb27d..f1e7791972 100644 --- a/openpype/hosts/fusion/plugins/create/create_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_saver.py @@ -232,7 +232,7 @@ class CreateSaver(NewCreator): def _get_frame_range_enum(self): frame_range_options = { - "asset_db": "From asset database", + "asset_db": "Current asset context", "render_range": "From viewer render in/out", "comp_range": "From composition timeline" } From fb3c4b613ffd08ac5677a11c2a42f9d217770ca7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 17 May 2023 14:47:32 +0200 Subject: [PATCH 113/130] improving label --- .../hosts/fusion/plugins/publish/collect_instances.py | 8 ++++++-- openpype/hosts/fusion/plugins/publish/collect_render.py | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/fusion/plugins/publish/collect_instances.py b/openpype/hosts/fusion/plugins/publish/collect_instances.py index 98ea0a34e4..458f00c7ed 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_instances.py +++ b/openpype/hosts/fusion/plugins/publish/collect_instances.py @@ -58,10 +58,14 @@ class CollectInstanceData(pyblish.api.InstancePlugin): # Include start and end render frame in label subset = instance.data["subset"] - label = "{subset} ({start}-{end})".format( + label = ( + "{subset} ({start}-{end}) [{handle_start}-{handle_end}]" + ).format( subset=subset, start=int(start), - end=int(end) + end=int(end), + handle_start=int(handle_start), + handle_end=int(handle_end) ) instance.data.update({ diff --git a/openpype/hosts/fusion/plugins/publish/collect_render.py b/openpype/hosts/fusion/plugins/publish/collect_render.py index dd6d9c2567..c13d2a0c99 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_render.py +++ b/openpype/hosts/fusion/plugins/publish/collect_render.py @@ -64,7 +64,7 @@ class CollectFusionRender( version=version, time="", source=current_file, - label="{} - {}".format(subset_name, family), + label=inst.data["label"], subset=subset_name, asset=inst.data["asset"], task=task_name, From 089fe88ee104c7f0c3d7d85f66d9d03f3aafbbbf Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 17 May 2023 14:53:09 +0200 Subject: [PATCH 114/130] task is on context --- openpype/hosts/fusion/plugins/publish/collect_render.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/plugins/publish/collect_render.py b/openpype/hosts/fusion/plugins/publish/collect_render.py index c13d2a0c99..0a850a4982 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_render.py +++ b/openpype/hosts/fusion/plugins/publish/collect_render.py @@ -51,7 +51,7 @@ class CollectFusionRender( if family != "render": continue - task_name = inst.data.get("task") # legacy + task_name = context.data["task"] tool = inst.data["transientData"]["tool"] instance_families = inst.data.get("families", []) From 163756d74c93ad74b5edf0095ee2279de66fabef Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 17 May 2023 14:58:24 +0200 Subject: [PATCH 115/130] adding comment for ambiguous function call --- openpype/hosts/fusion/plugins/publish/collect_render.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/fusion/plugins/publish/collect_render.py b/openpype/hosts/fusion/plugins/publish/collect_render.py index 0a850a4982..cbb9fea76d 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_render.py +++ b/openpype/hosts/fusion/plugins/publish/collect_render.py @@ -125,6 +125,7 @@ class CollectFusionRender( def post_collecting_action(self): for instance in self._context: if "render.frames" in instance.data.get("families", []): + # adding representation data to the instance self._update_for_frames(instance) def get_expected_files(self, render_instance): From 40426cd69c4c70723254d0b301d7060fc858a1f7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 17 May 2023 15:03:21 +0200 Subject: [PATCH 116/130] simplifying code --- .../fusion/plugins/publish/collect_render.py | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/fusion/plugins/publish/collect_render.py b/openpype/hosts/fusion/plugins/publish/collect_render.py index cbb9fea76d..d0b7f1c4ff 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_render.py +++ b/openpype/hosts/fusion/plugins/publish/collect_render.py @@ -32,8 +32,8 @@ class CollectFusionRender( comp = context.data.get("currentComp") comp_frame_format_prefs = comp.GetPrefs("Comp.FrameFormat") - aspect_x = comp_frame_format_prefs.get("AspectX") - aspect_y = comp_frame_format_prefs.get("AspectY") + aspect_x = comp_frame_format_prefs["AspectX"] + aspect_y = comp_frame_format_prefs["AspectY"] instances = [] instances_to_remove = [] @@ -93,14 +93,14 @@ class CollectFusionRender( render_target = inst.data["creator_attributes"]["render_target"] - if render_target == "local": - # for local renders - self._instance_data_local_update( - project_entity, instance, f"render.{render_target}") + # Add render target family + render_target_family = f"render.{render_target}" + if render_target_family not in instance.families: + instance.families.append(render_target_family) - if render_target == "frames": - self._instance_data_local_update( - project_entity, instance, f"render.{render_target}") + # Add render target specific data + if render_target in {"local", "frames"}: + instance.projectEntity = project_entity if render_target == "farm": fam = "render.farm" @@ -205,8 +205,3 @@ class CollectFusionRender( instance.data["representations"].append(repre) return instance - - def _instance_data_local_update(self, project_entity, instance, family): - instance.projectEntity = project_entity - if family not in instance.families: - instance.families.append(family) From 25832ed496f79063c4c2f0c3f9c87eb0e4b227c9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 17 May 2023 15:07:10 +0200 Subject: [PATCH 117/130] collect frame range simplification --- .../publish/collect_comp_frame_range.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/fusion/plugins/publish/collect_comp_frame_range.py b/openpype/hosts/fusion/plugins/publish/collect_comp_frame_range.py index 08bdad3120..24a9a92337 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_comp_frame_range.py +++ b/openpype/hosts/fusion/plugins/publish/collect_comp_frame_range.py @@ -34,16 +34,11 @@ class CollectFusionCompFrameRanges(pyblish.api.ContextPlugin): comp = context.data["currentComp"] # Store comp render ranges - ( - start, end, - global_start, - global_end, - ) = get_comp_render_range(comp) + start, end, global_start, global_end = get_comp_render_range(comp) - data = {} - data["renderFrameStart"] = int(start) - data["renderFrameEnd"] = int(end) - data["compFrameStart"] = int(global_start) - data["compFrameEnd"] = int(global_end) - - context.data.update(data) + context.data.update({ + "renderFrameStart": int(start), + "renderFrameEnd": int(end), + "compFrameStart": int(global_start), + "compFrameEnd": int(global_end) + }) From d39fe870923383c62ea8995b578c9b3b82d8995a Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 17 May 2023 14:58:10 +0100 Subject: [PATCH 118/130] Fix abc loading in Blender --- .../hosts/blender/plugins/load/load_abc.py | 35 +++++-------------- 1 file changed, 9 insertions(+), 26 deletions(-) diff --git a/openpype/hosts/blender/plugins/load/load_abc.py b/openpype/hosts/blender/plugins/load/load_abc.py index 1b2e800769..c1d73eff02 100644 --- a/openpype/hosts/blender/plugins/load/load_abc.py +++ b/openpype/hosts/blender/plugins/load/load_abc.py @@ -65,37 +65,19 @@ class CacheModelLoader(plugin.AssetLoader): imported = lib.get_selection() - empties = [obj for obj in imported if obj.type == 'EMPTY'] - - container = None - - for empty in empties: - if not empty.parent: - container = empty - break - - assert container, "No asset group found" - # Children must be linked before parents, # otherwise the hierarchy will break objects = [] - nodes = list(container.children) - for obj in nodes: + for obj in imported: obj.parent = asset_group - bpy.data.objects.remove(container) - - for obj in nodes: + for obj in imported: objects.append(obj) - nodes.extend(list(obj.children)) + imported.extend(list(obj.children)) objects.reverse() - for obj in objects: - parent.objects.link(obj) - collection.objects.unlink(obj) - for obj in objects: name = obj.name obj.name = f"{group_name}:{name}" @@ -138,13 +120,14 @@ class CacheModelLoader(plugin.AssetLoader): group_name = plugin.asset_name(asset, subset, unique_number) namespace = namespace or f"{asset}_{unique_number}" - avalon_container = bpy.data.collections.get(AVALON_CONTAINERS) - if not avalon_container: - avalon_container = bpy.data.collections.new(name=AVALON_CONTAINERS) - bpy.context.scene.collection.children.link(avalon_container) + avalon_containers = bpy.data.collections.get(AVALON_CONTAINERS) + if not avalon_containers: + avalon_containers = bpy.data.collections.new( + name=AVALON_CONTAINERS) + bpy.context.scene.collection.children.link(avalon_containers) asset_group = bpy.data.objects.new(group_name, object_data=None) - avalon_container.objects.link(asset_group) + avalon_containers.objects.link(asset_group) objects = self._process(libpath, asset_group, group_name) From 9ea2bb0c4d039636fd27142c12a7c0e87119d6cd Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 18 May 2023 15:24:19 +0800 Subject: [PATCH 119/130] use pymxs instead of maxscript for fbx loader --- .../hosts/max/plugins/load/load_camera_fbx.py | 24 +++++++------------ .../hosts/max/plugins/load/load_model_fbx.py | 22 +++++++---------- 2 files changed, 17 insertions(+), 29 deletions(-) diff --git a/openpype/hosts/max/plugins/load/load_camera_fbx.py b/openpype/hosts/max/plugins/load/load_camera_fbx.py index ce4dec32a0..9767321e3d 100644 --- a/openpype/hosts/max/plugins/load/load_camera_fbx.py +++ b/openpype/hosts/max/plugins/load/load_camera_fbx.py @@ -20,21 +20,15 @@ class FbxLoader(load.LoaderPlugin): from pymxs import runtime as rt filepath = os.path.normpath(self.fname) - - fbx_import_cmd = ( - f""" - -FBXImporterSetParam "Animation" true -FBXImporterSetParam "Cameras" true -FBXImporterSetParam "AxisConversionMethod" true -FbxExporterSetParam "UpAxis" "Y" -FbxExporterSetParam "Preserveinstances" true - -importFile @"{filepath}" #noPrompt using:FBXIMP - """) - - self.log.debug(f"Executing command: {fbx_import_cmd}") - rt.execute(fbx_import_cmd) + rt.FBXImporterSetParam("Animation", True) + rt.FBXImporterSetParam("Camera", True) + rt.FBXImporterSetParam("AxisConversionMethod", True) + rt.FBXImporterSetParam("UpAxis", "Y") + rt.FBXImporterSetParam("Preserveinstances", True) + rt.importFile( + filepath, + rt.name("noPrompt"), + using=rt.FBXIMP) container = rt.getNodeByName(f"{name}") if not container: diff --git a/openpype/hosts/max/plugins/load/load_model_fbx.py b/openpype/hosts/max/plugins/load/load_model_fbx.py index 7532e3a8a0..bf51e3b191 100644 --- a/openpype/hosts/max/plugins/load/load_model_fbx.py +++ b/openpype/hosts/max/plugins/load/load_model_fbx.py @@ -21,21 +21,15 @@ class FbxModelLoader(load.LoaderPlugin): from pymxs import runtime as rt filepath = os.path.normpath(self.fname) + rt.FBXImporterSetParam("Animation", False) + rt.FBXImporterSetParam("Cameras", False) + rt.FBXImporterSetParam("UpAxis", "Y") + rt.FBXImporterSetParam("Preserveinstances", True) + rt.importFile( + filepath, + rt.name("noPrompt"), + using=rt.FBXIMP) - fbx_import_cmd = ( - f""" - -FBXImporterSetParam "Animation" false -FBXImporterSetParam "Cameras" false -FBXImporterSetParam "AxisConversionMethod" true -FbxExporterSetParam "UpAxis" "Y" -FbxExporterSetParam "Preserveinstances" true - -importFile @"{filepath}" #noPrompt using:FBXIMP - """) - - self.log.debug(f"Executing command: {fbx_import_cmd}") - rt.execute(fbx_import_cmd) container = rt.getNodeByName(f"{name}") if not container: container = rt.container() From 6fd4cbf2998f6d486858d01472d6385c8f2bc809 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 18 May 2023 16:47:58 +0800 Subject: [PATCH 120/130] fix the axis conversion issue --- openpype/hosts/max/plugins/load/load_camera_fbx.py | 1 - openpype/hosts/max/plugins/load/load_model_fbx.py | 1 - 2 files changed, 2 deletions(-) diff --git a/openpype/hosts/max/plugins/load/load_camera_fbx.py b/openpype/hosts/max/plugins/load/load_camera_fbx.py index 9767321e3d..0c5dd762cf 100644 --- a/openpype/hosts/max/plugins/load/load_camera_fbx.py +++ b/openpype/hosts/max/plugins/load/load_camera_fbx.py @@ -23,7 +23,6 @@ class FbxLoader(load.LoaderPlugin): rt.FBXImporterSetParam("Animation", True) rt.FBXImporterSetParam("Camera", True) rt.FBXImporterSetParam("AxisConversionMethod", True) - rt.FBXImporterSetParam("UpAxis", "Y") rt.FBXImporterSetParam("Preserveinstances", True) rt.importFile( filepath, diff --git a/openpype/hosts/max/plugins/load/load_model_fbx.py b/openpype/hosts/max/plugins/load/load_model_fbx.py index bf51e3b191..01e6acae12 100644 --- a/openpype/hosts/max/plugins/load/load_model_fbx.py +++ b/openpype/hosts/max/plugins/load/load_model_fbx.py @@ -23,7 +23,6 @@ class FbxModelLoader(load.LoaderPlugin): filepath = os.path.normpath(self.fname) rt.FBXImporterSetParam("Animation", False) rt.FBXImporterSetParam("Cameras", False) - rt.FBXImporterSetParam("UpAxis", "Y") rt.FBXImporterSetParam("Preserveinstances", True) rt.importFile( filepath, From 60efa939a86196749a1dab1f5afe2fbdc01991dc Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 18 May 2023 10:59:13 +0200 Subject: [PATCH 121/130] OP-5714 - allow returning stub with not saved workfile (#4984) Without it is not possible to create first workile. --- openpype/hosts/aftereffects/api/pipeline.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/openpype/hosts/aftereffects/api/pipeline.py b/openpype/hosts/aftereffects/api/pipeline.py index 95f6f3235b..020022e263 100644 --- a/openpype/hosts/aftereffects/api/pipeline.py +++ b/openpype/hosts/aftereffects/api/pipeline.py @@ -60,9 +60,6 @@ class AfterEffectsHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): print("Not connected yet, ignoring") return - if not stub.get_active_document_name(): - return - self._stub = stub return self._stub From 87802d06203ad1cdad22d2aec5112fba1a04f0e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Thu, 18 May 2023 14:39:27 +0200 Subject: [PATCH 122/130] Fix: Download last workfile doesn't work if wf not already downloaded (#4942) --- openpype/modules/sync_server/sync_server.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/modules/sync_server/sync_server.py b/openpype/modules/sync_server/sync_server.py index d1d5c2863d..98065b68a0 100644 --- a/openpype/modules/sync_server/sync_server.py +++ b/openpype/modules/sync_server/sync_server.py @@ -237,8 +237,7 @@ def download_last_published_workfile( last_published_workfile_path = get_representation_path_with_anatomy( workfile_representation, anatomy ) - if (not last_published_workfile_path or - not os.path.exists(last_published_workfile_path)): + if not last_published_workfile_path: return # If representation isn't available on remote site, then return. From 51bba73ebe1801ae1c9443307277d6e0156783a6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 19 May 2023 17:13:53 +0200 Subject: [PATCH 123/130] Unreal: Addon Py2 compatibility (#4994) * fix python 2 compatibility of unreal addon * added a comment --- openpype/hosts/unreal/addon.py | 9 +++++---- openpype/hosts/unreal/lib.py | 1 - 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/unreal/addon.py b/openpype/hosts/unreal/addon.py index 9ded333d7d..1119b5c16c 100644 --- a/openpype/hosts/unreal/addon.py +++ b/openpype/hosts/unreal/addon.py @@ -1,8 +1,5 @@ import os -from pathlib import Path - from openpype.modules import IHostAddon, OpenPypeModule -from .lib import get_compatible_integration UNREAL_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -17,10 +14,14 @@ class UnrealAddon(OpenPypeModule, IHostAddon): def add_implementation_envs(self, env, app): """Modify environments to contain all required for implementation.""" # Set AYON_UNREAL_PLUGIN required for Unreal implementation + # Imports are in this method for Python 2 compatiblity of an addon + from pathlib import Path + + from .lib import get_compatible_integration ue_version = app.name.replace("-", ".") unreal_plugin_path = os.path.join( - UNREAL_ROOT_DIR, "integration", f"UE_{ue_version}", "Ayon" + UNREAL_ROOT_DIR, "integration", "UE_{}".format(ue_version), "Ayon" ) if not Path(unreal_plugin_path).exists(): compatible_versions = get_compatible_integration( diff --git a/openpype/hosts/unreal/lib.py b/openpype/hosts/unreal/lib.py index 821b4daecc..97771472cf 100644 --- a/openpype/hosts/unreal/lib.py +++ b/openpype/hosts/unreal/lib.py @@ -7,7 +7,6 @@ import json from typing import List -import openpype from distutils import dir_util import subprocess import re From 8de85e94a7e42cf12b26b6d813dc375bfb0485b5 Mon Sep 17 00:00:00 2001 From: Sponge96 Date: Fri, 19 May 2023 16:15:09 +0100 Subject: [PATCH 124/130] 3dsmax: Refactored publish plugins to use proper implementation of pymxs (#4988) * refactor: rt.execute(saveNodes) replaces with pymxs function - changed the function to no longer use the selection and instead feed it the nodes directly from get_all_children function. - removed maintained_seclection() as we're no longer overriding the selection of the Max scene. - black also used to format. * refactor: replaced rt.export string with proper pymxs implementation - black used for formatting - moved the general flow around as each function call is now seperate instead of large string * refactor: replaced rt.execute with pymxs implementation * refactor: replaced rt.execute with proper pymxs * refactor: replaced rt.execute where possible * fix: pymxs terrible argument handling - noPrompt doesn't seem to work unless you call rt.name and is also positional - using doesn't work as a string you need to feed it the actual rt object * fix: exportFile to use correct arguments * fix: rt.exportfile args * fix: exportfile args * refactor: replaced rt.execute with proper function * refactor: removed use of rt.execute and replaced with pymxs * refactor: removed configs from maintained_selection() block * refactor: updated black to be 79 charlines --- .../max/plugins/publish/extract_camera_abc.py | 50 ++++++---------- .../max/plugins/publish/extract_camera_fbx.py | 57 +++++++----------- .../plugins/publish/extract_max_scene_raw.py | 38 ++++-------- .../max/plugins/publish/extract_model.py | 54 +++++++---------- .../max/plugins/publish/extract_model_fbx.py | 59 ++++++++----------- .../max/plugins/publish/extract_model_obj.py | 37 ++++++------ .../max/plugins/publish/extract_pointcache.py | 38 +++++------- 7 files changed, 131 insertions(+), 202 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_camera_abc.py b/openpype/hosts/max/plugins/publish/extract_camera_abc.py index 8c23ff9878..6b3bb178a3 100644 --- a/openpype/hosts/max/plugins/publish/extract_camera_abc.py +++ b/openpype/hosts/max/plugins/publish/extract_camera_abc.py @@ -1,18 +1,11 @@ import os import pyblish.api -from openpype.pipeline import ( - publish, - OptionalPyblishPluginMixin -) +from openpype.pipeline import publish, OptionalPyblishPluginMixin from pymxs import runtime as rt -from openpype.hosts.max.api import ( - maintained_selection, - get_all_children -) +from openpype.hosts.max.api import maintained_selection, get_all_children -class ExtractCameraAlembic(publish.Extractor, - OptionalPyblishPluginMixin): +class ExtractCameraAlembic(publish.Extractor, OptionalPyblishPluginMixin): """ Extract Camera with AlembicExport """ @@ -38,38 +31,33 @@ class ExtractCameraAlembic(publish.Extractor, path = os.path.join(stagingdir, filename) # We run the render - self.log.info("Writing alembic '%s' to '%s'" % (filename, - stagingdir)) + self.log.info("Writing alembic '%s' to '%s'" % (filename, stagingdir)) - export_cmd = ( - f""" -AlembicExport.ArchiveType = #ogawa -AlembicExport.CoordinateSystem = #maya -AlembicExport.StartFrame = {start} -AlembicExport.EndFrame = {end} -AlembicExport.CustomAttributes = true - -exportFile @"{path}" #noPrompt selectedOnly:on using:AlembicExport - - """) - - self.log.debug(f"Executing command: {export_cmd}") + rt.AlembicExport.ArchiveType = rt.name("ogawa") + rt.AlembicExport.CoordinateSystem = rt.name("maya") + rt.AlembicExport.StartFrame = start + rt.AlembicExport.EndFrame = end + rt.AlembicExport.CustomAttributes = True with maintained_selection(): # select and export rt.select(get_all_children(rt.getNodeByName(container))) - rt.execute(export_cmd) + rt.exportFile( + path, + rt.name("noPrompt"), + selectedOnly=True, + using=rt.AlembicExport, + ) self.log.info("Performing Extraction ...") if "representations" not in instance.data: instance.data["representations"] = [] representation = { - 'name': 'abc', - 'ext': 'abc', - 'files': filename, + "name": "abc", + "ext": "abc", + "files": filename, "stagingDir": stagingdir, } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s" % (instance.name, - path)) + self.log.info("Extracted instance '%s' to: %s" % (instance.name, path)) diff --git a/openpype/hosts/max/plugins/publish/extract_camera_fbx.py b/openpype/hosts/max/plugins/publish/extract_camera_fbx.py index 7e92f355ed..4b4b349e19 100644 --- a/openpype/hosts/max/plugins/publish/extract_camera_fbx.py +++ b/openpype/hosts/max/plugins/publish/extract_camera_fbx.py @@ -1,18 +1,11 @@ import os import pyblish.api -from openpype.pipeline import ( - publish, - OptionalPyblishPluginMixin -) +from openpype.pipeline import publish, OptionalPyblishPluginMixin from pymxs import runtime as rt -from openpype.hosts.max.api import ( - maintained_selection, - get_all_children -) +from openpype.hosts.max.api import maintained_selection, get_all_children -class ExtractCameraFbx(publish.Extractor, - OptionalPyblishPluginMixin): +class ExtractCameraFbx(publish.Extractor, OptionalPyblishPluginMixin): """ Extract Camera with FbxExporter """ @@ -33,43 +26,35 @@ class ExtractCameraFbx(publish.Extractor, filename = "{name}.fbx".format(**instance.data) filepath = os.path.join(stagingdir, filename) - self.log.info("Writing fbx file '%s' to '%s'" % (filename, - filepath)) + self.log.info("Writing fbx file '%s' to '%s'" % (filename, filepath)) - # Need to export: - # Animation = True - # Cameras = True - # AxisConversionMethod - fbx_export_cmd = ( - f""" - -FBXExporterSetParam "Animation" true -FBXExporterSetParam "Cameras" true -FBXExporterSetParam "AxisConversionMethod" "Animation" -FbxExporterSetParam "UpAxis" "Y" -FbxExporterSetParam "Preserveinstances" true - -exportFile @"{filepath}" #noPrompt selectedOnly:true using:FBXEXP - - """) - - self.log.debug(f"Executing command: {fbx_export_cmd}") + rt.FBXExporterSetParam("Animation", True) + rt.FBXExporterSetParam("Cameras", True) + rt.FBXExporterSetParam("AxisConversionMethod", "Animation") + rt.FBXExporterSetParam("UpAxis", "Y") + rt.FBXExporterSetParam("Preserveinstances", True) with maintained_selection(): # select and export rt.select(get_all_children(rt.getNodeByName(container))) - rt.execute(fbx_export_cmd) + rt.exportFile( + filepath, + rt.name("noPrompt"), + selectedOnly=True, + using=rt.FBXEXP, + ) self.log.info("Performing Extraction ...") if "representations" not in instance.data: instance.data["representations"] = [] representation = { - 'name': 'fbx', - 'ext': 'fbx', - 'files': filename, + "name": "fbx", + "ext": "fbx", + "files": filename, "stagingDir": stagingdir, } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s" % (instance.name, - filepath)) + self.log.info( + "Extracted instance '%s' to: %s" % (instance.name, filepath) + ) diff --git a/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py b/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py index c14fcdbd0b..f0c2aff7f3 100644 --- a/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py +++ b/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py @@ -1,18 +1,11 @@ import os import pyblish.api -from openpype.pipeline import ( - publish, - OptionalPyblishPluginMixin -) +from openpype.pipeline import publish, OptionalPyblishPluginMixin from pymxs import runtime as rt -from openpype.hosts.max.api import ( - maintained_selection, - get_all_children -) +from openpype.hosts.max.api import get_all_children -class ExtractMaxSceneRaw(publish.Extractor, - OptionalPyblishPluginMixin): +class ExtractMaxSceneRaw(publish.Extractor, OptionalPyblishPluginMixin): """ Extract Raw Max Scene with SaveSelected """ @@ -20,9 +13,7 @@ class ExtractMaxSceneRaw(publish.Extractor, order = pyblish.api.ExtractorOrder - 0.2 label = "Extract Max Scene (Raw)" hosts = ["max"] - families = ["camera", - "maxScene", - "model"] + families = ["camera", "maxScene", "model"] optional = True def process(self, instance): @@ -37,26 +28,23 @@ class ExtractMaxSceneRaw(publish.Extractor, filename = "{name}.max".format(**instance.data) max_path = os.path.join(stagingdir, filename) - self.log.info("Writing max file '%s' to '%s'" % (filename, - max_path)) + self.log.info("Writing max file '%s' to '%s'" % (filename, max_path)) if "representations" not in instance.data: instance.data["representations"] = [] - # saving max scene - with maintained_selection(): - # need to figure out how to select the camera - rt.select(get_all_children(rt.getNodeByName(container))) - rt.execute(f'saveNodes selection "{max_path}" quiet:true') + nodes = get_all_children(rt.getNodeByName(container)) + rt.saveNodes(nodes, max_path, quiet=True) self.log.info("Performing Extraction ...") representation = { - 'name': 'max', - 'ext': 'max', - 'files': filename, + "name": "max", + "ext": "max", + "files": filename, "stagingDir": stagingdir, } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s" % (instance.name, - max_path)) + self.log.info( + "Extracted instance '%s' to: %s" % (instance.name, max_path) + ) diff --git a/openpype/hosts/max/plugins/publish/extract_model.py b/openpype/hosts/max/plugins/publish/extract_model.py index 710ad5f97d..4c7c98e2cc 100644 --- a/openpype/hosts/max/plugins/publish/extract_model.py +++ b/openpype/hosts/max/plugins/publish/extract_model.py @@ -1,18 +1,11 @@ import os import pyblish.api -from openpype.pipeline import ( - publish, - OptionalPyblishPluginMixin -) +from openpype.pipeline import publish, OptionalPyblishPluginMixin from pymxs import runtime as rt -from openpype.hosts.max.api import ( - maintained_selection, - get_all_children -) +from openpype.hosts.max.api import maintained_selection, get_all_children -class ExtractModel(publish.Extractor, - OptionalPyblishPluginMixin): +class ExtractModel(publish.Extractor, OptionalPyblishPluginMixin): """ Extract Geometry in Alembic Format """ @@ -36,39 +29,36 @@ class ExtractModel(publish.Extractor, filepath = os.path.join(stagingdir, filename) # We run the render - self.log.info("Writing alembic '%s' to '%s'" % (filename, - stagingdir)) + self.log.info("Writing alembic '%s' to '%s'" % (filename, stagingdir)) - export_cmd = ( - f""" -AlembicExport.ArchiveType = #ogawa -AlembicExport.CoordinateSystem = #maya -AlembicExport.CustomAttributes = true -AlembicExport.UVs = true -AlembicExport.VertexColors = true -AlembicExport.PreserveInstances = true - -exportFile @"{filepath}" #noPrompt selectedOnly:on using:AlembicExport - - """) - - self.log.debug(f"Executing command: {export_cmd}") + rt.AlembicExport.ArchiveType = rt.name("ogawa") + rt.AlembicExport.CoordinateSystem = rt.name("maya") + rt.AlembicExport.CustomAttributes = True + rt.AlembicExport.UVs = True + rt.AlembicExport.VertexColors = True + rt.AlembicExport.PreserveInstances = True with maintained_selection(): # select and export rt.select(get_all_children(rt.getNodeByName(container))) - rt.execute(export_cmd) + rt.exportFile( + filepath, + rt.name("noPrompt"), + selectedOnly=True, + using=rt.AlembicExport, + ) self.log.info("Performing Extraction ...") if "representations" not in instance.data: instance.data["representations"] = [] representation = { - 'name': 'abc', - 'ext': 'abc', - 'files': filename, + "name": "abc", + "ext": "abc", + "files": filename, "stagingDir": stagingdir, } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s" % (instance.name, - filepath)) + self.log.info( + "Extracted instance '%s' to: %s" % (instance.name, filepath) + ) diff --git a/openpype/hosts/max/plugins/publish/extract_model_fbx.py b/openpype/hosts/max/plugins/publish/extract_model_fbx.py index ce58e8cc17..e6ccb24cdd 100644 --- a/openpype/hosts/max/plugins/publish/extract_model_fbx.py +++ b/openpype/hosts/max/plugins/publish/extract_model_fbx.py @@ -1,18 +1,11 @@ import os import pyblish.api -from openpype.pipeline import ( - publish, - OptionalPyblishPluginMixin -) +from openpype.pipeline import publish, OptionalPyblishPluginMixin from pymxs import runtime as rt -from openpype.hosts.max.api import ( - maintained_selection, - get_all_children -) +from openpype.hosts.max.api import maintained_selection, get_all_children -class ExtractModelFbx(publish.Extractor, - OptionalPyblishPluginMixin): +class ExtractModelFbx(publish.Extractor, OptionalPyblishPluginMixin): """ Extract Geometry in FBX Format """ @@ -33,42 +26,38 @@ class ExtractModelFbx(publish.Extractor, stagingdir = self.staging_dir(instance) filename = "{name}.fbx".format(**instance.data) - filepath = os.path.join(stagingdir, - filename) - self.log.info("Writing FBX '%s' to '%s'" % (filepath, - stagingdir)) + filepath = os.path.join(stagingdir, filename) + self.log.info("Writing FBX '%s' to '%s'" % (filepath, stagingdir)) - export_fbx_cmd = ( - f""" -FBXExporterSetParam "Animation" false -FBXExporterSetParam "Cameras" false -FBXExporterSetParam "Lights" false -FBXExporterSetParam "PointCache" false -FBXExporterSetParam "AxisConversionMethod" "Animation" -FbxExporterSetParam "UpAxis" "Y" -FbxExporterSetParam "Preserveinstances" true - -exportFile @"{filepath}" #noPrompt selectedOnly:true using:FBXEXP - - """) - - self.log.debug(f"Executing command: {export_fbx_cmd}") + rt.FBXExporterSetParam("Animation", False) + rt.FBXExporterSetParam("Cameras", False) + rt.FBXExporterSetParam("Lights", False) + rt.FBXExporterSetParam("PointCache", False) + rt.FBXExporterSetParam("AxisConversionMethod", "Animation") + rt.FBXExporterSetParam("UpAxis", "Y") + rt.FBXExporterSetParam("Preserveinstances", True) with maintained_selection(): # select and export rt.select(get_all_children(rt.getNodeByName(container))) - rt.execute(export_fbx_cmd) + rt.exportFile( + filepath, + rt.name("noPrompt"), + selectedOnly=True, + using=rt.FBXEXP, + ) self.log.info("Performing Extraction ...") if "representations" not in instance.data: instance.data["representations"] = [] representation = { - 'name': 'fbx', - 'ext': 'fbx', - 'files': filename, + "name": "fbx", + "ext": "fbx", + "files": filename, "stagingDir": stagingdir, } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s" % (instance.name, - filepath)) + self.log.info( + "Extracted instance '%s' to: %s" % (instance.name, filepath) + ) diff --git a/openpype/hosts/max/plugins/publish/extract_model_obj.py b/openpype/hosts/max/plugins/publish/extract_model_obj.py index 7bda237880..ed3d68c990 100644 --- a/openpype/hosts/max/plugins/publish/extract_model_obj.py +++ b/openpype/hosts/max/plugins/publish/extract_model_obj.py @@ -1,18 +1,11 @@ import os import pyblish.api -from openpype.pipeline import ( - publish, - OptionalPyblishPluginMixin -) +from openpype.pipeline import publish, OptionalPyblishPluginMixin from pymxs import runtime as rt -from openpype.hosts.max.api import ( - maintained_selection, - get_all_children -) +from openpype.hosts.max.api import maintained_selection, get_all_children -class ExtractModelObj(publish.Extractor, - OptionalPyblishPluginMixin): +class ExtractModelObj(publish.Extractor, OptionalPyblishPluginMixin): """ Extract Geometry in OBJ Format """ @@ -33,27 +26,31 @@ class ExtractModelObj(publish.Extractor, stagingdir = self.staging_dir(instance) filename = "{name}.obj".format(**instance.data) - filepath = os.path.join(stagingdir, - filename) - self.log.info("Writing OBJ '%s' to '%s'" % (filepath, - stagingdir)) + filepath = os.path.join(stagingdir, filename) + self.log.info("Writing OBJ '%s' to '%s'" % (filepath, stagingdir)) with maintained_selection(): # select and export rt.select(get_all_children(rt.getNodeByName(container))) - rt.execute(f'exportFile @"{filepath}" #noPrompt selectedOnly:true using:ObjExp') # noqa + rt.exportFile( + filepath, + rt.name("noPrompt"), + selectedOnly=True, + using=rt.ObjExp, + ) self.log.info("Performing Extraction ...") if "representations" not in instance.data: instance.data["representations"] = [] representation = { - 'name': 'obj', - 'ext': 'obj', - 'files': filename, + "name": "obj", + "ext": "obj", + "files": filename, "stagingDir": stagingdir, } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s" % (instance.name, - filepath)) + self.log.info( + "Extracted instance '%s' to: %s" % (instance.name, filepath) + ) diff --git a/openpype/hosts/max/plugins/publish/extract_pointcache.py b/openpype/hosts/max/plugins/publish/extract_pointcache.py index 75d8a7972c..8658cecb1b 100644 --- a/openpype/hosts/max/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/max/plugins/publish/extract_pointcache.py @@ -41,10 +41,7 @@ import os import pyblish.api from openpype.pipeline import publish from pymxs import runtime as rt -from openpype.hosts.max.api import ( - maintained_selection, - get_all_children -) +from openpype.hosts.max.api import maintained_selection, get_all_children class ExtractAlembic(publish.Extractor): @@ -66,35 +63,30 @@ class ExtractAlembic(publish.Extractor): path = os.path.join(parent_dir, file_name) # We run the render - self.log.info("Writing alembic '%s' to '%s'" % (file_name, - parent_dir)) + self.log.info("Writing alembic '%s' to '%s'" % (file_name, parent_dir)) - abc_export_cmd = ( - f""" -AlembicExport.ArchiveType = #ogawa -AlembicExport.CoordinateSystem = #maya -AlembicExport.StartFrame = {start} -AlembicExport.EndFrame = {end} - -exportFile @"{path}" #noPrompt selectedOnly:on using:AlembicExport - - """) - - self.log.debug(f"Executing command: {abc_export_cmd}") + rt.AlembicExport.ArchiveType = rt.name("ogawa") + rt.AlembicExport.CoordinateSystem = rt.name("maya") + rt.AlembicExport.StartFrame = start + rt.AlembicExport.EndFrame = end with maintained_selection(): # select and export - rt.select(get_all_children(rt.getNodeByName(container))) - rt.execute(abc_export_cmd) + rt.exportFile( + path, + rt.name("noPrompt"), + selectedOnly=True, + using=rt.AlembicExport, + ) if "representations" not in instance.data: instance.data["representations"] = [] representation = { - 'name': 'abc', - 'ext': 'abc', - 'files': file_name, + "name": "abc", + "ext": "abc", + "files": file_name, "stagingDir": parent_dir, } instance.data["representations"].append(representation) From eea816f0353b4dbcfed745061d83f61eb08cbcb2 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Sat, 20 May 2023 03:25:02 +0000 Subject: [PATCH 125/130] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 954cfa945b..8874eb510d 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.8-nightly.1" +__version__ = "3.15.8-nightly.2" From 0b8400bc8e5785cc23239bc432b10a7cc3de4f4c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 20 May 2023 03:25:46 +0000 Subject: [PATCH 126/130] 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 0f58d61881..244eb1a363 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.8-nightly.2 - 3.15.8-nightly.1 - 3.15.7 - 3.15.7-nightly.3 @@ -134,7 +135,6 @@ body: - 3.14.2-nightly.2 - 3.14.2-nightly.1 - 3.14.1 - - 3.14.1-nightly.4 validations: required: true - type: dropdown From e178244d46401e58fa398501e584e3a6360c0200 Mon Sep 17 00:00:00 2001 From: Ember Light <49758407+EmberLightVFX@users.noreply.github.com> Date: Mon, 22 May 2023 10:43:11 +0200 Subject: [PATCH 127/130] Fusion - Loader plugins updates (#4920) * Added get_bmd_library to acces BMD's internal python library * Added the option to import image and online families. + black formatted * Added workfile loader To import the content of another workfile into your current comp * Fixed wrong family and extension in workfile loader * black formatting * Added missing formats to fbx importer Fusions fbx importer can import a bunch of different formats other then fbx (confusing I know but it's how it is) * Update openpype/hosts/fusion/plugins/load/load_workfile.py Co-authored-by: Roy Nieterau --------- Co-authored-by: Roy Nieterau --- openpype/hosts/fusion/api/__init__.py | 1 + openpype/hosts/fusion/api/lib.py | 6 ++ .../hosts/fusion/plugins/load/load_fbx.py | 34 +++++-- .../fusion/plugins/load/load_sequence.py | 92 +++++++++++-------- .../fusion/plugins/load/load_workfile.py | 32 +++++++ 5 files changed, 118 insertions(+), 47 deletions(-) create mode 100644 openpype/hosts/fusion/plugins/load/load_workfile.py diff --git a/openpype/hosts/fusion/api/__init__.py b/openpype/hosts/fusion/api/__init__.py index 495fe286d5..dba55a98d9 100644 --- a/openpype/hosts/fusion/api/__init__.py +++ b/openpype/hosts/fusion/api/__init__.py @@ -13,6 +13,7 @@ from .lib import ( update_frame_range, set_asset_framerange, get_current_comp, + get_bmd_library, comp_lock_and_undo_chunk ) diff --git a/openpype/hosts/fusion/api/lib.py b/openpype/hosts/fusion/api/lib.py index 40cc4d2963..c33209823e 100644 --- a/openpype/hosts/fusion/api/lib.py +++ b/openpype/hosts/fusion/api/lib.py @@ -309,6 +309,12 @@ def get_fusion_module(): return fusion +def get_bmd_library(): + """Get bmd library""" + bmd = getattr(sys.modules["__main__"], "bmd", None) + return bmd + + def get_current_comp(): """Get current comp in this session""" fusion = get_fusion_module() diff --git a/openpype/hosts/fusion/plugins/load/load_fbx.py b/openpype/hosts/fusion/plugins/load/load_fbx.py index b8f501ae7e..c73ad78394 100644 --- a/openpype/hosts/fusion/plugins/load/load_fbx.py +++ b/openpype/hosts/fusion/plugins/load/load_fbx.py @@ -1,4 +1,3 @@ - from openpype.pipeline import ( load, get_representation_path, @@ -6,7 +5,7 @@ from openpype.pipeline import ( from openpype.hosts.fusion.api import ( imprint_container, get_current_comp, - comp_lock_and_undo_chunk + comp_lock_and_undo_chunk, ) @@ -15,7 +14,21 @@ class FusionLoadFBXMesh(load.LoaderPlugin): families = ["*"] representations = ["*"] - extensions = {"fbx"} + extensions = { + "3ds", + "amc", + "aoa", + "asf", + "bvh", + "c3d", + "dae", + "dxf", + "fbx", + "htr", + "mcd", + "obj", + "trc", + } label = "Load FBX mesh" order = -10 @@ -27,23 +40,24 @@ class FusionLoadFBXMesh(load.LoaderPlugin): def load(self, context, name, namespace, data): # Fallback to asset name when namespace is None if namespace is None: - namespace = context['asset']['name'] + namespace = context["asset"]["name"] # Create the Loader with the filename path set comp = get_current_comp() with comp_lock_and_undo_chunk(comp, "Create tool"): - path = self.fname args = (-32768, -32768) tool = comp.AddTool(self.tool_type, *args) tool["ImportFile"] = path - imprint_container(tool, - name=name, - namespace=namespace, - context=context, - loader=self.__class__.__name__) + imprint_container( + tool, + name=name, + namespace=namespace, + context=context, + loader=self.__class__.__name__, + ) def switch(self, container, representation): self.update(container, representation) diff --git a/openpype/hosts/fusion/plugins/load/load_sequence.py b/openpype/hosts/fusion/plugins/load/load_sequence.py index 38fd41c8b2..552e282587 100644 --- a/openpype/hosts/fusion/plugins/load/load_sequence.py +++ b/openpype/hosts/fusion/plugins/load/load_sequence.py @@ -3,17 +3,14 @@ import contextlib import openpype.pipeline.load as load from openpype.pipeline.load import ( get_representation_context, - get_representation_path_from_context + get_representation_path_from_context, ) from openpype.hosts.fusion.api import ( imprint_container, get_current_comp, - comp_lock_and_undo_chunk -) -from openpype.lib.transcoding import ( - IMAGE_EXTENSIONS, - VIDEO_EXTENSIONS + comp_lock_and_undo_chunk, ) +from openpype.lib.transcoding import IMAGE_EXTENSIONS, VIDEO_EXTENSIONS comp = get_current_comp() @@ -57,20 +54,23 @@ def preserve_trim(loader, log=None): try: yield finally: - length = loader.GetAttrs()["TOOLIT_Clip_Length"][1] - 1 if trim_from_start > length: trim_from_start = length if log: - log.warning("Reducing trim in to %d " - "(because of less frames)" % trim_from_start) + log.warning( + "Reducing trim in to %d " + "(because of less frames)" % trim_from_start + ) remainder = length - trim_from_start if trim_from_end > remainder: trim_from_end = remainder if log: - log.warning("Reducing trim in to %d " - "(because of less frames)" % trim_from_end) + log.warning( + "Reducing trim in to %d " + "(because of less frames)" % trim_from_end + ) loader["ClipTimeStart"][time] = trim_from_start loader["ClipTimeEnd"][time] = length - trim_from_end @@ -109,11 +109,15 @@ def loader_shift(loader, frame, relative=True): # Shifting global in will try to automatically compensate for the change # in the "ClipTimeStart" and "HoldFirstFrame" inputs, so we preserve those # input values to "just shift" the clip - with preserve_inputs(loader, inputs=["ClipTimeStart", - "ClipTimeEnd", - "HoldFirstFrame", - "HoldLastFrame"]): - + with preserve_inputs( + loader, + inputs=[ + "ClipTimeStart", + "ClipTimeEnd", + "HoldFirstFrame", + "HoldLastFrame", + ], + ): # GlobalIn cannot be set past GlobalOut or vice versa # so we must apply them in the order of the shift. if shift > 0: @@ -129,7 +133,14 @@ def loader_shift(loader, frame, relative=True): class FusionLoadSequence(load.LoaderPlugin): """Load image sequence into Fusion""" - families = ["imagesequence", "review", "render", "plate"] + families = [ + "imagesequence", + "review", + "render", + "plate", + "image", + "onilne", + ] representations = ["*"] extensions = set( ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS) @@ -143,7 +154,7 @@ class FusionLoadSequence(load.LoaderPlugin): def load(self, context, name, namespace, data): # Fallback to asset name when namespace is None if namespace is None: - namespace = context['asset']['name'] + namespace = context["asset"]["name"] # Use the first file for now path = get_representation_path_from_context(context) @@ -151,7 +162,6 @@ class FusionLoadSequence(load.LoaderPlugin): # Create the Loader with the filename path set comp = get_current_comp() with comp_lock_and_undo_chunk(comp, "Create Loader"): - args = (-32768, -32768) tool = comp.AddTool("Loader", *args) tool["Clip"] = path @@ -160,11 +170,13 @@ class FusionLoadSequence(load.LoaderPlugin): start = self._get_start(context["version"], tool) loader_shift(tool, start, relative=False) - imprint_container(tool, - name=name, - namespace=namespace, - context=context, - loader=self.__class__.__name__) + imprint_container( + tool, + name=name, + namespace=namespace, + context=context, + loader=self.__class__.__name__, + ) def switch(self, container, representation): self.update(container, representation) @@ -222,24 +234,28 @@ class FusionLoadSequence(load.LoaderPlugin): start = self._get_start(context["version"], tool) with comp_lock_and_undo_chunk(comp, "Update Loader"): - # Update the loader's path whilst preserving some values with preserve_trim(tool, log=self.log): - with preserve_inputs(tool, - inputs=("HoldFirstFrame", - "HoldLastFrame", - "Reverse", - "Depth", - "KeyCode", - "TimeCodeOffset")): + with preserve_inputs( + tool, + inputs=( + "HoldFirstFrame", + "HoldLastFrame", + "Reverse", + "Depth", + "KeyCode", + "TimeCodeOffset", + ), + ): tool["Clip"] = path # Set the global in to the start frame of the sequence global_in_changed = loader_shift(tool, start, relative=False) if global_in_changed: # Log this change to the user - self.log.debug("Changed '%s' global in: %d" % (tool.Name, - start)) + self.log.debug( + "Changed '%s' global in: %d" % (tool.Name, start) + ) # Update the imprinted representation tool.SetData("avalon.representation", str(representation["_id"])) @@ -264,9 +280,11 @@ class FusionLoadSequence(load.LoaderPlugin): # Get frame start without handles start = data.get("frameStart") if start is None: - self.log.warning("Missing start frame for version " - "assuming starts at frame 0 for: " - "{}".format(tool.Name)) + self.log.warning( + "Missing start frame for version " + "assuming starts at frame 0 for: " + "{}".format(tool.Name) + ) return 0 # Use `handleStart` if the data is available diff --git a/openpype/hosts/fusion/plugins/load/load_workfile.py b/openpype/hosts/fusion/plugins/load/load_workfile.py new file mode 100644 index 0000000000..b49d104a15 --- /dev/null +++ b/openpype/hosts/fusion/plugins/load/load_workfile.py @@ -0,0 +1,32 @@ +"""Import workfiles into your current comp. +As all imported nodes are free floating and will probably be changed there +is no update or reload function added for this plugin +""" + +from openpype.pipeline import load + +from openpype.hosts.fusion.api import ( + get_current_comp, + get_bmd_library, +) + + +class FusionLoadWorkfile(load.LoaderPlugin): + """Load the content of a workfile into Fusion""" + + families = ["workfile"] + representations = ["*"] + extensions = {"comp"} + + label = "Load Workfile" + order = -10 + icon = "code-fork" + color = "orange" + + def load(self, context, name, namespace, data): + # Get needed elements + bmd = get_bmd_library() + comp = get_current_comp() + + # Paste the content of the file into the current comp + comp.Paste(bmd.readfile(self.fname)) From 136af34a7189e78fd8549ffe029285283b7997c8 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 22 May 2023 10:45:20 +0200 Subject: [PATCH 128/130] AfterEffects: set frame range and resolution (#4983) * OP-5660 - adding menu buttons to Set frame range in AE * OP-5660 - refactored location of scripts set_settings should be in lib as it is used elsewhere, but launch_logic and lib created circular dependency. Moved main to launch logic as it is actually handling launching. * OP-5660 - added set_settings to creator When instance gets created, set frame range and resolution from DB. * OP-5660 - minor fix * OP-5660 - updated extension zip * OP-5660 - updated documentation * OP-5660 - fixed missing exception * OP-5660 - fixed argument * OP-5560 - fix imports * OP-5660 - updated extension * OP-5660 - add js alert message for buttons * OP-5660 - repacked extension Without Anastasyi showed success, but extension wasn't loaded. * OP-5660 - make log message nicer * OP-5660 - added log if workfile not saved * OP-5660 - provide defaults to limit None exception * OP-5660 - updated error message --- openpype/hosts/aftereffects/api/__init__.py | 10 +- openpype/hosts/aftereffects/api/extension.zxp | Bin 101426 -> 101866 bytes .../api/extension/CSXS/manifest.xml | 4 +- .../aftereffects/api/extension/index.html | 72 +++++++--- .../aftereffects/api/extension/js/main.js | 56 +++++--- .../api/extension/jsx/hostscript.jsx | 134 ++++++++++++------ .../hosts/aftereffects/api/launch_logic.py | 86 ++++++++--- openpype/hosts/aftereffects/api/lib.py | 118 ++++++++------- openpype/hosts/aftereffects/api/pipeline.py | 6 +- openpype/hosts/aftereffects/api/ws_stub.py | 65 ++++++--- .../plugins/create/create_render.py | 19 ++- .../plugins/publish/collect_render.py | 16 +-- openpype/scripts/non_python_host_launch.py | 3 +- website/docs/artist_hosts_aftereffects.md | 37 ++++- .../docs/assets/aftereffects_extension.png | Bin 0 -> 12533 bytes 15 files changed, 414 insertions(+), 212 deletions(-) create mode 100644 website/docs/assets/aftereffects_extension.png diff --git a/openpype/hosts/aftereffects/api/__init__.py b/openpype/hosts/aftereffects/api/__init__.py index a7137ba8fb..28062cc35d 100644 --- a/openpype/hosts/aftereffects/api/__init__.py +++ b/openpype/hosts/aftereffects/api/__init__.py @@ -4,9 +4,8 @@ Anything that isn't defined here is INTERNAL and unreliable for external use. """ -from .launch_logic import ( +from .ws_stub import ( get_stub, - stub, ) from .pipeline import ( @@ -18,7 +17,8 @@ from .pipeline import ( from .lib import ( maintained_selection, get_extension_manifest_path, - get_asset_settings + get_asset_settings, + set_settings ) from .plugin import ( @@ -27,9 +27,8 @@ from .plugin import ( __all__ = [ - # launch_logic + # ws_stub "get_stub", - "stub", # pipeline "ls", @@ -39,6 +38,7 @@ __all__ = [ "maintained_selection", "get_extension_manifest_path", "get_asset_settings", + "set_settings", # plugin "AfterEffectsLoader" diff --git a/openpype/hosts/aftereffects/api/extension.zxp b/openpype/hosts/aftereffects/api/extension.zxp index b436f0ca0b67313cf4509d1ec5e7d4a1dfd2d106..50fda416f806515e8a6e90e745153e341f73c7b2 100644 GIT binary patch delta 14164 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 XY6T7RTV3K`-(5HW;4}pI>(~DQ$dj(< delta 13616 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 - + diff --git a/openpype/hosts/aftereffects/api/extension/index.html b/openpype/hosts/aftereffects/api/extension/index.html index 52a7c4964f..291965559f 100644 --- a/openpype/hosts/aftereffects/api/extension/index.html +++ b/openpype/hosts/aftereffects/api/extension/index.html @@ -2,7 +2,7 @@ - + @@ -25,11 +25,11 @@ - + - + - + - + - + + + + + + + - - + + + - - + @@ -107,6 +143,6 @@ - + - \ No newline at end of file + diff --git a/openpype/hosts/aftereffects/api/extension/js/main.js b/openpype/hosts/aftereffects/api/extension/js/main.js index bb0f3b1f0c..ffc41f0937 100644 --- a/openpype/hosts/aftereffects/api/extension/js/main.js +++ b/openpype/hosts/aftereffects/api/extension/js/main.js @@ -4,7 +4,7 @@ indent: 4, maxerr: 50 */ var csInterface = new CSInterface(); - + log.warn("script start"); WSRPC.DEBUG = false; @@ -14,7 +14,7 @@ WSRPC.TRACE = false; async function startUp(url){ promis = runEvalScript("getEnv('" + url + "')"); - var res = await promis; + var res = await promis; log.warn("res: " + res); promis = runEvalScript("getEnv('OPENPYPE_DEBUG')"); @@ -56,7 +56,7 @@ function get_extension_version(){ } function main(websocket_url){ - // creates connection to 'websocket_url', registers routes + // creates connection to 'websocket_url', registers routes var default_url = 'ws://localhost:8099/ws/'; if (websocket_url == ''){ @@ -66,7 +66,7 @@ function main(websocket_url){ RPC.connect(); - log.warn("connected"); + log.warn("connected"); RPC.addRoute('AfterEffects.open', function (data) { log.warn('Server called client route "open":', data); @@ -88,7 +88,7 @@ function main(websocket_url){ }); RPC.addRoute('AfterEffects.get_active_document_name', function (data) { - log.warn('Server called client route ' + + log.warn('Server called client route ' + '"get_active_document_name":', data); return runEvalScript("getActiveDocumentName()") .then(function(result){ @@ -98,7 +98,7 @@ function main(websocket_url){ }); RPC.addRoute('AfterEffects.get_active_document_full_name', function (data){ - log.warn('Server called client route ' + + log.warn('Server called client route ' + '"get_active_document_full_name":', data); return runEvalScript("getActiveDocumentFullName()") .then(function(result){ @@ -118,7 +118,7 @@ function main(websocket_url){ }); }); - + RPC.addRoute('AfterEffects.get_selected_items', function (data) { log.warn('Server called client route "get_selected_items":', data); return runEvalScript("getSelectedItems(" + data.comps + "," + @@ -194,23 +194,25 @@ function main(websocket_url){ }); }); - RPC.addRoute('AfterEffects.get_work_area', function (data) { - log.warn('Server called client route "get_work_area":', data); - return runEvalScript("getWorkArea(" + data.item_id + ")") + RPC.addRoute('AfterEffects.get_comp_properties', function (data) { + log.warn('Server called client route "get_comp_properties":', data); + return runEvalScript("getCompProperties(" + data.item_id + ")") .then(function(result){ - log.warn("getWorkArea: " + result); + log.warn("get_comp_properties: " + result); return result; }); }); - RPC.addRoute('AfterEffects.set_work_area', function (data) { + RPC.addRoute('AfterEffects.set_comp_properties', function (data) { log.warn('Server called client route "set_work_area":', data); - return runEvalScript("setWorkArea(" + data.item_id + ',' + + return runEvalScript("setCompProperties(" + data.item_id + ',' + data.start + ',' + data.duration + ',' + - data.frame_rate + ")") + data.frame_rate + ',' + + data.width + ',' + + data.height + ")") .then(function(result){ - log.warn("getWorkArea: " + result); + log.warn("set_comp_properties: " + result); return result; }); }); @@ -255,7 +257,7 @@ function main(websocket_url){ RPC.addRoute('AfterEffects.import_background', function (data) { log.warn('Server called client route "import_background":', data); - return runEvalScript("importBackground(" + data.comp_id + ", " + + return runEvalScript("importBackground(" + data.comp_id + ", " + "'" + data.comp_name + "', " + JSON.stringify(data.files) + ")") .then(function(result){ @@ -266,7 +268,7 @@ function main(websocket_url){ RPC.addRoute('AfterEffects.reload_background', function (data) { log.warn('Server called client route "reload_background":', data); - return runEvalScript("reloadBackground(" + data.comp_id + ", " + + return runEvalScript("reloadBackground(" + data.comp_id + ", " + "'" + data.comp_name + "', " + JSON.stringify(data.files) + ")") .then(function(result){ @@ -314,6 +316,16 @@ function main(websocket_url){ log.warn('Server called client route "close":', data); return runEvalScript("close()"); }); + + RPC.addRoute('AfterEffects.print_msg', function (data) { + log.warn('Server called client route "print_msg":', data); + var escaped_msg = EscapeStringForJSX(data.msg); + return runEvalScript("printMsg('" + escaped_msg +"')") + .then(function(result){ + log.warn("print_msg: " + result); + return result; + }); + }); } /** main entry point **/ @@ -323,17 +335,17 @@ startUp("WEBSOCKET_URL"); 'use strict'; var csInterface = new CSInterface(); - - + + function init() { - + themeManager.init(); - + $("#btn_test").click(function () { csInterface.evalScript('sayHello()'); }); } - + init(); }()); diff --git a/openpype/hosts/aftereffects/api/extension/jsx/hostscript.jsx b/openpype/hosts/aftereffects/api/extension/jsx/hostscript.jsx index 5c1d163439..7d0b20bbb4 100644 --- a/openpype/hosts/aftereffects/api/extension/jsx/hostscript.jsx +++ b/openpype/hosts/aftereffects/api/extension/jsx/hostscript.jsx @@ -1,7 +1,7 @@ /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ /*global $, Folder*/ -#include "../js/libs/json.js"; +//@include "../js/libs/json.js" /* All public API function should return JSON! */ @@ -29,13 +29,13 @@ function getEnv(variable){ function getMetadata(){ /** * Returns payload in 'Label' field of project's metadata - * + * **/ if (ExternalObject.AdobeXMPScript === undefined){ ExternalObject.AdobeXMPScript = new ExternalObject('lib:AdobeXMPScript'); } - + var proj = app.project; var meta = new XMPMeta(app.project.xmpPacket); var schemaNS = XMPMeta.getNamespaceURI("xmp"); @@ -53,7 +53,7 @@ function getMetadata(){ function imprint(payload){ /** * Stores payload in 'Label' field of project's metadata - * + * * Args: * payload (string): json content */ @@ -61,14 +61,14 @@ function imprint(payload){ ExternalObject.AdobeXMPScript = new ExternalObject('lib:AdobeXMPScript'); } - + var proj = app.project; var meta = new XMPMeta(app.project.xmpPacket); var schemaNS = XMPMeta.getNamespaceURI("xmp"); var label = "xmp:Label"; meta.setProperty(schemaNS, label, payload); - + app.project.xmpPacket = meta.serialize(); } @@ -116,14 +116,14 @@ function getItems(comps, folders, footages){ /** * Returns JSON representation of compositions and * if 'collectLayers' then layers in comps too. - * + * * Args: * comps (bool): return selected compositions * folders (bool): return folders * footages (bool): return FootageItem * Returns: * (list) of JSON items - */ + */ var items = [] for (i = 1; i <= app.project.items.length; ++i){ var item = app.project.items[i]; @@ -142,14 +142,14 @@ function getItems(comps, folders, footages){ function getSelectedItems(comps, folders, footages){ /** * Returns list of selected items from Project menu - * + * * Args: * comps (bool): return selected compositions * folders (bool): return folders * footages (bool): return FootageItem * Returns: * (list) of JSON items - */ + */ var items = [] for (i = 0; i < app.project.selection.length; ++i){ var item = app.project.selection[i]; @@ -166,9 +166,9 @@ function getSelectedItems(comps, folders, footages){ function _getItem(item, comps, folders, footages){ /** - * Auxiliary function as project items and selections + * Auxiliary function as project items and selections * are indexed in different way :/ - * Refactor + * Refactor */ var item_type = ''; if (item instanceof FolderItem){ @@ -189,7 +189,7 @@ function _getItem(item, comps, folders, footages){ return "{}"; } } - + var item = {"name": item.name, "id": item.id, "type": item_type}; @@ -200,7 +200,7 @@ function importFile(path, item_name, import_options){ /** * Imports file (image tested for now) as a FootageItem. * Creates new composition - * + * * Args: * path (string): absolute path to image file * item_name (string): label for composition @@ -218,7 +218,7 @@ function importFile(path, item_name, import_options){ app.beginUndoGroup("Import File"); fp = new File(path); if (fp.exists){ - try { + try { im_opt = new ImportOptions(fp); importAsType = import_options["ImportAsType"]; @@ -234,18 +234,18 @@ function importFile(path, item_name, import_options){ } if (importAsType.indexOf('PROJECT') > 0){ im_opt.importAs = ImportAsType.PROJECT; - } - + } + } if ('sequence' in import_options){ im_opt.sequence = true; } - + comp = app.project.importFile(im_opt); if (app.project.selection.length == 2 && app.project.selection[0] instanceof FolderItem){ - comp.parentFolder = app.project.selection[0] + comp.parentFolder = app.project.selection[0] } } catch (error) { return _prepareError(error.toString() + importOptions.file.fsName); @@ -283,14 +283,14 @@ function setLabelColor(comp_id, color_idx){ function replaceItem(comp_id, path, item_name){ /** * Replaces loaded file with new file and updates name - * + * * Args: * comp_id (int): id of composition, not a index! * path (string): absolute path to new file * item_name (string): new composition name */ app.beginUndoGroup("Replace File"); - + fp = new File(path); if (!fp.exists){ return _prepareError("File " + path + " not found."); @@ -303,7 +303,7 @@ function replaceItem(comp_id, path, item_name){ }else{ item.replace(fp); } - + item.name = item_name; } catch (error) { return _prepareError(error.toString() + path); @@ -319,7 +319,7 @@ function replaceItem(comp_id, path, item_name){ function renameItem(item_id, new_name){ /** * Renames item with 'item_id' to 'new_name' - * + * * Args: * item_id (int): id to search item * new_name (str) @@ -335,7 +335,7 @@ function renameItem(item_id, new_name){ function deleteItem(item_id){ /** * Delete any 'item_id' - * + * * Not restricted only to comp, it could delete * any item with 'id' */ @@ -347,38 +347,76 @@ function deleteItem(item_id){ } } -function getWorkArea(comp_id){ +function getCompProperties(comp_id){ /** - * Returns information about workarea - are that will be - * rendered. All calculation will be done in OpenPype, - * easier to modify without redeploy of extension. - * + * Returns information about composition - are that will be + * rendered. + * * Returns * (dict) */ - var item = app.project.itemByID(comp_id); - if (item){ - return JSON.stringify({ - "workAreaStart": item.displayStartFrame, - "workAreaDuration": item.duration, - "frameRate": item.frameRate}); - }else{ + var comp = app.project.itemByID(comp_id); + if (!comp){ return _prepareError("There is no composition with "+ comp_id); } + + return JSON.stringify({ + "id": comp.id, + "name": comp.name, + "frameStart": comp.displayStartFrame, + "framesDuration": comp.duration * comp.frameRate, + "frameRate": comp.frameRate, + "width": comp.width, + "height": comp.height}); } -function setWorkArea(comp_id, workAreaStart, workAreaDuration, frameRate){ +function setCompProperties(comp_id, frameStart, framesCount, frameRate, + width, height){ /** * Sets work area info from outside (from Ftrack via OpenPype) */ - var item = app.project.itemByID(comp_id); - if (item){ - item.displayStartTime = workAreaStart; - item.duration = workAreaDuration; - item.frameRate = frameRate; - }else{ + var comp = app.project.itemByID(comp_id); + if (!comp){ return _prepareError("There is no composition with "+ comp_id); } + + app.beginUndoGroup('change comp properties'); + if (frameStart && framesCount && frameRate){ + comp.displayStartFrame = frameStart; + comp.duration = framesCount / frameRate; + comp.frameRate = frameRate; + } + if (width && height){ + var widthOld = comp.width; + var widthNew = width; + var widthDelta = widthNew - widthOld; + + var heightOld = comp.height; + var heightNew = height; + var heightDelta = heightNew - heightOld; + + var offset = [widthDelta / 2, heightDelta / 2]; + + comp.width = widthNew; + comp.height = heightNew; + + for (var i = 1, il = comp.numLayers; i <= il; i++) { + var layer = comp.layer(i); + var positionProperty = layer.property('ADBE Transform Group').property('ADBE Position'); + + if (positionProperty.numKeys > 0) { + for (var j = 1, jl = positionProperty.numKeys; j <= jl; j++) { + var keyValue = positionProperty.keyValue(j); + positionProperty.setValueAtKey(j, keyValue + offset); + } + } else { + var positionValue = positionProperty.value; + positionProperty.setValue(positionValue + offset); + } + } + } + + app.endUndoGroup(); } function save(){ @@ -504,7 +542,7 @@ function addItemAsLayerToComp(comp_id, item_id, found_comp){ * Args: * comp_id (int): id of target composition * item_id (int): FootageItem.id - * found_comp (CompItem, optional): to limit querying if + * found_comp (CompItem, optional): to limit quering if * comp already found previously */ var comp = found_comp || app.project.itemByID(comp_id); @@ -749,7 +787,7 @@ function render(target_folder, comp_id){ var om1 = app.project.renderQueue.item(i).outputModule(1); var file_name = File.decode( om1.file.name ).replace('℗', ''); // Name contains special character, space? - + var omItem1_settable_str = app.project.renderQueue.item(i).outputModule(1).getSettings( GetSettingsFormat.STRING_SETTABLE ); var targetFolder = new Folder(target_folder); @@ -763,7 +801,7 @@ function render(target_folder, comp_id){ render_item.render = false; } } - + } app.beginSuppressDialogs(); app.project.renderQueue.render(); @@ -779,6 +817,10 @@ function getAppVersion(){ return _prepareSingleValue(app.version); } +function printMsg(msg){ + alert(msg); +} + function _prepareSingleValue(value){ return JSON.stringify({"result": value}) } diff --git a/openpype/hosts/aftereffects/api/launch_logic.py b/openpype/hosts/aftereffects/api/launch_logic.py index c428043d99..77c2b0b6ca 100644 --- a/openpype/hosts/aftereffects/api/launch_logic.py +++ b/openpype/hosts/aftereffects/api/launch_logic.py @@ -1,49 +1,77 @@ import os +import sys import subprocess import collections import logging import asyncio import functools +import traceback + from wsrpc_aiohttp import ( WebSocketRoute, WebSocketAsync ) -from qtpy import QtCore +from qtpy import QtCore, QtWidgets from openpype.lib import Logger -from openpype.pipeline import legacy_io from openpype.tools.utils import host_tools +from openpype.tests.lib import is_in_tests +from openpype.pipeline import install_host, legacy_io +from openpype.modules import ModulesManager from openpype.tools.adobe_webserver.app import WebServerTool -from .ws_stub import AfterEffectsServerStub +from .ws_stub import get_stub +from .lib import set_settings log = logging.getLogger(__name__) log.setLevel(logging.DEBUG) -class ConnectionNotEstablishedYet(Exception): - pass +def safe_excepthook(*args): + traceback.print_exception(*args) -def get_stub(): - """ - Convenience function to get server RPC stub to call methods directed - for host (Photoshop). - It expects already created connection, started from client. - Currently created when panel is opened (PS: Window>Extensions>Avalon) - :return: where functions could be called from - """ - ae_stub = AfterEffectsServerStub() - if not ae_stub.client: - raise ConnectionNotEstablishedYet("Connection is not created yet") +def main(*subprocess_args): + """Main entrypoint to AE launching, called from pre hook.""" + sys.excepthook = safe_excepthook - return ae_stub + from openpype.hosts.aftereffects.api import AfterEffectsHost + host = AfterEffectsHost() + install_host(host) -def stub(): - return get_stub() + os.environ["OPENPYPE_LOG_NO_COLORS"] = "False" + app = QtWidgets.QApplication([]) + app.setQuitOnLastWindowClosed(False) + + launcher = ProcessLauncher(subprocess_args) + launcher.start() + + if os.environ.get("HEADLESS_PUBLISH"): + manager = ModulesManager() + webpublisher_addon = manager["webpublisher"] + + launcher.execute_in_main_thread( + functools.partial( + webpublisher_addon.headless_publish, + log, + "CloseAE", + is_in_tests() + ) + ) + + elif os.environ.get("AVALON_PHOTOSHOP_WORKFILES_ON_LAUNCH", True): + save = False + if os.getenv("WORKFILES_SAVE_AS"): + save = True + + launcher.execute_in_main_thread( + lambda: host_tools.show_tool_by_name("workfiles", save=save) + ) + + sys.exit(app.exec_()) def show_tool_by_name(tool_name): @@ -55,6 +83,7 @@ def show_tool_by_name(tool_name): class ProcessLauncher(QtCore.QObject): + """Launches webserver, connects to it, runs main thread.""" route_name = "AfterEffects" _main_thread_callbacks = collections.deque() @@ -296,6 +325,15 @@ class AfterEffectsRoute(WebSocketRoute): async def sceneinventory_route(self): self._tool_route("sceneinventory") + async def setresolution_route(self): + self._settings_route(False, True) + + async def setframes_route(self): + self._settings_route(True, False) + + async def setall_route(self): + self._settings_route(True, True) + async def experimental_tools_route(self): self._tool_route("experimental_tools") @@ -309,3 +347,13 @@ class AfterEffectsRoute(WebSocketRoute): # Required return statement. return "nothing" + + def _settings_route(self, frames, resolution): + partial_method = functools.partial(set_settings, + frames, + resolution) + + ProcessLauncher.execute_in_main_thread(partial_method) + + # Required return statement. + return "nothing" diff --git a/openpype/hosts/aftereffects/api/lib.py b/openpype/hosts/aftereffects/api/lib.py index a39af5c81f..e8352c382b 100644 --- a/openpype/hosts/aftereffects/api/lib.py +++ b/openpype/hosts/aftereffects/api/lib.py @@ -1,69 +1,17 @@ import os -import sys import re import json import contextlib -import traceback import logging -from functools import partial -from qtpy import QtWidgets - -from openpype.pipeline import install_host -from openpype.modules import ModulesManager - -from openpype.tools.utils import host_tools -from openpype.tests.lib import is_in_tests -from .launch_logic import ProcessLauncher, get_stub +from openpype.pipeline.context_tools import get_current_context +from openpype.client import get_asset_by_name +from .ws_stub import get_stub log = logging.getLogger(__name__) log.setLevel(logging.DEBUG) -def safe_excepthook(*args): - traceback.print_exception(*args) - - -def main(*subprocess_args): - sys.excepthook = safe_excepthook - - from openpype.hosts.aftereffects.api import AfterEffectsHost - - host = AfterEffectsHost() - install_host(host) - - os.environ["OPENPYPE_LOG_NO_COLORS"] = "False" - app = QtWidgets.QApplication([]) - app.setQuitOnLastWindowClosed(False) - - launcher = ProcessLauncher(subprocess_args) - launcher.start() - - if os.environ.get("HEADLESS_PUBLISH"): - manager = ModulesManager() - webpublisher_addon = manager["webpublisher"] - - launcher.execute_in_main_thread( - partial( - webpublisher_addon.headless_publish, - log, - "CloseAE", - is_in_tests() - ) - ) - - elif os.environ.get("AVALON_PHOTOSHOP_WORKFILES_ON_LAUNCH", True): - save = False - if os.getenv("WORKFILES_SAVE_AS"): - save = True - - launcher.execute_in_main_thread( - lambda: host_tools.show_tool_by_name("workfiles", save=save) - ) - - sys.exit(app.exec_()) - - @contextlib.contextmanager def maintained_selection(): """Maintain selection during context.""" @@ -145,13 +93,13 @@ def get_asset_settings(asset_doc): """ asset_data = asset_doc["data"] - fps = asset_data.get("fps") - frame_start = asset_data.get("frameStart") - frame_end = asset_data.get("frameEnd") - handle_start = asset_data.get("handleStart") - handle_end = asset_data.get("handleEnd") - resolution_width = asset_data.get("resolutionWidth") - resolution_height = asset_data.get("resolutionHeight") + fps = asset_data.get("fps", 0) + frame_start = asset_data.get("frameStart", 0) + frame_end = asset_data.get("frameEnd", 0) + handle_start = asset_data.get("handleStart", 0) + handle_end = asset_data.get("handleEnd", 0) + resolution_width = asset_data.get("resolutionWidth", 0) + resolution_height = asset_data.get("resolutionHeight", 0) duration = (frame_end - frame_start + 1) + handle_start + handle_end return { @@ -164,3 +112,49 @@ def get_asset_settings(asset_doc): "resolutionHeight": resolution_height, "duration": duration } + + +def set_settings(frames, resolution, comp_ids=None, print_msg=True): + """Sets number of frames and resolution to selected comps. + + Args: + frames (bool): True if set frame info + resolution (bool): True if set resolution + comp_ids (list): specific composition ids, if empty + it tries to look for currently selected + print_msg (bool): True throw JS alert with msg + """ + frame_start = frames_duration = fps = width = height = None + current_context = get_current_context() + + asset_doc = get_asset_by_name(current_context["project_name"], + current_context["asset_name"]) + settings = get_asset_settings(asset_doc) + + msg = '' + if frames: + frame_start = settings["frameStart"] - settings["handleStart"] + frames_duration = settings["duration"] + fps = settings["fps"] + msg += f"frame start:{frame_start}, duration:{frames_duration}, "\ + f"fps:{fps}" + if resolution: + width = settings["resolutionWidth"] + height = settings["resolutionHeight"] + msg += f"width:{width} and height:{height}" + + stub = get_stub() + if not comp_ids: + comps = stub.get_selected_items(True, False, False) + comp_ids = [comp.id for comp in comps] + if not comp_ids: + stub.print_msg("Select at least one composition to apply settings.") + return + + for comp_id in comp_ids: + msg = f"Setting for comp {comp_id} " + msg + log.debug(msg) + stub.set_comp_properties(comp_id, frame_start, frames_duration, + fps, width, height) + if print_msg: + stub.print_msg(msg) diff --git a/openpype/hosts/aftereffects/api/pipeline.py b/openpype/hosts/aftereffects/api/pipeline.py index 020022e263..27aee8c7ce 100644 --- a/openpype/hosts/aftereffects/api/pipeline.py +++ b/openpype/hosts/aftereffects/api/pipeline.py @@ -8,10 +8,7 @@ from openpype.lib import Logger, register_event_callback from openpype.pipeline import ( register_loader_plugin_path, register_creator_plugin_path, - deregister_loader_plugin_path, - deregister_creator_plugin_path, AVALON_CONTAINER_ID, - legacy_io, ) from openpype.pipeline.load import any_outdated_containers import openpype.hosts.aftereffects @@ -23,7 +20,8 @@ from openpype.host import ( IPublishHost ) -from .launch_logic import get_stub, ConnectionNotEstablishedYet +from .launch_logic import get_stub +from .ws_stub import ConnectionNotEstablishedYet log = Logger.get_logger(__name__) diff --git a/openpype/hosts/aftereffects/api/ws_stub.py b/openpype/hosts/aftereffects/api/ws_stub.py index f094c7fa2a..576c997f49 100644 --- a/openpype/hosts/aftereffects/api/ws_stub.py +++ b/openpype/hosts/aftereffects/api/ws_stub.py @@ -11,6 +11,10 @@ from wsrpc_aiohttp import WebSocketAsync from openpype.tools.adobe_webserver.app import WebServerTool +class ConnectionNotEstablishedYet(Exception): + pass + + @attr.s class AEItem(object): """ @@ -24,8 +28,8 @@ class AEItem(object): # all imported elements, single for # regular image, array for Backgrounds members = attr.ib(factory=list) - workAreaStart = attr.ib(default=None) - workAreaDuration = attr.ib(default=None) + frameStart = attr.ib(default=None) + framesDuration = attr.ib(default=None) frameRate = attr.ib(default=None) file_name = attr.ib(default=None) instance_id = attr.ib(default=None) # New Publisher @@ -355,42 +359,50 @@ class AfterEffectsServerStub(): return self._handle_return(res) - def get_work_area(self, item_id): - """ Get work are information for render purposes + def get_comp_properties(self, comp_id): + """ Get composition information for render purposes + + Returns startFrame, frameDuration, fps, width, height. + Args: - item_id (int): + comp_id (int): Returns: (AEItem) """ res = self.websocketserver.call(self.client.call - ('AfterEffects.get_work_area', - item_id=item_id + ('AfterEffects.get_comp_properties', + item_id=comp_id )) records = self._to_records(self._handle_return(res)) if records: return records.pop() - def set_work_area(self, item, start, duration, frame_rate): + def set_comp_properties(self, comp_id, start, duration, frame_rate, + width, height): """ Set work area to predefined values (from Ftrack). Work area directs what gets rendered. Beware of rounding, AE expects seconds, not frames directly. Args: - item (dict): - start (float): workAreaStart in seconds - duration (float): in seconds + comp_id (int): + start (int): workAreaStart in frames + duration (int): in frames frame_rate (float): frames in seconds + width (int): resolution width + height (int): resolution height """ res = self.websocketserver.call(self.client.call - ('AfterEffects.set_work_area', - item_id=item.id, + ('AfterEffects.set_comp_properties', + item_id=comp_id, start=start, duration=duration, - frame_rate=frame_rate)) + frame_rate=frame_rate, + width=width, + height=height)) return self._handle_return(res) def save(self): @@ -554,6 +566,12 @@ class AfterEffectsServerStub(): return self._handle_return(res) + def print_msg(self, msg): + """Triggers Javascript alert dialog.""" + self.websocketserver.call(self.client.call + ('AfterEffects.print_msg', + msg=msg)) + def _handle_return(self, res): """Wraps return, throws ValueError if 'error' key is present.""" if res and isinstance(res, str) and res != "undefined": @@ -608,8 +626,8 @@ class AfterEffectsServerStub(): d.get('name'), d.get('type'), d.get('members'), - d.get('workAreaStart'), - d.get('workAreaDuration'), + d.get('frameStart'), + d.get('framesDuration'), d.get('frameRate'), d.get('file_name'), d.get("instance_id"), @@ -618,3 +636,18 @@ class AfterEffectsServerStub(): ret.append(item) return ret + + +def get_stub(): + """ + Convenience function to get server RPC stub to call methods directed + for host (Photoshop). + It expects already created connection, started from client. + Currently created when panel is opened (PS: Window>Extensions>Avalon) + :return: where functions could be called from + """ + ae_stub = AfterEffectsServerStub() + if not ae_stub.client: + raise ConnectionNotEstablishedYet("Connection is not created yet") + + return ae_stub diff --git a/openpype/hosts/aftereffects/plugins/create/create_render.py b/openpype/hosts/aftereffects/plugins/create/create_render.py index 171d7053ce..fa79fac78f 100644 --- a/openpype/hosts/aftereffects/plugins/create/create_render.py +++ b/openpype/hosts/aftereffects/plugins/create/create_render.py @@ -9,6 +9,7 @@ from openpype.pipeline import ( CreatorError ) from openpype.hosts.aftereffects.api.pipeline import cache_and_get_instances +from openpype.hosts.aftereffects.api.lib import set_settings from openpype.lib import prepare_template_data from openpype.pipeline.create import SUBSET_NAME_ALLOWED_SYMBOLS @@ -32,6 +33,14 @@ class RenderCreator(Creator): def create(self, subset_name_from_ui, data, pre_create_data): stub = api.get_stub() # only after After Effects is up + + try: + _ = stub.get_active_document_full_name() + except ValueError: + raise CreatorError( + "Please save workfile via Workfile app first!" + ) + if pre_create_data.get("use_selection"): comps = stub.get_selected_items( comps=True, folders=False, footages=False @@ -41,8 +50,8 @@ class RenderCreator(Creator): if not comps: raise CreatorError( - "Nothing to create. Select composition " - "if 'useSelection' or create at least " + "Nothing to create. Select composition in Project Bin if " + "'Use selection' is toggled or create at least " "one composition." ) use_composition_name = (pre_create_data.get("use_composition_name") or @@ -87,10 +96,14 @@ class RenderCreator(Creator): self._add_instance_to_context(new_instance) stub.rename_item(comp.id, subset_name) + set_settings(True, True, [comp.id], print_msg=False) def get_pre_create_attr_defs(self): output = [ - BoolDef("use_selection", default=True, label="Use selection"), + BoolDef("use_selection", + tooltip="Composition for publishable instance should be " + "selected by default.", + default=True, label="Use selection"), BoolDef("use_composition_name", label="Use composition name in subset"), UISeparatorDef(), diff --git a/openpype/hosts/aftereffects/plugins/publish/collect_render.py b/openpype/hosts/aftereffects/plugins/publish/collect_render.py index b01b707246..aa46461915 100644 --- a/openpype/hosts/aftereffects/plugins/publish/collect_render.py +++ b/openpype/hosts/aftereffects/plugins/publish/collect_render.py @@ -66,19 +66,19 @@ class CollectAERender(publish.AbstractCollectRender): comp_id = int(inst.data["members"][0]) - work_area_info = CollectAERender.get_stub().get_work_area(comp_id) + comp_info = CollectAERender.get_stub().get_comp_properties( + comp_id) - if not work_area_info: + if not comp_info: self.log.warning("Orphaned instance, deleting metadata") - inst_id = inst.get("instance_id") or str(comp_id) + inst_id = inst.data.get("instance_id") or str(comp_id) CollectAERender.get_stub().remove_instance(inst_id) continue - frame_start = work_area_info.workAreaStart - frame_end = round(work_area_info.workAreaStart + - float(work_area_info.workAreaDuration) * - float(work_area_info.frameRate)) - 1 - fps = work_area_info.frameRate + frame_start = comp_info.frameStart + frame_end = round(comp_info.frameStart + + comp_info.framesDuration) - 1 + fps = comp_info.frameRate # TODO add resolution when supported by extension task_name = inst.data.get("task") # legacy diff --git a/openpype/scripts/non_python_host_launch.py b/openpype/scripts/non_python_host_launch.py index 79fb1cbb52..c95a9df314 100644 --- a/openpype/scripts/non_python_host_launch.py +++ b/openpype/scripts/non_python_host_launch.py @@ -81,9 +81,10 @@ def main(argv): host_name = os.environ["AVALON_APP"].lower() if host_name == "photoshop": + # TODO refactor launch logic according to AE from openpype.hosts.photoshop.api.lib import main elif host_name == "aftereffects": - from openpype.hosts.aftereffects.api.lib import main + from openpype.hosts.aftereffects.api.launch_logic import main elif host_name == "harmony": from openpype.hosts.harmony.api.lib import main else: diff --git a/website/docs/artist_hosts_aftereffects.md b/website/docs/artist_hosts_aftereffects.md index d9522d5765..d415a1d47d 100644 --- a/website/docs/artist_hosts_aftereffects.md +++ b/website/docs/artist_hosts_aftereffects.md @@ -15,18 +15,18 @@ sidebar_label: AfterEffects ## Setup -To install the extension, download, install [Anastasyi's Extension Manager](https://install.anastasiy.com/). Open Anastasyi's Extension Manager and select AfterEffects in menu. Then go to `{path to pype}hosts/aftereffects/api/extension.zxp`. +To install the extension, download, install [Anastasyi's Extension Manager](https://install.anastasiy.com/). Open Anastasyi's Extension Manager and select AfterEffects in menu. Then go to `{path to pype}hosts/aftereffects/api/extension.zxp`. -Drag extension.zxp and drop it to Anastasyi's Extension Manager. The extension will install itself. +Drag extension.zxp and drop it to Anastasyi's Extension Manager. The extension will install itself. ## Implemented functionality AfterEffects implementation currently allows you to import and add various media to composition (image plates, renders, audio files, video files etc.) -and send prepared composition for rendering to Deadline or render locally. +and send prepared composition for rendering to Deadline or render locally. ## Usage -When you launch AfterEffects you will be met with the Workfiles app. If don't +When you launch AfterEffects you will be met with the Workfiles app. If don't have any previous workfiles, you can just close this window. Workfiles tools takes care of saving your .AEP files in the correct location and under @@ -34,7 +34,7 @@ a correct name. You should use it instead of standard file saving dialog. In AfterEffects you'll find the tools in the `OpenPype` extension: -![Extension](assets/photoshop_extension.png) +![Extension](assets/photoshop_extension.png) You can show the extension panel by going to `Window` > `Extensions` > `OpenPype`. @@ -58,6 +58,9 @@ Name of publishable instance (eg. subset name) could be configured with a templa Trash icon under the list of instances allows to delete any selected `render` instance. +Frame information (frame start, duration, fps) and resolution (width and height) is applied to selected composition from Asset Management System (Ftrack or DB) automatically! +(Eg. number of rendered frames is controlled by settings inserted from supervisor. Artist can override this by disabling validation only in special cases.) + Workfile instance will be automatically recreated though. If you do not want to publish it, use pill toggle on the instance item. If you would like to modify publishable instance, click on `Publish` tab at the top. This would allow you to change name of publishable @@ -67,7 +70,7 @@ Publisher allows publishing into different context, just click on any instance, #### RenderQueue -AE's Render Queue is required for publishing locally or on a farm. Artist needs to configure expected result format (extension, resolution) in the Render Queue in an Output module. +AE's Render Queue is required for publishing locally or on a farm. Artist needs to configure expected result format (extension, resolution) in the Render Queue in an Output module. Currently its expected to have only single render item per composition in the Render Queue. @@ -151,3 +154,25 @@ You can switch to a previous version of the image or update to the latest. ![Loader](assets/photoshop_manage_switch.gif) ![Loader](assets/photoshop_manage_update.gif) + + +### Setting section + +Composition properties should be controlled by state in Asset Management System (Ftrack etc). Extension provides couple of buttons to trigger this propagation. + +#### Set Resolution + +Set width and height from AMS to composition. + +#### Set Frame Range + +Start frame and duration in workarea is set according to the settings in AMS. Handles are incorporated (not inclusive). +It is expected that composition(s) is selected first before pushing this button! + +#### Apply All Settings + +Both previous settings are triggered at same time. + +### Experimental tools + +Currently empty. Could contain special tools available only for specific hosts for early access testing. diff --git a/website/docs/assets/aftereffects_extension.png b/website/docs/assets/aftereffects_extension.png new file mode 100644 index 0000000000000000000000000000000000000000..b14992471a1e42cb07c16e93858f180240f3f2e3 GIT binary patch literal 12533 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 Date: Mon, 22 May 2023 13:28:09 +0200 Subject: [PATCH 129/130] Publish: Enhance automated publish plugin settings (#4986) * prepared helper functions for custom settings apply method * publish plugin can have 'settings_category' attribute to define settings category * Better 'settings_category' comment Co-authored-by: Roy Nieterau * fix trailing spaces * added more information about pyblish plugins to dev docs --------- Co-authored-by: Roy Nieterau --- openpype/pipeline/publish/__init__.py | 6 ++ openpype/pipeline/publish/lib.py | 90 ++++++++++++++++++++------- website/docs/dev_publishing.md | 63 ++++++++++++++++++- 3 files changed, 136 insertions(+), 23 deletions(-) diff --git a/openpype/pipeline/publish/__init__.py b/openpype/pipeline/publish/__init__.py index 36252c9f3d..72f3774e1a 100644 --- a/openpype/pipeline/publish/__init__.py +++ b/openpype/pipeline/publish/__init__.py @@ -36,6 +36,9 @@ from .lib import ( context_plugin_should_run, get_instance_staging_dir, get_publish_repre_path, + + apply_plugin_settings_automatically, + get_plugin_settings, ) from .abstract_expected_files import ExpectedFiles @@ -80,6 +83,9 @@ __all__ = ( "get_instance_staging_dir", "get_publish_repre_path", + "apply_plugin_settings_automatically", + "get_plugin_settings", + "ExpectedFiles", "RenderInstance", diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 8b6212b3ef..080f93e514 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -355,29 +355,55 @@ def publish_plugins_discover(paths=None): return result -def _get_plugin_settings(host_name, project_settings, plugin, log): +def get_plugin_settings(plugin, project_settings, log, category=None): """Get plugin settings based on host name and plugin name. + Note: + Default implementation of automated settings is passing host name + into 'category'. + Args: - host_name (str): Name of host. + plugin (pyblish.Plugin): Plugin where settings are applied. project_settings (dict[str, Any]): Project settings. - plugin (pyliblish.Plugin): Plugin where settings are applied. log (logging.Logger): Logger to log messages. + category (Optional[str]): Settings category key where to look + for plugin settings. Returns: dict[str, Any]: Plugin settings {'attribute': 'value'}. """ - # Use project settings from host name category when available - try: - return ( - project_settings - [host_name] - ["publish"] - [plugin.__name__] - ) - except KeyError: - pass + # Plugin can define settings category by class attribute + # - it's impossible to set `settings_category` via settings because + # obviously settings are not applied before it. + # - if `settings_category` is set the fallback category method is ignored + settings_category = getattr(plugin, "settings_category", None) + if settings_category: + try: + return ( + project_settings + [settings_category] + ["publish"] + [plugin.__name__] + ) + except KeyError: + log.warning(( + "Couldn't find plugin '{}' settings" + " under settings category '{}'" + ).format(plugin.__name__, settings_category)) + return {} + + # Use project settings based on a category name + if category: + try: + return ( + project_settings + [category] + ["publish"] + [plugin.__name__] + ) + except KeyError: + pass # Settings category determined from path # - usually path is './/plugins/publish/' @@ -386,9 +412,10 @@ def _get_plugin_settings(host_name, project_settings, plugin, log): split_path = filepath.rsplit(os.path.sep, 5) if len(split_path) < 4: - log.warning( - 'plugin path too short to extract host {}'.format(filepath) - ) + log.debug(( + "Plugin path is too short to automatically" + " extract settings category. {}" + ).format(filepath)) return {} category_from_file = split_path[-4] @@ -410,6 +437,28 @@ def _get_plugin_settings(host_name, project_settings, plugin, log): return {} +def apply_plugin_settings_automatically(plugin, settings, logger=None): + """Automatically apply plugin settings to a plugin object. + + Note: + This function was created to be able to use it in custom overrides of + 'apply_settings' class method. + + Args: + plugin (type[pyblish.api.Plugin]): Class of a plugin. + settings (dict[str, Any]): Plugin specific settings. + logger (Optional[logging.Logger]): Logger to log debug messages about + applied settings values. + """ + + for option, value in settings.items(): + if logger: + logger.debug("Plugin {} - Attr: {} -> {}".format( + option, value, plugin.__name__ + )) + setattr(plugin, option, value) + + def filter_pyblish_plugins(plugins): """Pyblish plugin filter which applies OpenPype settings. @@ -453,13 +502,10 @@ def filter_pyblish_plugins(plugins): ) else: # Automated - plugin_settins = _get_plugin_settings( - host_name, project_settings, plugin, log + plugin_settins = get_plugin_settings( + plugin, project_settings, log, host_name ) - for option, value in plugin_settins.items(): - log.info("setting {}:{} on plugin {}".format( - option, value, plugin.__name__)) - setattr(plugin, option, value) + apply_plugin_settings_automatically(plugin, plugin_settins, log) # Remove disabled plugins if getattr(plugin, "enabled", True) is False: diff --git a/website/docs/dev_publishing.md b/website/docs/dev_publishing.md index 2c57537223..3ef6272373 100644 --- a/website/docs/dev_publishing.md +++ b/website/docs/dev_publishing.md @@ -506,6 +506,67 @@ or the scene file was copy pasted from different context. #### *Known errors* When there is a known error that can't be fixed by the user (e.g. can't connect to deadline service, etc.) `KnownPublishError` should be raised. The only difference is that its message is shown in UI to the artist otherwise a neutral message without context is shown. +### Plugins +Plugin is a single processing unit that can work with publish context and instances. + +#### Plugin types +There are 2 types of plugins - `InstancePlugin` and `ContextPlugin`. Be aware that inheritance of plugin from `InstancePlugin` or `ContextPlugin` actually does not affect if plugin is instance or context plugin, that is affected by argument name in `process` method. + +```python +import pyblish.api + + +# Context plugin +class MyContextPlugin(pyblish.api.ContextPlugin): + def process(self, context): + ... + +# Instance plugin +class MyInstancePlugin(pyblish.api.InstancePlugin): + def process(self, instance): + ... + +# Still an instance plugin +class MyOtherInstancePlugin(pyblish.api.ContextPlugin): + def process(self, instance): + ... +``` + +#### Plugin filtering +By pyblish logic, plugins have predefined filtering class attributes `hosts`, `targets` and `families`. Filter by `hosts` and `targets` are filters that are applied for current publishing process. Both filters are registered in `pyblish` module, `hosts` filtering may not match OpenPype host name (e.g. farm publishing uses `shell` in pyblish). Filter `families` works only on instance plugins and is dynamic during publish process by changing families of an instance. + +All filters are list of a strings `families = ["image"]`. Empty list is invalid filter and plugin will be skipped, to allow plugin for all values use a start `families = ["*"]`. For more detailed filtering options check [pyblish documentation](https://api.pyblish.com/pluginsystem). + +Each plugin must have order, there are 4 order milestones - Collect, Validate, Extract, Integration. Any plugin below collection order won't be processed. for more details check [pyblish documentation](https://api.pyblish.com/ordering). + +#### Plugin settings +Pyblish plugins may have settings. There are 2 ways how settings are applied, first is automated, and it's logic is based on function `filter_pyblish_plugins` in `./openpype/pipeline/publish/lib.py`, second is explicit by implementing class method `apply_settings` on a plugin. + + +Automated logic is expecting specific structure of project settings `project_settings[{category}]["plugins"]["publish"][{plugin class name}]`. The category is a key in root of project settings. There are currently 3 ways how the category key is received. +1. Use `settings_category` class attribute value from plugin. If `settings_category` is not `None` there is not any fallback to other way. +2. Use currently registered pyblish host. This will be probably deprecated soon. +3. Use 3rd folder name from a plugin filepath. From path `./maya/plugins/publish/collect_render.py` is used `maya` as the key. + +For any other use-case is recommended to use explicit approach by implementing `apply_settings` method. Must use `@classmethod` decorator and expect arguments for project settings and system settings. We're planning to support single argument with only project settings. +```python +import pyblish.api + + +class MyPlugin(pyblish.api.InstancePlugin): + profiles = [] + + @classmethod + def apply_settings(cls, project_settings, system_settings): + cls.profiles = ( + project_settings + ["addon"] + ["plugins"] + ["publish"] + ["vfx_profiles"] + ) +``` + ### Plugin extension Publish plugins can be extended by additional logic when inheriting from `OpenPypePyblishPluginMixin` which can be used as mixin (additional inheritance of class). Publish plugins that inherit from this mixin can define attributes that will be shown in **CreatedInstance**. One of the most important usages is to be able turn on/off optional plugins. @@ -596,4 +657,4 @@ Publish attributes work the same way as create attributes but the source of attr ### Create dialog ![Publisher UI - Create dialog](assets/publisher_create_dialog.png) -Create dialog is used by artist to create new instances in a context. The context selection can be enabled/disabled by changing `create_allow_context_change` on [creator plugin](#creator). In the middle part the artist selects what will be created and what variant it is. On the right side is information about the selected creator and its pre-create attributes. There is also a question mark button which extends the window and displays more detailed information about the creator. \ No newline at end of file +Create dialog is used by artist to create new instances in a context. The context selection can be enabled/disabled by changing `create_allow_context_change` on [creator plugin](#creator). In the middle part the artist selects what will be created and what variant it is. On the right side is information about the selected creator and its pre-create attributes. There is also a question mark button which extends the window and displays more detailed information about the creator. From 41fbe3031f67b9ed9141939c1e70a955b8240d4d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 22 May 2023 14:59:58 +0200 Subject: [PATCH 130/130] fusion: asset_db is collecting by default. --- .../fusion/plugins/publish/collect_instances.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/fusion/plugins/publish/collect_instances.py b/openpype/hosts/fusion/plugins/publish/collect_instances.py index 458f00c7ed..6016baa2a9 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_instances.py +++ b/openpype/hosts/fusion/plugins/publish/collect_instances.py @@ -25,15 +25,16 @@ class CollectInstanceData(pyblish.api.InstancePlugin): frame_range_source = creator_attributes.get("frame_range_source") instance.data["frame_range_source"] = frame_range_source - if frame_range_source == "asset_db": - # get asset frame ranges - start = context.data["frameStart"] - end = context.data["frameEnd"] - handle_start = context.data["handleStart"] - handle_end = context.data["handleEnd"] - start_with_handle = start - handle_start - end_with_handle = end + handle_end + # get asset frame ranges to all instances + # render family instances `asset_db` render target + start = context.data["frameStart"] + end = context.data["frameEnd"] + handle_start = context.data["handleStart"] + handle_end = context.data["handleEnd"] + start_with_handle = start - handle_start + end_with_handle = end + handle_end + # conditions for render family instances if frame_range_source == "render_range": # set comp render frame ranges start = context.data["renderFrameStart"]