From 4bd1f05036e1cb22326f6621ed3df8c8dc810ad4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 28 Mar 2024 23:10:08 +0100 Subject: [PATCH 01/46] Remove legacy `suspend_publish` attribute definition in favor of `publishJobState` --- .../plugins/publish/submit_fusion_deadline.py | 14 +------------- .../publish/submit_houdini_render_deadline.py | 6 ------ .../plugins/publish/submit_nuke_deadline.py | 9 --------- .../plugins/publish/submit_publish_cache_job.py | 3 --- .../deadline/plugins/publish/submit_publish_job.py | 3 --- 5 files changed, 1 insertion(+), 34 deletions(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py index cf124c0bcc..3232edfeb3 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py @@ -9,10 +9,7 @@ import pyblish.api from ayon_core.pipeline.publish import ( AYONPyblishPluginMixin ) -from ayon_core.lib import ( - BoolDef, - NumberDef, -) +from ayon_core.lib import NumberDef class FusionSubmitDeadline( @@ -64,11 +61,6 @@ class FusionSubmitDeadline( decimals=0, minimum=1, maximum=10 - ), - BoolDef( - "suspend_publish", - default=False, - label="Suspend publish" ) ] @@ -80,10 +72,6 @@ class FusionSubmitDeadline( attribute_values = self.get_attr_values_from_data( instance.data) - # add suspend_publish attributeValue to instance data - instance.data["suspend_publish"] = attribute_values[ - "suspend_publish"] - context = instance.context key = "__hasRun{}".format(self.__class__.__name__) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py index 6952604293..597a3cfc55 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -10,7 +10,6 @@ from openpype_modules.deadline import abstract_submit_deadline from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo from ayon_core.lib import ( is_in_tests, - BoolDef, TextDef, NumberDef ) @@ -90,11 +89,6 @@ class HoudiniSubmitDeadline( @classmethod def get_attribute_defs(cls): return [ - BoolDef( - "suspend_publish", - default=False, - label="Suspend publish" - ), NumberDef( "priority", label="Priority", diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py index ac01af901c..3138cd02e3 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -76,11 +76,6 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, default=cls.use_gpu, label="Use GPU" ), - BoolDef( - "suspend_publish", - default=False, - label="Suspend publish" - ), BoolDef( "workfile_dependency", default=cls.workfile_dependency, @@ -100,10 +95,6 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, instance.data["attributeValues"] = self.get_attr_values_from_data( instance.data) - # add suspend_publish attributeValue to instance data - instance.data["suspend_publish"] = instance.data["attributeValues"][ - "suspend_publish"] - families = instance.data["families"] node = instance.data["transientData"]["node"] diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py index 50bd414587..800a906630 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py @@ -144,9 +144,6 @@ class ProcessSubmittedCacheJobOnFarm(pyblish.api.InstancePlugin, instance_settings = self.get_attr_values_from_data(instance.data) initial_status = instance_settings.get("publishJobState", "Active") - # TODO: Remove this backwards compatibility of `suspend_publish` - if instance.data.get("suspend_publish"): - initial_status = "Suspended" args = [ "--headless", diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py index 84bac6d017..7546ce08d6 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py @@ -221,9 +221,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, instance_settings = self.get_attr_values_from_data(instance.data) initial_status = instance_settings.get("publishJobState", "Active") - # TODO: Remove this backwards compatibility of `suspend_publish` - if instance.data.get("suspend_publish"): - initial_status = "Suspended" args = [ "--headless", From 8499660acbf9dc0317db2cd6010d65022942a47a Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 15 Apr 2024 12:14:24 +0100 Subject: [PATCH 02/46] Only close previous project if its different to current project. --- client/ayon_core/hosts/hiero/api/workio.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/hosts/hiero/api/workio.py b/client/ayon_core/hosts/hiero/api/workio.py index 4c2416ca38..9d8b1777e7 100644 --- a/client/ayon_core/hosts/hiero/api/workio.py +++ b/client/ayon_core/hosts/hiero/api/workio.py @@ -54,10 +54,9 @@ def open_file(filepath): # open project file hiero.core.openProject(filepath.replace(os.path.sep, "/")) - # close previous project - project.close() - - + # Close previous project if its different to the current project. + if project.path() != filepath: + project.close() return True From 77e214d8e6c2ece97ef3243c8e374a4c854f12f9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 17 Apr 2024 10:31:41 +0200 Subject: [PATCH 03/46] add launch command to applications addon --- .../client/ayon_applications/addon.py | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/server_addon/applications/client/ayon_applications/addon.py b/server_addon/applications/client/ayon_applications/addon.py index 0f1b68af0e..624f158baa 100644 --- a/server_addon/applications/client/ayon_applications/addon.py +++ b/server_addon/applications/client/ayon_applications/addon.py @@ -110,6 +110,26 @@ class ApplicationsAddon(AYONAddon, IPluginPaths): ] } + def launch_application( + self, app_name, project_name, folder_path, task_name + ): + """Launch application. + + Args: + app_name (str): Full application name e.g. 'maya/2024'. + project_name (str): Project name. + folder_path (str): Folder path. + task_name (str): Task name. + + """ + app_manager = self.get_applications_manager() + return app_manager.launch( + app_name, + project_name=project_name, + folder_path=folder_path, + task_name=task_name, + ) + # --- CLI --- def cli(self, addon_click_group): main_group = click_wrap.group( @@ -134,6 +154,17 @@ class ApplicationsAddon(AYONAddon, IPluginPaths): default=None ) ) + ( + main_group.command( + self._cli_launch_applications, + name="launch", + help="Launch application" + ) + .option("--app", help="Application name") + .option("--project", help="Project name") + .option("--folder", help="Folder path") + .option("--task", help="Task name") + ) # Convert main command to click object and add it to parent group addon_click_group.add_command( main_group.to_click_obj() @@ -171,3 +202,17 @@ class ApplicationsAddon(AYONAddon, IPluginPaths): with open(output_json_path, "w") as file_stream: json.dump(env, file_stream, indent=4) + + def _cli_launch_applications(self, project, folder, task, app): + """Produces json file with environment based on project and app. + + Called by farm integration to propagate environment into farm jobs. + + Args: + project (str): Project name. + folder (str): Folder path. + task (str): Task name. + app (str): Full application name e.g. 'maya/2024'. + + """ + self.launch_application(app, project, folder, task,) From 7f4292c797a66204cad817ec765ef232a755f035 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 17 Apr 2024 10:32:14 +0200 Subject: [PATCH 04/46] bump version '0.2.1' --- server_addon/applications/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/applications/package.py b/server_addon/applications/package.py index ce312ed662..bcc91f1d84 100644 --- a/server_addon/applications/package.py +++ b/server_addon/applications/package.py @@ -1,3 +1,3 @@ name = "applications" title = "Applications" -version = "0.2.0" +version = "0.2.1" From 787e3ad90d557dd8853151d7093c7ce4a72d5964 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 17 Apr 2024 11:27:13 +0200 Subject: [PATCH 05/46] remove trailing comma Co-authored-by: Roy Nieterau --- server_addon/applications/client/ayon_applications/addon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/applications/client/ayon_applications/addon.py b/server_addon/applications/client/ayon_applications/addon.py index 624f158baa..a573ee666a 100644 --- a/server_addon/applications/client/ayon_applications/addon.py +++ b/server_addon/applications/client/ayon_applications/addon.py @@ -215,4 +215,4 @@ class ApplicationsAddon(AYONAddon, IPluginPaths): app (str): Full application name e.g. 'maya/2024'. """ - self.launch_application(app, project, folder, task,) + self.launch_application(app, project, folder, task) From 24c3b2c5c8d9a2f725049b7c7bdb699dadd2d37a Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 17 Apr 2024 11:27:35 +0200 Subject: [PATCH 06/46] fix docstring Co-authored-by: Roy Nieterau --- server_addon/applications/client/ayon_applications/addon.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/server_addon/applications/client/ayon_applications/addon.py b/server_addon/applications/client/ayon_applications/addon.py index a573ee666a..f3ebedd364 100644 --- a/server_addon/applications/client/ayon_applications/addon.py +++ b/server_addon/applications/client/ayon_applications/addon.py @@ -204,9 +204,7 @@ class ApplicationsAddon(AYONAddon, IPluginPaths): json.dump(env, file_stream, indent=4) def _cli_launch_applications(self, project, folder, task, app): - """Produces json file with environment based on project and app. - - Called by farm integration to propagate environment into farm jobs. + """Launch application. Args: project (str): Project name. From d8081868d740dae87829de5e61988f41884186db Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 17 Apr 2024 11:31:05 +0200 Subject: [PATCH 07/46] require arguments for application launch --- .../applications/client/ayon_applications/addon.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server_addon/applications/client/ayon_applications/addon.py b/server_addon/applications/client/ayon_applications/addon.py index f3ebedd364..a8eaa46cad 100644 --- a/server_addon/applications/client/ayon_applications/addon.py +++ b/server_addon/applications/client/ayon_applications/addon.py @@ -160,10 +160,10 @@ class ApplicationsAddon(AYONAddon, IPluginPaths): name="launch", help="Launch application" ) - .option("--app", help="Application name") - .option("--project", help="Project name") - .option("--folder", help="Folder path") - .option("--task", help="Task name") + .option("--app", required=True, help="Application name") + .option("--project", required=True, help="Project name") + .option("--folder", required=True, help="Folder path") + .option("--task", required=True, help="Task name") ) # Convert main command to click object and add it to parent group addon_click_group.add_command( From 03cfed2c7957538a9c72afe4e31c010cb1d606ab Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 17 Apr 2024 20:33:39 +0200 Subject: [PATCH 08/46] Add more family so attribute definitions show in Nuke and Fusion for `render`, `image` and `prerender`. This should be safe because `instance.data.get("farm")` is checked in `process()` and if not true the processing is skipped anyway - so if e.g. a render instance in Fusion is set to render local instead of on the farm the actual attribute definition does show - but the processing of the plug-in is skipped regardless. --- .../modules/deadline/plugins/publish/submit_publish_job.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py index 41445fabc3..99a5f94cf1 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py @@ -88,9 +88,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, hosts = ["fusion", "max", "maya", "nuke", "houdini", "celaction", "aftereffects", "harmony", "blender"] - families = ["render.farm", "render.frames_farm", - "prerender.farm", "prerender.frames_farm", - "renderlayer", "imagesequence", + families = ["render", "render.farm", "render.frames_farm", + "prerender", "prerender.farm", "prerender.frames_farm", + "renderlayer", "imagesequence", "image", "vrayscene", "maxrender", "arnold_rop", "mantra_rop", "karma_rop", "vray_rop", @@ -311,7 +311,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, return deadline_publish_job_id - def process(self, instance): # type: (pyblish.api.Instance) -> None """Process plugin. From bd42a506cfd8b9c143f18c198e5920459e836124 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 18 Apr 2024 17:14:06 +0800 Subject: [PATCH 09/46] make sure the bake animation is boolean option --- client/ayon_core/hosts/maya/api/fbx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/api/fbx.py b/client/ayon_core/hosts/maya/api/fbx.py index 939da4011b..3f1395cb40 100644 --- a/client/ayon_core/hosts/maya/api/fbx.py +++ b/client/ayon_core/hosts/maya/api/fbx.py @@ -47,7 +47,7 @@ class FBXExtractor: "smoothMesh": bool, "instances": bool, # "referencedContainersContent": bool, # deprecated in Maya 2016+ - "bakeComplexAnimation": int, + "bakeComplexAnimation": bool, "bakeComplexStart": int, "bakeComplexEnd": int, "bakeComplexStep": int, From 8ff929e7199817eb006e3b5fb4e788268f87ddb1 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 18 Apr 2024 17:19:32 +0800 Subject: [PATCH 10/46] Revert "make sure the bake animation is boolean option" This reverts commit bd42a506cfd8b9c143f18c198e5920459e836124. --- client/ayon_core/hosts/maya/api/fbx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/api/fbx.py b/client/ayon_core/hosts/maya/api/fbx.py index 3f1395cb40..939da4011b 100644 --- a/client/ayon_core/hosts/maya/api/fbx.py +++ b/client/ayon_core/hosts/maya/api/fbx.py @@ -47,7 +47,7 @@ class FBXExtractor: "smoothMesh": bool, "instances": bool, # "referencedContainersContent": bool, # deprecated in Maya 2016+ - "bakeComplexAnimation": bool, + "bakeComplexAnimation": int, "bakeComplexStart": int, "bakeComplexEnd": int, "bakeComplexStep": int, From c260996522872a8bf56f74b52a598f61f9ba3b0e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 19 Apr 2024 20:07:02 +0200 Subject: [PATCH 11/46] Implement `Reorder` helper for Maya --- client/ayon_core/hosts/maya/api/lib.py | 252 +++++++++++++++++++++++++ 1 file changed, 252 insertions(+) diff --git a/client/ayon_core/hosts/maya/api/lib.py b/client/ayon_core/hosts/maya/api/lib.py index 321bcbc0b5..e30070c796 100644 --- a/client/ayon_core/hosts/maya/api/lib.py +++ b/client/ayon_core/hosts/maya/api/lib.py @@ -6,6 +6,7 @@ from pprint import pformat import sys import uuid import re +import operator import json import logging @@ -4403,3 +4404,254 @@ def create_rig_animation_instance( variant=namespace, pre_create_data={"use_selection": True} ) + + +class Reorder(object): + """Helper functions for reordering in Maya outliner""" + + @staticmethod + def group_by_parent(nodes): + """Groups the given input list of nodes by parent. + + This is a convenience function for the Reorder functionality. + This function assumes the nodes are in the `long/fullPath` format. + """ + nodes = cmds.ls(nodes, long=True) + nodes_by_parent = defaultdict(list) + for node in nodes: + parent = node.rsplit("|", 1)[0] + nodes_by_parent[parent].append(node) + return nodes_by_parent + + @staticmethod + def get_children_with_index(parent): + """Get children under parent with their indices""" + def node_to_index(nodes): + return {node: index for index, node in enumerate(nodes)} + + if not parent: + return node_to_index(cmds.ls(assemblies=True, long=True)) + else: + return node_to_index( + cmds.listRelatives(parent, + children=True, + fullPath=True) or [] + ) + + @staticmethod + def get_index(node): + node = cmds.ls(node, long=True)[0] # enforce long names + parent = node.rsplit("|", 1)[0] + if not parent: + return cmds.ls(assemblies=True, long=True).index(node) + else: + return cmds.listRelatives(parent, + children=True, + fullPath=True).index(node) + + @staticmethod + def get_indices(nodes): + """Returns a dictionary with node, index pairs. + + This is preferred over get_index method for larger number of nodes, + because it is more optimal in performance. + + eg: + { + '|side': 3, + '|top': 1, + '|pSphere1': 4, + '|persp': 0, + '|front': 2 + } + + Returns: + dict: index by node + """ + nodes = cmds.ls(nodes, long=True) # enforce long names + node_indices = dict() + cached_children = dict() + for node in nodes: + parent = node.rsplit("|", 1)[0] + if parent not in cached_children: + cached_children[parent] = Reorder.get_children_with_index(parent) # noqa: E501 + + node_indices[node] = cached_children[parent][node] + return node_indices + + @staticmethod + def set_index(node, index): + if not node: + return + cmds.reorder(node, front=True) + cmds.reorder(node, r=index) + + @staticmethod + def set_indices(node_indices): + """Set node order by node to index dict. + + Args: + node_indices (dict): Node name to index dictionary + + """ + if not isinstance(node_indices, dict): + raise TypeError( + "Reorder.set_indices() requires a dictionary with " + "(node, index) pairs as input. " + "`{0}` is an invalid input type.".format( + type(node_indices).__name__) + ) + + if not node_indices: + return + + # force nodes to the back to not influence each other during reorder + cmds.reorder(node_indices.keys(), back=True) + + for node, index in sorted(node_indices.items(), + key=operator.itemgetter(1)): + Reorder.set_index(node, index) + + @staticmethod + def sort(nodes, key=lambda x: x.rsplit("|", 1)[-1], reverse=False): + """Sorts the node in scene by the key function. + + Default sorting key is alphabetically by using the object's short name. + """ + nodes = cmds.ls(nodes, long=True) # ensure long paths + if not nodes: + return + + # Group by parent to sort nodes per parent + parents = Reorder.group_by_parent(nodes) + + for child_nodes in parents.values(): + + node_indices = Reorder.get_indices(child_nodes) + indices = sorted(node_indices.values()) + + new_indices = { + node: indices[i] for i, node in + enumerate(sorted(child_nodes, key=key, reverse=reverse)) + } + Reorder.set_indices(new_indices) + + @staticmethod + def reverse(nodes): + nodes = cmds.ls(nodes, long=True) # ensure long paths + if not nodes: + return + + # Group by parent to sort nodes per parent + parents = Reorder.group_by_parent(nodes) + + for child_nodes in parents.values(): + + node_indices = Reorder.get_indices(child_nodes) + indices = sorted(node_indices.values(), reverse=False) + + iterable = enumerate(sorted(node_indices.items(), + key=operator.itemgetter(1), + reverse=True)) + new_indices = { + node: indices[i] for i, (node, _old_index) in iterable + } + Reorder.set_indices(new_indices) + + @staticmethod + def align_bottom(nodes): + """Reorder to the lowest (most back) of node in `nodes`.""" + nodes = cmds.ls(nodes, long=True) # ensure long paths + if not nodes: + return + + # Group by parent to sort nodes per parent + parents = Reorder.group_by_parent(nodes) + for child_nodes in parents.values(): + + # Reorder.set_index forces to front and then moves all objects + # together (so they will be stacked together). Then it applies the + # index as relative offset, we can use that here to our advantage. + # And it is a lot faster than Reorder.set_indices in that scenario. + index_per_node = Reorder.get_indices(child_nodes) + back_index = max(index_per_node.values()) + new_front_index = back_index - len(child_nodes) + 1 + Reorder.set_index(child_nodes, new_front_index) + + @staticmethod + def align_top(nodes): + """Reorder to the highest (most front) of node in `nodes`.""" + nodes = cmds.ls(nodes, long=True) # ensure long paths + if not nodes: + return + + # Group by parent to sort nodes per parent + parents = Reorder.group_by_parent(nodes) + for childNodes in parents.values(): + + # Reorder.set_index forces to front and then moves all objects + # together (so they will be stacked together). Then it applies the + # index as relative offset, we can use that here to our advantage. + # And it is a lot faster than Reorder.set_indices in that scenario. + index_per_node = Reorder.get_indices(childNodes) + front_index = min(index_per_node.values()) + Reorder.set_index(childNodes, front_index) + + @staticmethod + def move(nodes, relative, wrap=True): + """ Reorder by the given relative amount. """ + # TODO: Implement the disabling of wrapping around when at bottom. + if not nodes: + return + cmds.reorder(nodes, r=relative) + + @staticmethod + def to_bottom(nodes): + """Reorder to all the way to the bottom.""" + if not nodes: + return + cmds.reorder(nodes, back=True) + + @staticmethod + def to_top(nodes): + """Reorder to all the way to the top.""" + if not nodes: + return + cmds.reorder(nodes, front=True) + + @staticmethod + def order_to(nodes): + """Reorder the nodes to the order of the input list. + + Tip: + If you pass this your current selection list it will reorder + the nodes to the order of your selection. + + """ + nodes = cmds.ls(nodes, long=True) # ensure long paths + if not nodes: + return + + # Make a dictionary of the input order so we can optimize the look-up + # of the index in the order of the input `nodes`. + selected_order = {node: i for i, node in enumerate(nodes)} + + # Group by parent since we want to sort nodes under its current parent + parents = Reorder.group_by_parent(nodes) + for child_nodes in parents.values(): + + # Get the current indices + node_indices = Reorder.get_indices(child_nodes) + + # We get the original indices so we can position to those same + # positions, albeit with the new ordering of the nodes. + orig_indices = sorted(node_indices.values()) + + # Order the nodes by current selection (input list) and then apply + # the list of indices from `nodeIndices` in low-to-high order. + new_indices = dict( + zip(sorted(node_indices.keys(), + key=lambda x: selected_order[x]), + orig_indices) + ) + Reorder.set_indices(new_indices) From 3db28b74f18441d4db8fd60e3681b34041d7de37 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 19 Apr 2024 20:10:58 +0200 Subject: [PATCH 12/46] Refactor Maya load placeholder, fixes: - Implement fixes for outliner order of loaded placeholders (always place after placeholder) - Allow storing more complex data, e.g. EnumDef with multiselection=True - Change in behavior: This may fix a bug where previously placeholders may sometimes load only one subset when it should have loaded more. It could thus influence the loading behavior on existing templates - Implements a PlaceholderPlugin base class to be used by others potential plug-ins --- .../maya/api/workfile_template_builder.py | 159 +++++++++++- .../maya/plugins/template/load_placeholder.py | 234 ++++-------------- 2 files changed, 208 insertions(+), 185 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/workfile_template_builder.py b/client/ayon_core/hosts/maya/api/workfile_template_builder.py index cfd416b708..d518d3933c 100644 --- a/client/ayon_core/hosts/maya/api/workfile_template_builder.py +++ b/client/ayon_core/hosts/maya/api/workfile_template_builder.py @@ -1,3 +1,5 @@ +import json + from maya import cmds from ayon_core.pipeline import ( @@ -8,13 +10,15 @@ from ayon_core.pipeline import ( ) from ayon_core.pipeline.workfile.workfile_template_builder import ( TemplateAlreadyImported, - AbstractTemplateBuilder + AbstractTemplateBuilder, + PlaceholderPlugin, + PlaceholderItem, ) from ayon_core.tools.workfile_template_build import ( WorkfileBuildPlaceholderDialog, ) -from .lib import get_main_window +from .lib import read, imprint, get_main_window PLACEHOLDER_SET = "PLACEHOLDERS_SET" @@ -86,6 +90,157 @@ class MayaTemplateBuilder(AbstractTemplateBuilder): return True +class MayaPlaceholderPlugin(PlaceholderPlugin): + """Base Placeholder Plugin for Maya with one unified cache. + + Creates a locator as placeholder node, which during populate provide + all of its attributes defined on the locator's transform in + `placeholder.data` and where `placeholder.scene_identifier` is the + full path to the node. + + Inherited classes must still implement `populate_placeholder` + + """ + + use_selection_as_parent = True + item_class = PlaceholderItem + + def _create_placeholder_name(self, placeholder_data): + return self.identifier.replace(".", "_") + + def _collect_scene_placeholders(self): + nodes_by_identifier = self.builder.get_shared_populate_data( + "placeholder_nodes" + ) + if nodes_by_identifier is None: + # Cache placeholder data to shared data + nodes = cmds.ls("*.plugin_identifier", long=True, objectsOnly=True) + + nodes_by_identifier = {} + for node in nodes: + identifier = cmds.getAttr("{}.plugin_identifier".format(node)) + nodes_by_identifier.setdefault(identifier, []).append(node) + + # Set the cache + self.builder.set_shared_populate_data( + "placeholder_nodes", nodes_by_identifier + ) + + return nodes_by_identifier + + def create_placeholder(self, placeholder_data): + + parent = None + if self.use_selection_as_parent: + selection = cmds.ls(selection=True) + if len(selection) > 1: + raise ValueError( + "More than one node is selected. " + "Please select only one to define the parent." + ) + parent = selection[0] if selection else None + + placeholder_data["plugin_identifier"] = self.identifier + placeholder_name = self._create_placeholder_name(placeholder_data) + + placeholder = cmds.spaceLocator(name=placeholder_name)[0] + if parent: + placeholder = cmds.parent(placeholder, selection[0])[0] + + self.imprint(placeholder, placeholder_data) + + def update_placeholder(self, placeholder_item, placeholder_data): + node_name = placeholder_item.scene_identifier + + changed_values = {} + for key, value in placeholder_data.items(): + if value != placeholder_item.data.get(key): + changed_values[key] = value + + # Delete attributes to ensure we imprint new data with correct type + for key in changed_values.keys(): + placeholder_item.data[key] = value + if cmds.attributeQuery(key, node=node_name, exists=True): + attribute = "{}.{}".format(node_name, key) + cmds.deleteAttr(attribute) + + self.imprint(node_name, changed_values) + + def collect_placeholders(self): + placeholders = [] + nodes_by_identifier = self._collect_scene_placeholders() + for node in nodes_by_identifier.get(self.identifier, []): + # TODO do data validations and maybe upgrades if they are invalid + placeholder_data = self.read(node) + placeholders.append( + self.item_class(scene_identifier=node, + data=placeholder_data, + plugin=self) + ) + + return placeholders + + def post_placeholder_process(self, placeholder, failed): + """Cleanup placeholder after load of its corresponding representations. + + Hide placeholder, add them to placeholder set. + Used only by PlaceholderCreateMixin and PlaceholderLoadMixin + + Args: + placeholder (PlaceholderItem): Item which was just used to load + representation. + failed (bool): Loading of representation failed. + """ + # Hide placeholder and add them to placeholder set + node = placeholder.scene_identifier + + cmds.sets(node, addElement=PLACEHOLDER_SET) + cmds.hide(node) + cmds.setAttr("{}.hiddenInOutliner".format(node), True) + + def delete_placeholder(self, placeholder): + """Remove placeholder if building was successful + + Used only by PlaceholderCreateMixin and PlaceholderLoadMixin. + """ + node = placeholder.scene_identifier + + # To avoid that deleting a placeholder node will have Maya delete + # any objectSets the node was a member of we will first remove it + # from any sets it was a member of. This way the `PLACEHOLDERS_SET` + # will survive long enough + sets = cmds.listSets(o=node) or [] + for object_set in sets: + cmds.sets(node, remove=object_set) + + cmds.delete(node) + + def imprint(self, node, data): + """Imprint call for placeholder node""" + + # Complicated data that can't be represented as flat maya attributes + # we write to json strings, e.g. multiselection EnumDef + for key, value in data.items(): + if isinstance(value, (list, tuple, dict)): + data[key] = "JSON::{}".format(json.dumps(value)) + + imprint(node, data) + + def read(self, node): + """Read call for placeholder node""" + + data = read(node) + + # Complicated data that can't be represented as flat maya attributes + # we read from json strings, e.g. multiselection EnumDef + for key, value in data.items(): + if isinstance(value, str) and value.startswith("JSON::"): + value = value[len("JSON::"):] # strip of JSON:: prefix + data[key] = json.loads(value) + + return data + + def build_workfile_template(*args): builder = MayaTemplateBuilder(registered_host()) builder.build_template() diff --git a/client/ayon_core/hosts/maya/plugins/template/load_placeholder.py b/client/ayon_core/hosts/maya/plugins/template/load_placeholder.py index 5bfaae6500..2de4594f47 100644 --- a/client/ayon_core/hosts/maya/plugins/template/load_placeholder.py +++ b/client/ayon_core/hosts/maya/plugins/template/load_placeholder.py @@ -1,87 +1,48 @@ -import json - from maya import cmds from ayon_core.pipeline.workfile.workfile_template_builder import ( - PlaceholderPlugin, - LoadPlaceholderItem, PlaceholderLoadMixin, + LoadPlaceholderItem ) from ayon_core.hosts.maya.api.lib import ( - read, - imprint, - get_reference_node + get_container_transforms, + get_node_parent, + Reorder +) +from ayon_core.hosts.maya.api.workfile_template_builder import ( + MayaPlaceholderPlugin, ) -from ayon_core.hosts.maya.api.workfile_template_builder import PLACEHOLDER_SET -class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin): +class MayaPlaceholderLoadPlugin(MayaPlaceholderPlugin, PlaceholderLoadMixin): identifier = "maya.load" label = "Maya load" - def _collect_scene_placeholders(self): - # Cache placeholder data to shared data - placeholder_nodes = self.builder.get_shared_populate_data( - "placeholder_nodes" - ) - if placeholder_nodes is None: - attributes = cmds.ls("*.plugin_identifier", long=True) - placeholder_nodes = {} - for attribute in attributes: - node_name = attribute.rpartition(".")[0] - placeholder_nodes[node_name] = ( - self._parse_placeholder_node_data(node_name) - ) - - self.builder.set_shared_populate_data( - "placeholder_nodes", placeholder_nodes - ) - return placeholder_nodes - - def _parse_placeholder_node_data(self, node_name): - placeholder_data = read(node_name) - parent_name = ( - cmds.getAttr(node_name + ".parent", asString=True) - or node_name.rpartition("|")[0] - or "" - ) - if parent_name: - siblings = cmds.listRelatives(parent_name, children=True) - else: - siblings = cmds.ls(assemblies=True) - node_shortname = node_name.rpartition("|")[2] - current_index = cmds.getAttr(node_name + ".index", asString=True) - if current_index < 0: - current_index = siblings.index(node_shortname) - - placeholder_data.update({ - "parent": parent_name, - "index": current_index - }) - return placeholder_data + item_class = LoadPlaceholderItem def _create_placeholder_name(self, placeholder_data): - placeholder_name_parts = placeholder_data["builder_type"].split("_") - pos = 1 + # Split builder type: context_assets, linked_assets, all_assets + prefix, suffix = placeholder_data["builder_type"].split("_", 1) + parts = [prefix] + + # add family if any placeholder_product_type = placeholder_data.get("product_type") if placeholder_product_type is None: placeholder_product_type = placeholder_data.get("family") if placeholder_product_type: - placeholder_name_parts.insert(pos, placeholder_product_type) - pos += 1 + parts.append(placeholder_product_type) # add loader arguments if any loader_args = placeholder_data["loader_args"] if loader_args: - loader_args = json.loads(loader_args.replace('\'', '\"')) - values = [v for v in loader_args.values()] - for value in values: - placeholder_name_parts.insert(pos, value) - pos += 1 + loader_args = eval(loader_args) + for value in loader_args.values(): + parts.append(str(value)) - placeholder_name = "_".join(placeholder_name_parts) + parts.append(suffix) + placeholder_name = "_".join(parts) return placeholder_name.capitalize() @@ -104,68 +65,6 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin): ) return loaded_representation_ids - def create_placeholder(self, placeholder_data): - selection = cmds.ls(selection=True) - if len(selection) > 1: - raise ValueError("More then one item are selected") - - parent = selection[0] if selection else None - - placeholder_data["plugin_identifier"] = self.identifier - - placeholder_name = self._create_placeholder_name(placeholder_data) - - placeholder = cmds.spaceLocator(name=placeholder_name)[0] - if parent: - placeholder = cmds.parent(placeholder, selection[0])[0] - - imprint(placeholder, placeholder_data) - - # Add helper attributes to keep placeholder info - cmds.addAttr( - placeholder, - longName="parent", - hidden=True, - dataType="string" - ) - cmds.addAttr( - placeholder, - longName="index", - hidden=True, - attributeType="short", - defaultValue=-1 - ) - - cmds.setAttr(placeholder + ".parent", "", type="string") - - def update_placeholder(self, placeholder_item, placeholder_data): - node_name = placeholder_item.scene_identifier - new_values = {} - for key, value in placeholder_data.items(): - placeholder_value = placeholder_item.data.get(key) - if value != placeholder_value: - new_values[key] = value - placeholder_item.data[key] = value - - for key in new_values.keys(): - cmds.deleteAttr(node_name + "." + key) - - imprint(node_name, new_values) - - def collect_placeholders(self): - output = [] - scene_placeholders = self._collect_scene_placeholders() - for node_name, placeholder_data in scene_placeholders.items(): - if placeholder_data.get("plugin_identifier") != self.identifier: - continue - - # TODO do data validations and maybe upgrades if they are invalid - output.append( - LoadPlaceholderItem(node_name, placeholder_data, self) - ) - - return output - def populate_placeholder(self, placeholder): self.populate_load_placeholder(placeholder) @@ -176,25 +75,6 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin): def get_placeholder_options(self, options=None): return self.get_load_plugin_options(options) - def post_placeholder_process(self, placeholder, failed): - """Cleanup placeholder after load of its corresponding representations. - - Args: - placeholder (PlaceholderItem): Item which was just used to load - representation. - failed (bool): Loading of representation failed. - """ - # Hide placeholder and add them to placeholder set - node = placeholder.scene_identifier - - cmds.sets(node, addElement=PLACEHOLDER_SET) - cmds.hide(node) - cmds.setAttr(node + ".hiddenInOutliner", True) - - def delete_placeholder(self, placeholder): - """Remove placeholder if building was successful""" - cmds.delete(placeholder.scene_identifier) - def load_succeed(self, placeholder, container): self._parent_in_hierarchy(placeholder, container) @@ -210,55 +90,43 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin): if not container: return - roots = cmds.sets(container, q=True) or [] - ref_node = None - try: - ref_node = get_reference_node(roots) - except AssertionError as e: - self.log.info(e.args[0]) + # TODO: This currently returns only a single root but a loaded scene + # could technically load more than a single root + container_root = get_container_transforms(container, root=True) - nodes_to_parent = [] - for root in roots: - if ref_node: - ref_root = cmds.referenceQuery(root, nodes=True)[0] - ref_root = ( - cmds.listRelatives(ref_root, parent=True, path=True) or - [ref_root] - ) - nodes_to_parent.extend(ref_root) - continue - if root.endswith("_RN"): - # Backwards compatibility for hardcoded reference names. - refRoot = cmds.referenceQuery(root, n=True)[0] - refRoot = cmds.listRelatives(refRoot, parent=True) or [refRoot] - nodes_to_parent.extend(refRoot) - elif root not in cmds.listSets(allSets=True): - nodes_to_parent.append(root) + # Bugfix: The get_container_transforms does not recognize the load + # reference group currently + # TODO: Remove this when it does + parent = get_node_parent(container_root) + if parent: + container_root = parent + roots = [container_root] - elif not cmds.sets(root, q=True): - return + # Add the loaded roots to the holding sets if they exist + holding_sets = cmds.listSets(object=placeholder.scene_identifier) or [] + for holding_set in holding_sets: + cmds.sets(roots, forceElement=holding_set) - # Move loaded nodes to correct index in outliner hierarchy + # Parent the roots to the place of the placeholder locator and match + # its matrix placeholder_form = cmds.xform( placeholder.scene_identifier, - q=True, + query=True, matrix=True, worldSpace=True ) - scene_parent = cmds.listRelatives( - placeholder.scene_identifier, parent=True, fullPath=True - ) - for node in set(nodes_to_parent): - cmds.reorder(node, front=True) - cmds.reorder(node, relative=placeholder.data["index"]) - cmds.xform(node, matrix=placeholder_form, ws=True) - if scene_parent: - cmds.parent(node, scene_parent) - else: - cmds.parent(node, world=True) + scene_parent = get_node_parent(placeholder.scene_identifier) + for node in set(roots): + cmds.xform(node, matrix=placeholder_form, worldSpace=True) - holding_sets = cmds.listSets(object=placeholder.scene_identifier) - if not holding_sets: - return - for holding_set in holding_sets: - cmds.sets(roots, forceElement=holding_set) + if scene_parent != get_node_parent(node): + if scene_parent: + node = cmds.parent(node, scene_parent)[0] + else: + node = cmds.parent(node, world=True)[0] + + # Move loaded nodes in index order next to their placeholder node + cmds.reorder(node, back=True) + index = Reorder.get_index(placeholder.scene_identifier) + cmds.reorder(node, front=True) + cmds.reorder(node, relative=index + 1) From 93dda6110add3ceb9d1956eec87e1fdbaf6cb9f8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 19 Apr 2024 20:15:56 +0200 Subject: [PATCH 13/46] Remove bloated `Reorder` class in favor of the only used function --- client/ayon_core/hosts/maya/api/lib.py | 261 +----------------- .../maya/plugins/template/load_placeholder.py | 4 +- 2 files changed, 17 insertions(+), 248 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/lib.py b/client/ayon_core/hosts/maya/api/lib.py index e30070c796..2e77fe6c64 100644 --- a/client/ayon_core/hosts/maya/api/lib.py +++ b/client/ayon_core/hosts/maya/api/lib.py @@ -4406,252 +4406,21 @@ def create_rig_animation_instance( ) -class Reorder(object): - """Helper functions for reordering in Maya outliner""" +def get_node_index_under_parent(node: str) -> int: + """Return the index of a DAG node under its parent. - @staticmethod - def group_by_parent(nodes): - """Groups the given input list of nodes by parent. + Arguments: + node (str): A DAG Node path. - This is a convenience function for the Reorder functionality. - This function assumes the nodes are in the `long/fullPath` format. - """ - nodes = cmds.ls(nodes, long=True) - nodes_by_parent = defaultdict(list) - for node in nodes: - parent = node.rsplit("|", 1)[0] - nodes_by_parent[parent].append(node) - return nodes_by_parent + Returns: + int: The DAG node's index under its parents or world - @staticmethod - def get_children_with_index(parent): - """Get children under parent with their indices""" - def node_to_index(nodes): - return {node: index for index, node in enumerate(nodes)} - - if not parent: - return node_to_index(cmds.ls(assemblies=True, long=True)) - else: - return node_to_index( - cmds.listRelatives(parent, - children=True, - fullPath=True) or [] - ) - - @staticmethod - def get_index(node): - node = cmds.ls(node, long=True)[0] # enforce long names - parent = node.rsplit("|", 1)[0] - if not parent: - return cmds.ls(assemblies=True, long=True).index(node) - else: - return cmds.listRelatives(parent, - children=True, - fullPath=True).index(node) - - @staticmethod - def get_indices(nodes): - """Returns a dictionary with node, index pairs. - - This is preferred over get_index method for larger number of nodes, - because it is more optimal in performance. - - eg: - { - '|side': 3, - '|top': 1, - '|pSphere1': 4, - '|persp': 0, - '|front': 2 - } - - Returns: - dict: index by node - """ - nodes = cmds.ls(nodes, long=True) # enforce long names - node_indices = dict() - cached_children = dict() - for node in nodes: - parent = node.rsplit("|", 1)[0] - if parent not in cached_children: - cached_children[parent] = Reorder.get_children_with_index(parent) # noqa: E501 - - node_indices[node] = cached_children[parent][node] - return node_indices - - @staticmethod - def set_index(node, index): - if not node: - return - cmds.reorder(node, front=True) - cmds.reorder(node, r=index) - - @staticmethod - def set_indices(node_indices): - """Set node order by node to index dict. - - Args: - node_indices (dict): Node name to index dictionary - - """ - if not isinstance(node_indices, dict): - raise TypeError( - "Reorder.set_indices() requires a dictionary with " - "(node, index) pairs as input. " - "`{0}` is an invalid input type.".format( - type(node_indices).__name__) - ) - - if not node_indices: - return - - # force nodes to the back to not influence each other during reorder - cmds.reorder(node_indices.keys(), back=True) - - for node, index in sorted(node_indices.items(), - key=operator.itemgetter(1)): - Reorder.set_index(node, index) - - @staticmethod - def sort(nodes, key=lambda x: x.rsplit("|", 1)[-1], reverse=False): - """Sorts the node in scene by the key function. - - Default sorting key is alphabetically by using the object's short name. - """ - nodes = cmds.ls(nodes, long=True) # ensure long paths - if not nodes: - return - - # Group by parent to sort nodes per parent - parents = Reorder.group_by_parent(nodes) - - for child_nodes in parents.values(): - - node_indices = Reorder.get_indices(child_nodes) - indices = sorted(node_indices.values()) - - new_indices = { - node: indices[i] for i, node in - enumerate(sorted(child_nodes, key=key, reverse=reverse)) - } - Reorder.set_indices(new_indices) - - @staticmethod - def reverse(nodes): - nodes = cmds.ls(nodes, long=True) # ensure long paths - if not nodes: - return - - # Group by parent to sort nodes per parent - parents = Reorder.group_by_parent(nodes) - - for child_nodes in parents.values(): - - node_indices = Reorder.get_indices(child_nodes) - indices = sorted(node_indices.values(), reverse=False) - - iterable = enumerate(sorted(node_indices.items(), - key=operator.itemgetter(1), - reverse=True)) - new_indices = { - node: indices[i] for i, (node, _old_index) in iterable - } - Reorder.set_indices(new_indices) - - @staticmethod - def align_bottom(nodes): - """Reorder to the lowest (most back) of node in `nodes`.""" - nodes = cmds.ls(nodes, long=True) # ensure long paths - if not nodes: - return - - # Group by parent to sort nodes per parent - parents = Reorder.group_by_parent(nodes) - for child_nodes in parents.values(): - - # Reorder.set_index forces to front and then moves all objects - # together (so they will be stacked together). Then it applies the - # index as relative offset, we can use that here to our advantage. - # And it is a lot faster than Reorder.set_indices in that scenario. - index_per_node = Reorder.get_indices(child_nodes) - back_index = max(index_per_node.values()) - new_front_index = back_index - len(child_nodes) + 1 - Reorder.set_index(child_nodes, new_front_index) - - @staticmethod - def align_top(nodes): - """Reorder to the highest (most front) of node in `nodes`.""" - nodes = cmds.ls(nodes, long=True) # ensure long paths - if not nodes: - return - - # Group by parent to sort nodes per parent - parents = Reorder.group_by_parent(nodes) - for childNodes in parents.values(): - - # Reorder.set_index forces to front and then moves all objects - # together (so they will be stacked together). Then it applies the - # index as relative offset, we can use that here to our advantage. - # And it is a lot faster than Reorder.set_indices in that scenario. - index_per_node = Reorder.get_indices(childNodes) - front_index = min(index_per_node.values()) - Reorder.set_index(childNodes, front_index) - - @staticmethod - def move(nodes, relative, wrap=True): - """ Reorder by the given relative amount. """ - # TODO: Implement the disabling of wrapping around when at bottom. - if not nodes: - return - cmds.reorder(nodes, r=relative) - - @staticmethod - def to_bottom(nodes): - """Reorder to all the way to the bottom.""" - if not nodes: - return - cmds.reorder(nodes, back=True) - - @staticmethod - def to_top(nodes): - """Reorder to all the way to the top.""" - if not nodes: - return - cmds.reorder(nodes, front=True) - - @staticmethod - def order_to(nodes): - """Reorder the nodes to the order of the input list. - - Tip: - If you pass this your current selection list it will reorder - the nodes to the order of your selection. - - """ - nodes = cmds.ls(nodes, long=True) # ensure long paths - if not nodes: - return - - # Make a dictionary of the input order so we can optimize the look-up - # of the index in the order of the input `nodes`. - selected_order = {node: i for i, node in enumerate(nodes)} - - # Group by parent since we want to sort nodes under its current parent - parents = Reorder.group_by_parent(nodes) - for child_nodes in parents.values(): - - # Get the current indices - node_indices = Reorder.get_indices(child_nodes) - - # We get the original indices so we can position to those same - # positions, albeit with the new ordering of the nodes. - orig_indices = sorted(node_indices.values()) - - # Order the nodes by current selection (input list) and then apply - # the list of indices from `nodeIndices` in low-to-high order. - new_indices = dict( - zip(sorted(node_indices.keys(), - key=lambda x: selected_order[x]), - orig_indices) - ) - Reorder.set_indices(new_indices) + """ + node = cmds.ls(node, long=True)[0] # enforce long names + parent = node.rsplit("|", 1)[0] + if not parent: + return cmds.ls(assemblies=True, long=True).index(node) + else: + return cmds.listRelatives(parent, + children=True, + fullPath=True).index(node) diff --git a/client/ayon_core/hosts/maya/plugins/template/load_placeholder.py b/client/ayon_core/hosts/maya/plugins/template/load_placeholder.py index 2de4594f47..b07c7e9a70 100644 --- a/client/ayon_core/hosts/maya/plugins/template/load_placeholder.py +++ b/client/ayon_core/hosts/maya/plugins/template/load_placeholder.py @@ -7,7 +7,7 @@ from ayon_core.pipeline.workfile.workfile_template_builder import ( from ayon_core.hosts.maya.api.lib import ( get_container_transforms, get_node_parent, - Reorder + get_node_index_under_parent ) from ayon_core.hosts.maya.api.workfile_template_builder import ( MayaPlaceholderPlugin, @@ -127,6 +127,6 @@ class MayaPlaceholderLoadPlugin(MayaPlaceholderPlugin, PlaceholderLoadMixin): # Move loaded nodes in index order next to their placeholder node cmds.reorder(node, back=True) - index = Reorder.get_index(placeholder.scene_identifier) + index = get_node_index_under_parent(placeholder.scene_identifier) cmds.reorder(node, front=True) cmds.reorder(node, relative=index + 1) From 02f0994903f05429a54b7e24953a2710516433d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 25 Apr 2024 14:57:19 +0200 Subject: [PATCH 14/46] :bug: fix support of PySide6 in Unreal 5.4 --- client/ayon_core/hosts/unreal/ue_workers.py | 32 +++++++++++++++------ pyproject.toml | 2 +- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/client/ayon_core/hosts/unreal/ue_workers.py b/client/ayon_core/hosts/unreal/ue_workers.py index e3f8729c2e..a987abcc83 100644 --- a/client/ayon_core/hosts/unreal/ue_workers.py +++ b/client/ayon_core/hosts/unreal/ue_workers.py @@ -260,11 +260,11 @@ class UEProjectGenerationWorker(UEWorker): self.failed.emit(msg, return_code) raise RuntimeError(msg) - # ensure we have PySide2 installed in engine + # ensure we have PySide2/6 installed in engine self.progress.emit(0) self.stage_begin.emit( - (f"Checking PySide2 installation... {stage_count} " + (f"Checking Qt bindings installation... {stage_count} " f" out of {stage_count}")) python_path = None if platform.system().lower() == "windows": @@ -287,11 +287,25 @@ class UEProjectGenerationWorker(UEWorker): msg = f"Unreal Python not found at {python_path}" self.failed.emit(msg, 1) raise RuntimeError(msg) - pyside_cmd = [python_path.as_posix(), - "-m", - "pip", - "install", - "pyside2"] + + pyside_version = "PySide2" + ue_version = self.ue_version.split(".") + if int(ue_version[0]) == 5 and int(ue_version[1]) >= 4: + pyside_version = "PySide6" + + site_packages_prefix = python_path.parent.as_posix() + + pyside_cmd = [ + python_path.as_posix(), + "-m", "pip", + "install", + "--ignore-installed", + pyside_version, + "--prefix", site_packages_prefix, + ] + + print(f"--- Installing {pyside_version} ...") + print(" ".join(pyside_cmd)) pyside_install = subprocess.Popen(pyside_cmd, stdout=subprocess.PIPE, @@ -306,8 +320,8 @@ class UEProjectGenerationWorker(UEWorker): return_code = pyside_install.wait() if return_code and return_code != 0: - msg = ("Failed to create the project! " - "The installation of PySide2 has failed!") + msg = (f"Failed to create the project! {return_code} " + f"The installation of {pyside_version} has failed!: {pyside_install}") self.failed.emit(msg, return_code) raise RuntimeError(msg) diff --git a/pyproject.toml b/pyproject.toml index c1f6ddfb0b..4726bef41a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -106,7 +106,7 @@ line-ending = "auto" [tool.codespell] # Ignore words that are not in the dictionary. -ignore-words-list = "ayon,ynput,parms,parm,hda,developpement" +ignore-words-list = "ayon,ynput,parms,parm,hda,developpement,ue" skip = "./.*,./package/*,*/vendor/*,*/unreal/integration/*,*/aftereffects/api/extension/js/libs/*" count = true From 7c20ec0b564d5d3392dc558b48400144c56be8c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 25 Apr 2024 15:31:15 +0200 Subject: [PATCH 15/46] :bug: downgrade PySide6 because of the bug --- client/ayon_core/hosts/unreal/ue_workers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/unreal/ue_workers.py b/client/ayon_core/hosts/unreal/ue_workers.py index a987abcc83..cdac2c28af 100644 --- a/client/ayon_core/hosts/unreal/ue_workers.py +++ b/client/ayon_core/hosts/unreal/ue_workers.py @@ -291,7 +291,9 @@ class UEProjectGenerationWorker(UEWorker): pyside_version = "PySide2" ue_version = self.ue_version.split(".") if int(ue_version[0]) == 5 and int(ue_version[1]) >= 4: - pyside_version = "PySide6" + # Use PySide6 6.6.3 because 6.7.0 had a bug + # - 'QPushButton' can't be added to 'QBoxLayout' + pyside_version = "PySide6==6.6.3" site_packages_prefix = python_path.parent.as_posix() From 32c538d5c53ef3440d906822871fef8b67c0b8d3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:09:00 +0200 Subject: [PATCH 16/46] copied cache items from tools to lib --- client/ayon_core/lib/__init__.py | 7 + client/ayon_core/lib/cache.py | 243 +++++++++++++++++++++++++++++++ 2 files changed, 250 insertions(+) create mode 100644 client/ayon_core/lib/cache.py diff --git a/client/ayon_core/lib/__init__.py b/client/ayon_core/lib/__init__.py index 408262ca42..e436396c6c 100644 --- a/client/ayon_core/lib/__init__.py +++ b/client/ayon_core/lib/__init__.py @@ -27,6 +27,10 @@ from .local_settings import ( get_openpype_username, ) from .ayon_connection import initialize_ayon_connection +from .cache import ( + CacheItem, + NestedCacheItem, +) from .events import ( emit_event, register_event_callback @@ -157,6 +161,9 @@ __all__ = [ "initialize_ayon_connection", + "CacheItem", + "NestedCacheItem", + "emit_event", "register_event_callback", diff --git a/client/ayon_core/lib/cache.py b/client/ayon_core/lib/cache.py new file mode 100644 index 0000000000..170b9853fe --- /dev/null +++ b/client/ayon_core/lib/cache.py @@ -0,0 +1,243 @@ +import time +import collections + +InitInfo = collections.namedtuple( + "InitInfo", + ["default_factory", "lifetime"] +) + + +def _default_factory_func(): + return None + + +class CacheItem: + """Simple cache item with lifetime and default factory for default value. + + + Default factory should return default value that is used on init + and on reset. + + Args: + default_factory (Optional[callable]): Function that returns default + value used on init and on reset. + lifetime (Optional[int]): Lifetime of the cache data in seconds. + """ + + def __init__(self, default_factory=None, lifetime=None): + if lifetime is None: + lifetime = 120 + self._lifetime = lifetime + self._last_update = None + if default_factory is None: + default_factory = _default_factory_func + self._default_factory = default_factory + self._data = default_factory() + + @property + def is_valid(self): + """Is cache valid to use. + + Return: + bool: True if cache is valid, False otherwise. + """ + + if self._last_update is None: + return False + + return (time.time() - self._last_update) < self._lifetime + + def set_lifetime(self, lifetime): + """Change lifetime of cache item. + + Args: + lifetime (int): Lifetime of the cache data in seconds. + """ + + self._lifetime = lifetime + + def set_invalid(self): + """Set cache as invalid.""" + + self._last_update = None + + def reset(self): + """Set cache as invalid and reset data.""" + + self._last_update = None + self._data = self._default_factory() + + def get_data(self): + """Receive cached data. + + Returns: + Any: Any data that are cached. + """ + + return self._data + + def update_data(self, data): + self._data = data + self._last_update = time.time() + + +class NestedCacheItem: + """Helper for cached items stored in nested structure. + + Example: + >>> cache = NestedCacheItem(levels=2, default_factory=lambda: 0) + >>> cache["a"]["b"].is_valid + False + >>> cache["a"]["b"].get_data() + 0 + >>> cache["a"]["b"] = 1 + >>> cache["a"]["b"].is_valid + True + >>> cache["a"]["b"].get_data() + 1 + >>> cache.reset() + >>> cache["a"]["b"].is_valid + False + + Args: + levels (int): Number of nested levels where read cache is stored. + default_factory (Optional[callable]): Function that returns default + value used on init and on reset. + lifetime (Optional[int]): Lifetime of the cache data in seconds. + _init_info (Optional[InitInfo]): Private argument. Init info for + nested cache where created from parent item. + """ + + def __init__( + self, levels=1, default_factory=None, lifetime=None, _init_info=None + ): + if levels < 1: + raise ValueError("Nested levels must be greater than 0") + self._data_by_key = {} + if _init_info is None: + _init_info = InitInfo(default_factory, lifetime) + self._init_info = _init_info + self._levels = levels + + def __getitem__(self, key): + """Get cached data. + + Args: + key (str): Key of the cache item. + + Returns: + Union[NestedCacheItem, CacheItem]: Cache item. + """ + + cache = self._data_by_key.get(key) + if cache is None: + if self._levels > 1: + cache = NestedCacheItem( + levels=self._levels - 1, + _init_info=self._init_info + ) + else: + cache = CacheItem( + self._init_info.default_factory, + self._init_info.lifetime + ) + self._data_by_key[key] = cache + return cache + + def __setitem__(self, key, value): + """Update cached data. + + Args: + key (str): Key of the cache item. + value (Any): Any data that are cached. + """ + + if self._levels > 1: + raise AttributeError(( + "{} does not support '__setitem__'. Lower nested level by {}" + ).format(self.__class__.__name__, self._levels - 1)) + cache = self[key] + cache.update_data(value) + + def get(self, key): + """Get cached data. + + Args: + key (str): Key of the cache item. + + Returns: + Union[NestedCacheItem, CacheItem]: Cache item. + """ + + return self[key] + + def cached_count(self): + """Amount of cached items. + + Returns: + int: Amount of cached items. + """ + + return len(self._data_by_key) + + def clear_key(self, key): + """Clear cached item by key. + + Args: + key (str): Key of the cache item. + """ + + self._data_by_key.pop(key, None) + + def clear_invalid(self): + """Clear all invalid cache items. + + Note: + To clear all cache items use 'reset'. + """ + + changed = {} + children_are_nested = self._levels > 1 + for key, cache in tuple(self._data_by_key.items()): + if children_are_nested: + output = cache.clear_invalid() + if output: + changed[key] = output + if not cache.cached_count(): + self._data_by_key.pop(key) + elif not cache.is_valid: + changed[key] = cache.get_data() + self._data_by_key.pop(key) + return changed + + def reset(self): + """Reset cache. + + Note: + To clear only invalid cache items use 'clear_invalid'. + """ + + self._data_by_key = {} + + def set_lifetime(self, lifetime): + """Change lifetime of all children cache items. + + Args: + lifetime (int): Lifetime of the cache data in seconds. + """ + + self._init_info.lifetime = lifetime + for cache in self._data_by_key.values(): + cache.set_lifetime(lifetime) + + @property + def is_valid(self): + """Raise reasonable error when called on wrong level. + + Raises: + AttributeError: If called on nested cache item. + """ + + raise AttributeError(( + "{} does not support 'is_valid'. Lower nested level by '{}'" + ).format(self.__class__.__name__, self._levels)) From 8f9ae0669d1b7b86b867a1ff685ebe3b2300b015 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:09:15 +0200 Subject: [PATCH 17/46] import cache items from new location --- client/ayon_core/tools/common_models/hierarchy.py | 3 +-- client/ayon_core/tools/common_models/projects.py | 3 +-- client/ayon_core/tools/common_models/thumbnails.py | 2 +- client/ayon_core/tools/loader/models/actions.py | 2 +- client/ayon_core/tools/loader/models/products.py | 2 +- client/ayon_core/tools/loader/models/sitesync.py | 3 +-- 6 files changed, 6 insertions(+), 9 deletions(-) diff --git a/client/ayon_core/tools/common_models/hierarchy.py b/client/ayon_core/tools/common_models/hierarchy.py index d8b28f020d..78b8a7f492 100644 --- a/client/ayon_core/tools/common_models/hierarchy.py +++ b/client/ayon_core/tools/common_models/hierarchy.py @@ -6,8 +6,7 @@ import ayon_api import six from ayon_core.style import get_default_entity_icon_color - -from .cache import NestedCacheItem +from ayon_core.lib import NestedCacheItem HIERARCHY_MODEL_SENDER = "hierarchy.model" diff --git a/client/ayon_core/tools/common_models/projects.py b/client/ayon_core/tools/common_models/projects.py index e30561000e..19a38bee21 100644 --- a/client/ayon_core/tools/common_models/projects.py +++ b/client/ayon_core/tools/common_models/projects.py @@ -5,8 +5,7 @@ import ayon_api import six from ayon_core.style import get_default_entity_icon_color - -from .cache import CacheItem +from ayon_core.lib import CacheItem PROJECTS_MODEL_SENDER = "projects.model" diff --git a/client/ayon_core/tools/common_models/thumbnails.py b/client/ayon_core/tools/common_models/thumbnails.py index 1c3aadc49f..6d14783b9a 100644 --- a/client/ayon_core/tools/common_models/thumbnails.py +++ b/client/ayon_core/tools/common_models/thumbnails.py @@ -5,7 +5,7 @@ import collections import ayon_api import appdirs -from .cache import NestedCacheItem +from ayon_core.lib import NestedCacheItem FileInfo = collections.namedtuple( "FileInfo", diff --git a/client/ayon_core/tools/loader/models/actions.py b/client/ayon_core/tools/loader/models/actions.py index ad2993af50..cfe91cadab 100644 --- a/client/ayon_core/tools/loader/models/actions.py +++ b/client/ayon_core/tools/loader/models/actions.py @@ -6,6 +6,7 @@ import uuid import ayon_api +from ayon_core.lib import NestedCacheItem from ayon_core.pipeline.load import ( discover_loader_plugins, ProductLoaderPlugin, @@ -17,7 +18,6 @@ from ayon_core.pipeline.load import ( LoadError, IncompatibleLoaderError, ) -from ayon_core.tools.common_models import NestedCacheItem from ayon_core.tools.loader.abstract import ActionItem ACTIONS_MODEL_SENDER = "actions.model" diff --git a/client/ayon_core/tools/loader/models/products.py b/client/ayon_core/tools/loader/models/products.py index 812446a012..a3bbc30a09 100644 --- a/client/ayon_core/tools/loader/models/products.py +++ b/client/ayon_core/tools/loader/models/products.py @@ -5,8 +5,8 @@ import arrow import ayon_api from ayon_api.operations import OperationsSession +from ayon_core.lib import NestedCacheItem from ayon_core.style import get_default_entity_icon_color -from ayon_core.tools.common_models import NestedCacheItem from ayon_core.tools.loader.abstract import ( ProductTypeItem, ProductItem, diff --git a/client/ayon_core/tools/loader/models/sitesync.py b/client/ayon_core/tools/loader/models/sitesync.py index 987510905b..02504c2ad3 100644 --- a/client/ayon_core/tools/loader/models/sitesync.py +++ b/client/ayon_core/tools/loader/models/sitesync.py @@ -2,9 +2,8 @@ import collections from ayon_api import get_representations, get_versions_links -from ayon_core.lib import Logger +from ayon_core.lib import Logger, NestedCacheItem from ayon_core.addon import AddonsManager -from ayon_core.tools.common_models import NestedCacheItem from ayon_core.tools.loader.abstract import ActionItem DOWNLOAD_IDENTIFIER = "sitesync.download" From 734ce367fa3177ac4fd4272a16a00c5e43104d3f Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:10:09 +0200 Subject: [PATCH 18/46] added deprecation warnings to classes on previous location --- client/ayon_core/tools/common_models/cache.py | 266 ++---------------- 1 file changed, 29 insertions(+), 237 deletions(-) diff --git a/client/ayon_core/tools/common_models/cache.py b/client/ayon_core/tools/common_models/cache.py index 221a14160c..59b727728f 100644 --- a/client/ayon_core/tools/common_models/cache.py +++ b/client/ayon_core/tools/common_models/cache.py @@ -1,239 +1,31 @@ -import time -import collections +import warnings -InitInfo = collections.namedtuple( - "InitInfo", - ["default_factory", "lifetime"] +from ayon_core.lib import CacheItem as _CacheItem +from ayon_core.lib import NestedCacheItem as _NestedCacheItem + + +# Cache classes were moved to `ayon_core.lib.cache` +class CacheItem(_CacheItem): + def __init__(self, *args, **kwargs): + warnings.warn( + "Used 'CacheItem' from deprecated location " + "'ayon_core.tools.common_models', use 'ayon_core.lib' instead.", + DeprecationWarning, + ) + super().__init__(*args, **kwargs) + + +class NestedCacheItem(_NestedCacheItem): + def __init__(self, *args, **kwargs): + warnings.warn( + "Used 'NestedCacheItem' from deprecated location " + "'ayon_core.tools.common_models', use 'ayon_core.lib' instead.", + DeprecationWarning, + ) + super().__init__(*args, **kwargs) + + +__all__ = ( + "CacheItem", + "NestedCacheItem", ) - - -def _default_factory_func(): - return None - - -class CacheItem: - """Simple cache item with lifetime and default value. - - Args: - default_factory (Optional[callable]): Function that returns default - value used on init and on reset. - lifetime (Optional[int]): Lifetime of the cache data in seconds. - """ - - def __init__(self, default_factory=None, lifetime=None): - if lifetime is None: - lifetime = 120 - self._lifetime = lifetime - self._last_update = None - if default_factory is None: - default_factory = _default_factory_func - self._default_factory = default_factory - self._data = default_factory() - - @property - def is_valid(self): - """Is cache valid to use. - - Return: - bool: True if cache is valid, False otherwise. - """ - - if self._last_update is None: - return False - - return (time.time() - self._last_update) < self._lifetime - - def set_lifetime(self, lifetime): - """Change lifetime of cache item. - - Args: - lifetime (int): Lifetime of the cache data in seconds. - """ - - self._lifetime = lifetime - - def set_invalid(self): - """Set cache as invalid.""" - - self._last_update = None - - def reset(self): - """Set cache as invalid and reset data.""" - - self._last_update = None - self._data = self._default_factory() - - def get_data(self): - """Receive cached data. - - Returns: - Any: Any data that are cached. - """ - - return self._data - - def update_data(self, data): - self._data = data - self._last_update = time.time() - - -class NestedCacheItem: - """Helper for cached items stored in nested structure. - - Example: - >>> cache = NestedCacheItem(levels=2, default_factory=lambda: 0) - >>> cache["a"]["b"].is_valid - False - >>> cache["a"]["b"].get_data() - 0 - >>> cache["a"]["b"] = 1 - >>> cache["a"]["b"].is_valid - True - >>> cache["a"]["b"].get_data() - 1 - >>> cache.reset() - >>> cache["a"]["b"].is_valid - False - - Args: - levels (int): Number of nested levels where read cache is stored. - default_factory (Optional[callable]): Function that returns default - value used on init and on reset. - lifetime (Optional[int]): Lifetime of the cache data in seconds. - _init_info (Optional[InitInfo]): Private argument. Init info for - nested cache where created from parent item. - """ - - def __init__( - self, levels=1, default_factory=None, lifetime=None, _init_info=None - ): - if levels < 1: - raise ValueError("Nested levels must be greater than 0") - self._data_by_key = {} - if _init_info is None: - _init_info = InitInfo(default_factory, lifetime) - self._init_info = _init_info - self._levels = levels - - def __getitem__(self, key): - """Get cached data. - - Args: - key (str): Key of the cache item. - - Returns: - Union[NestedCacheItem, CacheItem]: Cache item. - """ - - cache = self._data_by_key.get(key) - if cache is None: - if self._levels > 1: - cache = NestedCacheItem( - levels=self._levels - 1, - _init_info=self._init_info - ) - else: - cache = CacheItem( - self._init_info.default_factory, - self._init_info.lifetime - ) - self._data_by_key[key] = cache - return cache - - def __setitem__(self, key, value): - """Update cached data. - - Args: - key (str): Key of the cache item. - value (Any): Any data that are cached. - """ - - if self._levels > 1: - raise AttributeError(( - "{} does not support '__setitem__'. Lower nested level by {}" - ).format(self.__class__.__name__, self._levels - 1)) - cache = self[key] - cache.update_data(value) - - def get(self, key): - """Get cached data. - - Args: - key (str): Key of the cache item. - - Returns: - Union[NestedCacheItem, CacheItem]: Cache item. - """ - - return self[key] - - def cached_count(self): - """Amount of cached items. - - Returns: - int: Amount of cached items. - """ - - return len(self._data_by_key) - - def clear_key(self, key): - """Clear cached item by key. - - Args: - key (str): Key of the cache item. - """ - - self._data_by_key.pop(key, None) - - def clear_invalid(self): - """Clear all invalid cache items. - - Note: - To clear all cache items use 'reset'. - """ - - changed = {} - children_are_nested = self._levels > 1 - for key, cache in tuple(self._data_by_key.items()): - if children_are_nested: - output = cache.clear_invalid() - if output: - changed[key] = output - if not cache.cached_count(): - self._data_by_key.pop(key) - elif not cache.is_valid: - changed[key] = cache.get_data() - self._data_by_key.pop(key) - return changed - - def reset(self): - """Reset cache. - - Note: - To clear only invalid cache items use 'clear_invalid'. - """ - - self._data_by_key = {} - - def set_lifetime(self, lifetime): - """Change lifetime of all children cache items. - - Args: - lifetime (int): Lifetime of the cache data in seconds. - """ - - self._init_info.lifetime = lifetime - for cache in self._data_by_key.values(): - cache.set_lifetime(lifetime) - - @property - def is_valid(self): - """Raise reasonable error when called on wront level. - - Raises: - AttributeError: If called on nested cache item. - """ - - raise AttributeError(( - "{} does not support 'is_valid'. Lower nested level by '{}'" - ).format(self.__class__.__name__, self._levels)) From 86bbd24a9a7666d1475fbc22bf61597da3eef25d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:15:46 +0200 Subject: [PATCH 19/46] use cache items in anatomy --- client/ayon_core/pipeline/anatomy/anatomy.py | 76 ++++---------------- 1 file changed, 15 insertions(+), 61 deletions(-) diff --git a/client/ayon_core/pipeline/anatomy/anatomy.py b/client/ayon_core/pipeline/anatomy/anatomy.py index 2aa8eeddbc..35599e85d6 100644 --- a/client/ayon_core/pipeline/anatomy/anatomy.py +++ b/client/ayon_core/pipeline/anatomy/anatomy.py @@ -3,11 +3,16 @@ import re import copy import platform import collections -import time import ayon_api -from ayon_core.lib import Logger, get_local_site_id, StringTemplate +from ayon_core.lib import ( + Logger, + get_local_site_id, + StringTemplate, + CacheItem, + NestedCacheItem, +) from ayon_core.addon import AddonsManager from .exceptions import RootCombinationError, ProjectNotSet @@ -397,62 +402,11 @@ class BaseAnatomy(object): ) -class CacheItem: - """Helper to cache data. - - Helper does not handle refresh of data and does not mark data as outdated. - Who uses the object should check of outdated state on his own will. - """ - - default_lifetime = 10 - - def __init__(self, lifetime=None): - self._data = None - self._cached = None - self._lifetime = lifetime or self.default_lifetime - - @property - def data(self): - """Cached data/object. - - Returns: - Any: Whatever was cached. - """ - - return self._data - - @property - def is_outdated(self): - """Item has outdated cache. - - Lifetime of cache item expired or was not yet set. - - Returns: - bool: Item is outdated. - """ - - if self._cached is None: - return True - return (time.time() - self._cached) > self._lifetime - - def update_data(self, data): - """Update cache of data. - - Args: - data (Any): Data to cache. - """ - - self._data = data - self._cached = time.time() - - class Anatomy(BaseAnatomy): - _sitesync_addon_cache = CacheItem() - _project_cache = collections.defaultdict(CacheItem) - _default_site_id_cache = collections.defaultdict(CacheItem) - _root_overrides_cache = collections.defaultdict( - lambda: collections.defaultdict(CacheItem) - ) + _sitesync_addon_cache = CacheItem(lifetime=10) + _project_cache = NestedCacheItem(lifetime=10) + _default_site_id_cache = NestedCacheItem(lifetime=10) + _root_overrides_cache = NestedCacheItem(2, lifetime=10) def __init__( self, project_name=None, site_name=None, project_entity=None @@ -479,7 +433,7 @@ class Anatomy(BaseAnatomy): project_cache = cls._project_cache[project_name] if project_cache.is_outdated: project_cache.update_data(ayon_api.get_project(project_name)) - return copy.deepcopy(project_cache.data) + return copy.deepcopy(project_cache.get_data()) @classmethod def get_sitesync_addon(cls): @@ -488,7 +442,7 @@ class Anatomy(BaseAnatomy): cls._sitesync_addon_cache.update_data( manager.get_enabled_addon("sitesync") ) - return cls._sitesync_addon_cache.data + return cls._sitesync_addon_cache.get_data() @classmethod def _get_studio_roots_overrides(cls, project_name): @@ -537,7 +491,7 @@ class Anatomy(BaseAnatomy): project_cache.update_data( sitesync_addon.get_active_site_type(project_name) ) - site_name = project_cache.data + site_name = project_cache.get_data() site_cache = cls._root_overrides_cache[project_name][site_name] if site_cache.is_outdated: @@ -553,4 +507,4 @@ class Anatomy(BaseAnatomy): project_name, site_name ) site_cache.update_data(roots_overrides) - return site_cache.data + return site_cache.get_data() From 27c3a7bcaa2e3df0e2075624bc99d483c770b0b9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:15:56 +0200 Subject: [PATCH 20/46] change defautlt timeouts --- client/ayon_core/pipeline/anatomy/anatomy.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/pipeline/anatomy/anatomy.py b/client/ayon_core/pipeline/anatomy/anatomy.py index 35599e85d6..db8a00fa08 100644 --- a/client/ayon_core/pipeline/anatomy/anatomy.py +++ b/client/ayon_core/pipeline/anatomy/anatomy.py @@ -403,10 +403,10 @@ class BaseAnatomy(object): class Anatomy(BaseAnatomy): - _sitesync_addon_cache = CacheItem(lifetime=10) _project_cache = NestedCacheItem(lifetime=10) - _default_site_id_cache = NestedCacheItem(lifetime=10) - _root_overrides_cache = NestedCacheItem(2, lifetime=10) + _sitesync_addon_cache = CacheItem(lifetime=60) + _default_site_id_cache = NestedCacheItem(lifetime=60) + _root_overrides_cache = NestedCacheItem(2, lifetime=60) def __init__( self, project_name=None, site_name=None, project_entity=None From 3980f59211a389c7d1a11b4d9874d0f2147898c2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:07:28 +0200 Subject: [PATCH 21/46] fix docstring lines --- client/ayon_core/lib/cache.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/client/ayon_core/lib/cache.py b/client/ayon_core/lib/cache.py index 170b9853fe..be6fd4f4d7 100644 --- a/client/ayon_core/lib/cache.py +++ b/client/ayon_core/lib/cache.py @@ -14,7 +14,6 @@ def _default_factory_func(): class CacheItem: """Simple cache item with lifetime and default factory for default value. - Default factory should return default value that is used on init and on reset. @@ -22,8 +21,8 @@ class CacheItem: default_factory (Optional[callable]): Function that returns default value used on init and on reset. lifetime (Optional[int]): Lifetime of the cache data in seconds. - """ + """ def __init__(self, default_factory=None, lifetime=None): if lifetime is None: lifetime = 120 @@ -40,8 +39,8 @@ class CacheItem: Return: bool: True if cache is valid, False otherwise. - """ + """ if self._last_update is None: return False @@ -72,8 +71,8 @@ class CacheItem: Returns: Any: Any data that are cached. - """ + """ return self._data def update_data(self, data): @@ -106,8 +105,8 @@ class NestedCacheItem: lifetime (Optional[int]): Lifetime of the cache data in seconds. _init_info (Optional[InitInfo]): Private argument. Init info for nested cache where created from parent item. - """ + """ def __init__( self, levels=1, default_factory=None, lifetime=None, _init_info=None ): @@ -127,8 +126,8 @@ class NestedCacheItem: Returns: Union[NestedCacheItem, CacheItem]: Cache item. - """ + """ cache = self._data_by_key.get(key) if cache is None: if self._levels > 1: @@ -150,8 +149,8 @@ class NestedCacheItem: Args: key (str): Key of the cache item. value (Any): Any data that are cached. - """ + """ if self._levels > 1: raise AttributeError(( "{} does not support '__setitem__'. Lower nested level by {}" @@ -167,8 +166,8 @@ class NestedCacheItem: Returns: Union[NestedCacheItem, CacheItem]: Cache item. - """ + """ return self[key] def cached_count(self): @@ -176,8 +175,8 @@ class NestedCacheItem: Returns: int: Amount of cached items. - """ + """ return len(self._data_by_key) def clear_key(self, key): @@ -185,8 +184,8 @@ class NestedCacheItem: Args: key (str): Key of the cache item. - """ + """ self._data_by_key.pop(key, None) def clear_invalid(self): @@ -194,8 +193,8 @@ class NestedCacheItem: Note: To clear all cache items use 'reset'. - """ + """ changed = {} children_are_nested = self._levels > 1 for key, cache in tuple(self._data_by_key.items()): @@ -215,8 +214,8 @@ class NestedCacheItem: Note: To clear only invalid cache items use 'clear_invalid'. - """ + """ self._data_by_key = {} def set_lifetime(self, lifetime): @@ -224,8 +223,8 @@ class NestedCacheItem: Args: lifetime (int): Lifetime of the cache data in seconds. - """ + """ self._init_info.lifetime = lifetime for cache in self._data_by_key.values(): cache.set_lifetime(lifetime) @@ -236,8 +235,8 @@ class NestedCacheItem: Raises: AttributeError: If called on nested cache item. - """ + """ raise AttributeError(( "{} does not support 'is_valid'. Lower nested level by '{}'" ).format(self.__class__.__name__, self._levels)) From f10d2660297224720dcc12aa57340d4cd648985d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:07:35 +0200 Subject: [PATCH 22/46] add missing docstring --- client/ayon_core/lib/cache.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/ayon_core/lib/cache.py b/client/ayon_core/lib/cache.py index be6fd4f4d7..c661a16f7b 100644 --- a/client/ayon_core/lib/cache.py +++ b/client/ayon_core/lib/cache.py @@ -76,6 +76,12 @@ class CacheItem: return self._data def update_data(self, data): + """Update cache data. + + Args: + data (Any): Any data that are cached. + + """ self._data = data self._last_update = time.time() From 2594fc82b061ceacee66765b402b1148057e0960 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:07:50 +0200 Subject: [PATCH 23/46] added default value of lifetime to docstring --- client/ayon_core/lib/cache.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/lib/cache.py b/client/ayon_core/lib/cache.py index c661a16f7b..005c900c9f 100644 --- a/client/ayon_core/lib/cache.py +++ b/client/ayon_core/lib/cache.py @@ -21,6 +21,7 @@ class CacheItem: default_factory (Optional[callable]): Function that returns default value used on init and on reset. lifetime (Optional[int]): Lifetime of the cache data in seconds. + Default lifetime is 120 seconds. """ def __init__(self, default_factory=None, lifetime=None): From 907fa5de934aeea15c9706bed1d3cb46f496a199 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:08:43 +0200 Subject: [PATCH 24/46] added default value to 'NestedCacheItem' --- client/ayon_core/lib/cache.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/lib/cache.py b/client/ayon_core/lib/cache.py index 005c900c9f..dc83520f76 100644 --- a/client/ayon_core/lib/cache.py +++ b/client/ayon_core/lib/cache.py @@ -110,6 +110,7 @@ class NestedCacheItem: default_factory (Optional[callable]): Function that returns default value used on init and on reset. lifetime (Optional[int]): Lifetime of the cache data in seconds. + Default value is based on default value of 'CacheItem'. _init_info (Optional[InitInfo]): Private argument. Init info for nested cache where created from parent item. From 64ff24dbf12c9979d23f3558e7f619ba02fa8b54 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:45:07 +0200 Subject: [PATCH 25/46] use 'is_valid' instead of 'is_outdated' --- client/ayon_core/pipeline/anatomy/anatomy.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/pipeline/anatomy/anatomy.py b/client/ayon_core/pipeline/anatomy/anatomy.py index db8a00fa08..98bbaa9bdc 100644 --- a/client/ayon_core/pipeline/anatomy/anatomy.py +++ b/client/ayon_core/pipeline/anatomy/anatomy.py @@ -431,13 +431,13 @@ class Anatomy(BaseAnatomy): @classmethod def get_project_entity_from_cache(cls, project_name): project_cache = cls._project_cache[project_name] - if project_cache.is_outdated: + if not project_cache.is_valid: project_cache.update_data(ayon_api.get_project(project_name)) return copy.deepcopy(project_cache.get_data()) @classmethod def get_sitesync_addon(cls): - if cls._sitesync_addon_cache.is_outdated: + if not cls._sitesync_addon_cache.is_valid: manager = AddonsManager() cls._sitesync_addon_cache.update_data( manager.get_enabled_addon("sitesync") @@ -487,14 +487,14 @@ class Anatomy(BaseAnatomy): elif not site_name: # Use sync server to receive active site name project_cache = cls._default_site_id_cache[project_name] - if project_cache.is_outdated: + if not project_cache.is_valid: project_cache.update_data( sitesync_addon.get_active_site_type(project_name) ) site_name = project_cache.get_data() site_cache = cls._root_overrides_cache[project_name][site_name] - if site_cache.is_outdated: + if not site_cache.is_valid: if site_name == "studio": # Handle studio root overrides without sync server # - studio root overrides can be done even without sync server From 2d0c1062387aec013de1ca7d0e34747e67c90f60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 30 Apr 2024 12:55:12 +0200 Subject: [PATCH 26/46] :art: add 3dequalizer support to applications addon --- client/ayon_core/resources/app_icons/3de4.png | Bin 0 -> 15879 bytes server_addon/applications/package.py | 2 +- .../applications/server/applications.json | 22 ++++++++++++++++++ server_addon/applications/server/settings.py | 2 ++ 4 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 client/ayon_core/resources/app_icons/3de4.png diff --git a/client/ayon_core/resources/app_icons/3de4.png b/client/ayon_core/resources/app_icons/3de4.png new file mode 100644 index 0000000000000000000000000000000000000000..bd0fe40d37a3ca96444f37644de01fc45ab22106 GIT binary patch literal 15879 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 literal 0 HcmV?d00001 diff --git a/server_addon/applications/package.py b/server_addon/applications/package.py index ce312ed662..bcc91f1d84 100644 --- a/server_addon/applications/package.py +++ b/server_addon/applications/package.py @@ -1,3 +1,3 @@ name = "applications" title = "Applications" -version = "0.2.0" +version = "0.2.1" diff --git a/server_addon/applications/server/applications.json b/server_addon/applications/server/applications.json index e4b72fdff9..48a8a66161 100644 --- a/server_addon/applications/server/applications.json +++ b/server_addon/applications/server/applications.json @@ -1271,6 +1271,28 @@ } ] }, + "equalizer": { + "enabled": true, + "label": "3DEqualizer", + "icon": "{}/app_icons/zbrush.png", + "host_name": "equalizer", + "environment": "{}", + "variants": [ + { + "name": "7-1v2", + "label": "7.1v2", + "use_python_2": false, + "executables": { + "windows": [ + "C:\\Program Files\\3DE4_win64_r7.1v2\\bin\\3DE4.exe" + ], + "darwin": [], + "linux": [] + }, + + } + ] + }, "additional_apps": [] } } diff --git a/server_addon/applications/server/settings.py b/server_addon/applications/server/settings.py index 5743e9f471..b77686cee0 100644 --- a/server_addon/applications/server/settings.py +++ b/server_addon/applications/server/settings.py @@ -190,6 +190,8 @@ class ApplicationsSettings(BaseSettingsModel): default_factory=AppGroupWithPython, title="OpenRV") zbrush: AppGroup = SettingsField( default_factory=AppGroupWithPython, title="Zbrush") + equalizer: AppGroup = SettingsField( + default_factory=AppGroupWithPython, title="3DEqualizer") additional_apps: list[AdditionalAppGroup] = SettingsField( default_factory=list, title="Additional Applications") From 678296c1dbab9b84f421504d3cb95bcab7b2bf9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 30 Apr 2024 12:56:57 +0200 Subject: [PATCH 27/46] :bug: fix icon --- server_addon/applications/server/applications.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/applications/server/applications.json b/server_addon/applications/server/applications.json index 48a8a66161..ae3e82794d 100644 --- a/server_addon/applications/server/applications.json +++ b/server_addon/applications/server/applications.json @@ -1274,7 +1274,7 @@ "equalizer": { "enabled": true, "label": "3DEqualizer", - "icon": "{}/app_icons/zbrush.png", + "icon": "{}/app_icons/3de4.png", "host_name": "equalizer", "environment": "{}", "variants": [ From 8e2f3235c6ab6704e744ffe2cd977b22940f24b4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Apr 2024 12:58:08 +0200 Subject: [PATCH 28/46] Remove unused import --- client/ayon_core/hosts/maya/api/lib.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/api/lib.py b/client/ayon_core/hosts/maya/api/lib.py index ff94459a31..e7361c6910 100644 --- a/client/ayon_core/hosts/maya/api/lib.py +++ b/client/ayon_core/hosts/maya/api/lib.py @@ -6,7 +6,6 @@ from pprint import pformat import sys import uuid import re -import operator import json import logging From 74bbf91e097c353b44c8e3adc72dcb621c39ec63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 30 Apr 2024 12:59:25 +0200 Subject: [PATCH 29/46] :bug: fix json --- server_addon/applications/server/applications.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/applications/server/applications.json b/server_addon/applications/server/applications.json index ae3e82794d..84b7fa33cf 100644 --- a/server_addon/applications/server/applications.json +++ b/server_addon/applications/server/applications.json @@ -1289,7 +1289,7 @@ "darwin": [], "linux": [] }, - + "environment": "{}" } ] }, From 9f4c7018a289a2bd060bd8ad1c25969952e93c28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 30 Apr 2024 16:36:08 +0200 Subject: [PATCH 30/46] :recycle: make validator optional --- .../maya/plugins/publish/validate_rendersettings.py | 9 +++++++-- server_addon/maya/server/settings/publishers.py | 9 ++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_rendersettings.py b/client/ayon_core/hosts/maya/plugins/publish/validate_rendersettings.py index 78a247b3f2..987e9eec7c 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_rendersettings.py @@ -10,6 +10,7 @@ from ayon_core.pipeline.publish import ( RepairAction, ValidateContentsOrder, PublishValidationError, + OptionalPyblishPluginMixin ) from ayon_core.hosts.maya.api import lib from ayon_core.hosts.maya.api.lib_rendersettings import RenderSettings @@ -37,7 +38,8 @@ def get_redshift_image_format_labels(): return mel.eval("{0}={0}".format(var)) -class ValidateRenderSettings(pyblish.api.InstancePlugin): +class ValidateRenderSettings(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validates the global render settings * File Name Prefix must start with: `` @@ -55,7 +57,7 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): * Frame Padding must be: * default: 4 - * Animation must be toggle on, in Render Settings - Common tab: + * Animation must be toggled on, in Render Settings - Common tab: * vray: Animation on standard of specific * arnold: Frame / Animation ext: Any choice without "(Single Frame)" * redshift: Animation toggled on @@ -71,6 +73,7 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): hosts = ["maya"] families = ["renderlayer"] actions = [RepairAction] + optional = True ImagePrefixes = { 'mentalray': 'defaultRenderGlobals.imageFilePrefix', @@ -112,6 +115,8 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): DEFAULT_PREFIX = "//_" def process(self, instance): + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 27288053a2..c29d52f95e 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -184,7 +184,7 @@ class ValidateAttributesModel(BaseSettingsModel): if not success: raise BadRequestException( - "The attibutes can't be parsed as json object" + "The attributes can't be parsed as json object" ) return value @@ -220,7 +220,7 @@ class ValidateUnrealStaticMeshNameModel(BaseSettingsModel): enabled: bool = SettingsField(title="ValidateUnrealStaticMeshName") optional: bool = SettingsField(title="Optional") validate_mesh: bool = SettingsField(title="Validate mesh names") - validate_collision: bool = SettingsField(title="Validate collison names") + validate_collision: bool = SettingsField(title="Validate collision names") class ValidateCycleErrorModel(BaseSettingsModel): @@ -265,6 +265,7 @@ class RendererAttributesModel(BaseSettingsModel): class ValidateRenderSettingsModel(BaseSettingsModel): + optional: bool = SettingsField(title="Optional") arnold_render_attributes: list[RendererAttributesModel] = SettingsField( default_factory=list, title="Arnold Render Attributes") vray_render_attributes: list[RendererAttributesModel] = SettingsField( @@ -392,7 +393,7 @@ class ExtractGPUCacheModel(BaseSettingsModel): title="Optimize Animations For Motion Blur" ) writeMaterials: bool = SettingsField(title="Write Materials") - useBaseTessellation: bool = SettingsField(title="User Base Tesselation") + useBaseTessellation: bool = SettingsField(title="User Based Tessellation") class PublishersModel(BaseSettingsModel): @@ -942,6 +943,8 @@ DEFAULT_PUBLISH_SETTINGS = { ] }, "ValidateRenderSettings": { + "enabled": True, + "optional": False, "arnold_render_attributes": [], "vray_render_attributes": [], "redshift_render_attributes": [], From f943cb98e831476afbfe75f3a41a8b1b6ecda833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 30 Apr 2024 17:27:03 +0200 Subject: [PATCH 31/46] :recycle: support enabled state --- server_addon/maya/server/settings/publishers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index c29d52f95e..30f23904e2 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -265,6 +265,7 @@ class RendererAttributesModel(BaseSettingsModel): class ValidateRenderSettingsModel(BaseSettingsModel): + enabled: bool = SettingsField(title="ValidateRenderSettings") optional: bool = SettingsField(title="Optional") arnold_render_attributes: list[RendererAttributesModel] = SettingsField( default_factory=list, title="Arnold Render Attributes") From 8e4fe3bddec8adb29e8be4f079d6ea735482f56d Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Tue, 30 Apr 2024 16:28:15 +0100 Subject: [PATCH 32/46] Update client/ayon_core/hosts/hiero/api/workio.py Co-authored-by: Kayla Man <64118225+moonyuet@users.noreply.github.com> --- client/ayon_core/hosts/hiero/api/workio.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/ayon_core/hosts/hiero/api/workio.py b/client/ayon_core/hosts/hiero/api/workio.py index 9d8b1777e7..c7b749a9dd 100644 --- a/client/ayon_core/hosts/hiero/api/workio.py +++ b/client/ayon_core/hosts/hiero/api/workio.py @@ -56,8 +56,11 @@ def open_file(filepath): # Close previous project if its different to the current project. if project.path() != filepath: + # open project file + hiero.core.openProject(filepath.replace(os.path.sep, "/")) project.close() + return True From e3619120fff489b36d527f5e195a0827e674e3c8 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 30 Apr 2024 16:30:11 +0100 Subject: [PATCH 33/46] Remove redundant openProject --- client/ayon_core/hosts/hiero/api/workio.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/client/ayon_core/hosts/hiero/api/workio.py b/client/ayon_core/hosts/hiero/api/workio.py index c7b749a9dd..de6f1f4a37 100644 --- a/client/ayon_core/hosts/hiero/api/workio.py +++ b/client/ayon_core/hosts/hiero/api/workio.py @@ -51,9 +51,6 @@ def open_file(filepath): project = hiero.core.projects()[-1] - # open project file - hiero.core.openProject(filepath.replace(os.path.sep, "/")) - # Close previous project if its different to the current project. if project.path() != filepath: # open project file From 14df54020b4feae55e995f0ecd396af9b86f842a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 1 May 2024 00:02:19 +0200 Subject: [PATCH 34/46] Resolve merge conflict, implement change from `develop` --- client/ayon_core/hosts/maya/api/workfile_template_builder.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/ayon_core/hosts/maya/api/workfile_template_builder.py b/client/ayon_core/hosts/maya/api/workfile_template_builder.py index d518d3933c..f4f9a34983 100644 --- a/client/ayon_core/hosts/maya/api/workfile_template_builder.py +++ b/client/ayon_core/hosts/maya/api/workfile_template_builder.py @@ -194,6 +194,11 @@ class MayaPlaceholderPlugin(PlaceholderPlugin): # Hide placeholder and add them to placeholder set node = placeholder.scene_identifier + # If we just populate the placeholders from current scene, the + # placeholder set will not be created so account for that. + if not cmds.objExists(PLACEHOLDER_SET): + cmds.sets(name=PLACEHOLDER_SET, empty=True) + cmds.sets(node, addElement=PLACEHOLDER_SET) cmds.hide(node) cmds.setAttr("{}.hiddenInOutliner".format(node), True) From 84dc455bbe6aadce32361d154c3014a95951f998 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 1 May 2024 01:36:15 +0200 Subject: [PATCH 35/46] Fix `extract_alembic` imports --- client/ayon_core/hosts/maya/plugins/publish/extract_assembly.py | 2 +- .../ayon_core/hosts/maya/plugins/publish/extract_proxy_abc.py | 2 +- .../maya/plugins/publish/extract_unreal_skeletalmesh_abc.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_assembly.py b/client/ayon_core/hosts/maya/plugins/publish/extract_assembly.py index 2c23f9b752..5f51dc38cb 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_assembly.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_assembly.py @@ -2,7 +2,7 @@ import os import json from ayon_core.pipeline import publish -from ayon_core.hosts.maya.api.lib import extract_alembic +from ayon_core.hosts.maya.api.alembic import extract_alembic from maya import cmds diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_proxy_abc.py b/client/ayon_core/hosts/maya/plugins/publish/extract_proxy_abc.py index 3637a58614..5aefdfc33a 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_proxy_abc.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_proxy_abc.py @@ -3,8 +3,8 @@ import os from maya import cmds from ayon_core.pipeline import publish +from ayon_core.hosts.maya.api.alembic import extract_alembic from ayon_core.hosts.maya.api.lib import ( - extract_alembic, suspended_refresh, maintained_selection, iter_visible_nodes_in_range diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_unreal_skeletalmesh_abc.py b/client/ayon_core/hosts/maya/plugins/publish/extract_unreal_skeletalmesh_abc.py index 1a389f3d33..b5cc7745a1 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_unreal_skeletalmesh_abc.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_unreal_skeletalmesh_abc.py @@ -5,8 +5,8 @@ import os from maya import cmds # noqa from ayon_core.pipeline import publish +from ayon_core.hosts.maya.api.alembic import extract_alembic from ayon_core.hosts.maya.api.lib import ( - extract_alembic, suspended_refresh, maintained_selection ) From 5643cbb9ca5dd2dfcbaee3ff2fb2c7dc23954cf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 2 May 2024 11:25:42 +0200 Subject: [PATCH 36/46] :bug: fix invalid enabled flag unrelated to the original PR, sorry --- server_addon/maya/server/settings/publishers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 30f23904e2..c46aa59453 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -243,7 +243,7 @@ class ValidatePluginPathAttributesModel(BaseSettingsModel): and the node attribute is abc_file """ - enabled: bool = True + enabled: bool = SettingsField(title="Enabled") optional: bool = SettingsField(title="Optional") active: bool = SettingsField(title="Active") attribute: list[ValidatePluginPathAttributesAttrModel] = SettingsField( From 2c5a43dc931737c2e1aed2dfe6614545bb6c275f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 2 May 2024 11:42:46 +0200 Subject: [PATCH 37/46] :recycle: limit the site prefix only to windows --- client/ayon_core/hosts/unreal/ue_workers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/unreal/ue_workers.py b/client/ayon_core/hosts/unreal/ue_workers.py index cdac2c28af..256c0557be 100644 --- a/client/ayon_core/hosts/unreal/ue_workers.py +++ b/client/ayon_core/hosts/unreal/ue_workers.py @@ -303,9 +303,12 @@ class UEProjectGenerationWorker(UEWorker): "install", "--ignore-installed", pyside_version, - "--prefix", site_packages_prefix, + ] + if platform.system().lower() == "windows": + pyside_cmd += ["--target", site_packages_prefix] + print(f"--- Installing {pyside_version} ...") print(" ".join(pyside_cmd)) From 94ff9d92c33bc06fd39e4c6bb99fc11e8c0a871e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 2 May 2024 11:49:06 +0200 Subject: [PATCH 38/46] Fix validate_unique_names call validate_unique_names cannot be used on fields that are just regular `list[str]`, they must be `list[SomethingSomething]`. --- server_addon/deadline/server/settings/publish_plugins.py | 1 - 1 file changed, 1 deletion(-) diff --git a/server_addon/deadline/server/settings/publish_plugins.py b/server_addon/deadline/server/settings/publish_plugins.py index 9f69143e37..784ad2560b 100644 --- a/server_addon/deadline/server/settings/publish_plugins.py +++ b/server_addon/deadline/server/settings/publish_plugins.py @@ -191,7 +191,6 @@ class NukeSubmitDeadlineModel(BaseSettingsModel): @validator( "limit_groups", - "env_allowed_keys", "env_search_replace_values") def validate_unique_names(cls, value): ensure_unique_names(value) From 396c24d9d7f99460167a30c7729fbb8d4480dffe Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 2 May 2024 11:52:21 +0200 Subject: [PATCH 39/46] Bump up version of deadline --- server_addon/deadline/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/deadline/package.py b/server_addon/deadline/package.py index 944797fea6..25ba1c1166 100644 --- a/server_addon/deadline/package.py +++ b/server_addon/deadline/package.py @@ -1,3 +1,3 @@ name = "deadline" title = "Deadline" -version = "0.1.10" +version = "0.1.11" From 3fef14e2e738cf0b86e52a5462a6b9e0b496b905 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 2 May 2024 12:28:27 +0100 Subject: [PATCH 40/46] Update client/ayon_core/hosts/hiero/api/workio.py Co-authored-by: Kayla Man <64118225+moonyuet@users.noreply.github.com> --- client/ayon_core/hosts/hiero/api/workio.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/hiero/api/workio.py b/client/ayon_core/hosts/hiero/api/workio.py index de6f1f4a37..2d0874bd07 100644 --- a/client/ayon_core/hosts/hiero/api/workio.py +++ b/client/ayon_core/hosts/hiero/api/workio.py @@ -52,9 +52,11 @@ def open_file(filepath): project = hiero.core.projects()[-1] # Close previous project if its different to the current project. + # Close previous project if its different to the current project. + filepath = filepath.replace(os.path.sep, "/") if project.path() != filepath: # open project file - hiero.core.openProject(filepath.replace(os.path.sep, "/")) + hiero.core.openProject(filepath) project.close() From 65529f3662b1fa3cc2876e65d6ba49fd66e90e4a Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 2 May 2024 12:41:00 +0100 Subject: [PATCH 41/46] Fix workio --- client/ayon_core/hosts/hiero/api/workio.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/hiero/api/workio.py b/client/ayon_core/hosts/hiero/api/workio.py index 2d0874bd07..6e8fc20172 100644 --- a/client/ayon_core/hosts/hiero/api/workio.py +++ b/client/ayon_core/hosts/hiero/api/workio.py @@ -51,15 +51,13 @@ def open_file(filepath): project = hiero.core.projects()[-1] - # Close previous project if its different to the current project. # Close previous project if its different to the current project. filepath = filepath.replace(os.path.sep, "/") - if project.path() != filepath: + if project.path().replace(os.path.sep, "/") != filepath: # open project file hiero.core.openProject(filepath) project.close() - return True From 399cb47b05499412a1fad4d3030d69f24ada879c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 3 May 2024 11:43:38 +0200 Subject: [PATCH 42/46] :bug: fix undefined task name --- client/ayon_core/pipeline/create/context.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/pipeline/create/context.py b/client/ayon_core/pipeline/create/context.py index b8618738fb..e66e15b8b1 100644 --- a/client/ayon_core/pipeline/create/context.py +++ b/client/ayon_core/pipeline/create/context.py @@ -1987,12 +1987,16 @@ class CreateContext: "Folder '{}' was not found".format(folder_path) ) - task_name = None if task_entity is None: task_name = self.get_current_task_name() - task_entity = ayon_api.get_task_by_name( - project_name, folder_entity["id"], task_name - ) + if task_name: + task_entity = ayon_api.get_task_by_name( + project_name, folder_entity["id"], task_name + ) + + task_name = None + if task_entity: + task_name = task_entity["name"] if pre_create_data is None: pre_create_data = {} @@ -2022,6 +2026,7 @@ class CreateContext: "productType": creator.product_type, "variant": variant } + print("Create instance data", instance_data) return creator.create( product_name, instance_data, From f00acb5dd220b81a1f9989deaf3952c9882af5a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 3 May 2024 12:21:10 +0200 Subject: [PATCH 43/46] :recycle: add active flag, change label --- .../hosts/maya/plugins/publish/validate_rendersettings.py | 2 +- server_addon/maya/server/settings/publishers.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_rendersettings.py b/client/ayon_core/hosts/maya/plugins/publish/validate_rendersettings.py index 987e9eec7c..7badfdc027 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_rendersettings.py @@ -69,7 +69,7 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin, """ order = ValidateContentsOrder - label = "Render Settings" + label = "Validate Render Settings" hosts = ["maya"] families = ["renderlayer"] actions = [RepairAction] diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 4ad5c9b6d0..460df803f2 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -310,8 +310,9 @@ class RendererAttributesModel(BaseSettingsModel): class ValidateRenderSettingsModel(BaseSettingsModel): - enabled: bool = SettingsField(title="ValidateRenderSettings") + enabled: bool = SettingsField(title="Enabled") optional: bool = SettingsField(title="Optional") + active: bool = SettingsField(title="Active") arnold_render_attributes: list[RendererAttributesModel] = SettingsField( default_factory=list, title="Arnold Render Attributes") vray_render_attributes: list[RendererAttributesModel] = SettingsField( From c5471e409a993758855c7a147e2159c0a16e769e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 3 May 2024 12:57:21 +0200 Subject: [PATCH 44/46] :art: add default value --- server_addon/maya/server/settings/publishers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 460df803f2..bc38d5f746 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -1175,6 +1175,7 @@ DEFAULT_PUBLISH_SETTINGS = { }, "ValidateRenderSettings": { "enabled": True, + "active": True, "optional": False, "arnold_render_attributes": [], "vray_render_attributes": [], From 5189325225dad5c9ad917496711ab2d4282190ce Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 3 May 2024 15:58:53 +0200 Subject: [PATCH 45/46] Fix comp repair folder settings --- client/ayon_core/hosts/fusion/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/fusion/api/lib.py b/client/ayon_core/hosts/fusion/api/lib.py index 08722463e1..7f7d20010d 100644 --- a/client/ayon_core/hosts/fusion/api/lib.py +++ b/client/ayon_core/hosts/fusion/api/lib.py @@ -169,7 +169,7 @@ def validate_comp_prefs(comp=None, force_repair=False): def _on_repair(): attributes = dict() for key, comp_key, _label in validations: - value = folder_value[key] + value = folder_attributes[key] comp_key_full = "Comp.FrameFormat.{}".format(comp_key) attributes[comp_key_full] = value comp.SetPrefs(attributes) From d6ae1db1b64becb58ac4daef9f2a01fbdbdde707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Mon, 6 May 2024 13:43:14 +0200 Subject: [PATCH 46/46] :recycle: refactor var name and the logic a little bit --- client/ayon_core/pipeline/create/context.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/client/ayon_core/pipeline/create/context.py b/client/ayon_core/pipeline/create/context.py index e66e15b8b1..dd005d250c 100644 --- a/client/ayon_core/pipeline/create/context.py +++ b/client/ayon_core/pipeline/create/context.py @@ -1988,16 +1988,12 @@ class CreateContext: ) if task_entity is None: - task_name = self.get_current_task_name() - if task_name: + current_task_name = self.get_current_task_name() + if current_task_name: task_entity = ayon_api.get_task_by_name( - project_name, folder_entity["id"], task_name + project_name, folder_entity["id"], current_task_name ) - task_name = None - if task_entity: - task_name = task_entity["name"] - if pre_create_data is None: pre_create_data = {} @@ -2022,11 +2018,10 @@ class CreateContext: instance_data = { "folderPath": folder_entity["path"], - "task": task_name, + "task": task_entity["name"] if task_entity else None, "productType": creator.product_type, "variant": variant } - print("Create instance data", instance_data) return creator.create( product_name, instance_data,