diff --git a/client/ayon_core/hosts/aftereffects/plugins/load/load_background.py b/client/ayon_core/hosts/aftereffects/plugins/load/load_background.py index c7f743fce4..b834875e89 100644 --- a/client/ayon_core/hosts/aftereffects/plugins/load/load_background.py +++ b/client/ayon_core/hosts/aftereffects/plugins/load/load_background.py @@ -56,16 +56,21 @@ class BackgroundLoader(api.AfterEffectsLoader): self.__class__.__name__ ) - def update(self, container, representation): + def update(self, container, context): """ Switch asset or change version """ stub = self.get_stub() - context = representation.get("context", {}) + asset_doc = context["asset"] + subset_doc = context["subset"] + repre_doc = context["representation"] + + folder_name = asset_doc["name"] + product_name = subset_doc["name"] _ = container.pop("layer") # without iterator number (_001, 002...) namespace_from_container = re.sub(r'_\d{3}$', '', container["namespace"]) - comp_name = "{}_{}".format(context["asset"], context["subset"]) + comp_name = "{}_{}".format(folder_name, product_name) # switching assets if namespace_from_container != comp_name: @@ -73,11 +78,11 @@ class BackgroundLoader(api.AfterEffectsLoader): existing_items = [layer.name for layer in items] comp_name = get_unique_layer_name( existing_items, - "{}_{}".format(context["asset"], context["subset"])) + "{}_{}".format(folder_name, product_name)) else: # switching version - keep same name comp_name = container["namespace"] - path = get_representation_path(representation) + path = get_representation_path(repre_doc) layers = get_background_layers(path) comp = stub.reload_background(container["members"][1], @@ -85,8 +90,8 @@ class BackgroundLoader(api.AfterEffectsLoader): layers) # update container - container["representation"] = str(representation["_id"]) - container["name"] = context["subset"] + container["representation"] = str(repre_doc["_id"]) + container["name"] = product_name container["namespace"] = comp_name container["members"] = comp.members @@ -104,5 +109,5 @@ class BackgroundLoader(api.AfterEffectsLoader): stub.imprint(layer.id, {}) stub.delete_item(layer.id) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) diff --git a/client/ayon_core/hosts/aftereffects/plugins/load/load_file.py b/client/ayon_core/hosts/aftereffects/plugins/load/load_file.py index a8e67e9f88..bceea66e8e 100644 --- a/client/ayon_core/hosts/aftereffects/plugins/load/load_file.py +++ b/client/ayon_core/hosts/aftereffects/plugins/load/load_file.py @@ -64,31 +64,36 @@ class FileLoader(api.AfterEffectsLoader): self.__class__.__name__ ) - def update(self, container, representation): + def update(self, container, context): """ Switch asset or change version """ stub = self.get_stub() layer = container.pop("layer") - context = representation.get("context", {}) + asset_doc = context["asset"] + subset_doc = context["subset"] + repre_doc = context["representation"] + + folder_name = asset_doc["name"] + product_name = subset_doc["name"] namespace_from_container = re.sub(r'_\d{3}$', '', container["namespace"]) - layer_name = "{}_{}".format(context["asset"], context["subset"]) + layer_name = "{}_{}".format(folder_name, product_name) # switching assets if namespace_from_container != layer_name: layers = stub.get_items(comps=True) existing_layers = [layer.name for layer in layers] layer_name = get_unique_layer_name( existing_layers, - "{}_{}".format(context["asset"], context["subset"])) + "{}_{}".format(folder_name, product_name)) else: # switching version - keep same name layer_name = container["namespace"] - path = get_representation_path(representation) + path = get_representation_path(repre_doc) # with aftereffects.maintained_selection(): # TODO stub.replace_item(layer.id, path, stub.LOADED_ICON + layer_name) stub.imprint( - layer.id, {"representation": str(representation["_id"]), - "name": context["subset"], + layer.id, {"representation": str(repre_doc["_id"]), + "name": product_name, "namespace": layer_name} ) @@ -103,5 +108,5 @@ class FileLoader(api.AfterEffectsLoader): stub.imprint(layer.id, {}) stub.delete_item(layer.id) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) diff --git a/client/ayon_core/hosts/blender/api/plugin.py b/client/ayon_core/hosts/blender/api/plugin.py index 5f9cb4a830..4b45d8ffa3 100644 --- a/client/ayon_core/hosts/blender/api/plugin.py +++ b/client/ayon_core/hosts/blender/api/plugin.py @@ -506,13 +506,13 @@ class AssetLoader(LoaderPlugin): # return self._get_instance_collection(instance_name, nodes) - def exec_update(self, container: Dict, representation: Dict): + def exec_update(self, container: Dict, context: Dict): """Must be implemented by a sub-class""" raise NotImplementedError("Must be implemented by a sub-class") - def update(self, container: Dict, representation: Dict): + def update(self, container: Dict, context: Dict): """ Run the update on Blender main thread""" - mti = MainThreadItem(self.exec_update, container, representation) + mti = MainThreadItem(self.exec_update, container, context) execute_in_main_thread(mti) def exec_remove(self, container: Dict) -> bool: diff --git a/client/ayon_core/hosts/blender/plugins/load/load_abc.py b/client/ayon_core/hosts/blender/plugins/load/load_abc.py index 4fa9881376..3fc879f9c8 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_abc.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_abc.py @@ -179,7 +179,7 @@ class CacheModelLoader(plugin.AssetLoader): self[:] = objects return objects - def exec_update(self, container: Dict, representation: Dict): + def exec_update(self, container: Dict, context: Dict): """Update the loaded asset. This will remove all objects of the current collection, load the new @@ -191,15 +191,16 @@ class CacheModelLoader(plugin.AssetLoader): Warning: No nested collections are supported at the moment! """ + repre_doc = context["representation"] object_name = container["objectName"] asset_group = bpy.data.objects.get(object_name) - libpath = Path(get_representation_path(representation)) + libpath = Path(get_representation_path(repre_doc)) extension = libpath.suffix.lower() self.log.info( "Container: %s\nRepresentation: %s", pformat(container, indent=2), - pformat(representation, indent=2), + pformat(repre_doc, indent=2), ) assert asset_group, ( @@ -244,7 +245,7 @@ class CacheModelLoader(plugin.AssetLoader): asset_group.matrix_basis = mat metadata["libpath"] = str(libpath) - metadata["representation"] = str(representation["_id"]) + metadata["representation"] = str(repre_doc["_id"]) def exec_remove(self, container: Dict) -> bool: """Remove an existing container from a Blender scene. diff --git a/client/ayon_core/hosts/blender/plugins/load/load_action.py b/client/ayon_core/hosts/blender/plugins/load/load_action.py index 61ea996b2a..df7ffe439d 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_action.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_action.py @@ -114,7 +114,7 @@ class BlendActionLoader(plugin.AssetLoader): self[:] = nodes return nodes - def update(self, container: Dict, representation: Dict): + def update(self, container: Dict, context: Dict): """Update the loaded asset. This will remove all objects of the current collection, load the new @@ -126,18 +126,18 @@ class BlendActionLoader(plugin.AssetLoader): Warning: No nested collections are supported at the moment! """ - + repre_doc = context["representation"] collection = bpy.data.collections.get( container["objectName"] ) - libpath = Path(get_representation_path(representation)) + libpath = Path(get_representation_path(repre_doc)) extension = libpath.suffix.lower() logger.info( "Container: %s\nRepresentation: %s", pformat(container, indent=2), - pformat(representation, indent=2), + pformat(repre_doc, indent=2), ) assert collection, ( @@ -241,7 +241,7 @@ class BlendActionLoader(plugin.AssetLoader): # Save the list of objects in the metadata container collection_metadata["objects"] = objects_list collection_metadata["libpath"] = str(libpath) - collection_metadata["representation"] = str(representation["_id"]) + collection_metadata["representation"] = str(repre_doc["_id"]) bpy.ops.object.select_all(action='DESELECT') diff --git a/client/ayon_core/hosts/blender/plugins/load/load_audio.py b/client/ayon_core/hosts/blender/plugins/load/load_audio.py index 023a987d63..85d5277d40 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_audio.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_audio.py @@ -96,7 +96,7 @@ class AudioLoader(plugin.AssetLoader): self[:] = objects return [objects] - def exec_update(self, container: Dict, representation: Dict): + def exec_update(self, container: Dict, context: Dict): """Update an audio strip in the sequence editor. Arguments: @@ -105,14 +105,15 @@ class AudioLoader(plugin.AssetLoader): representation (openpype:representation-1.0): Representation to update, from `host.ls()`. """ + repre_doc = context["representation"] object_name = container["objectName"] asset_group = bpy.data.objects.get(object_name) - libpath = Path(get_representation_path(representation)) + libpath = Path(get_representation_path(repre_doc)) self.log.info( "Container: %s\nRepresentation: %s", pformat(container, indent=2), - pformat(representation, indent=2), + pformat(repre_doc, indent=2), ) assert asset_group, ( @@ -175,8 +176,8 @@ class AudioLoader(plugin.AssetLoader): window_manager.windows[-1].screen.areas[0].type = old_type metadata["libpath"] = str(libpath) - metadata["representation"] = str(representation["_id"]) - metadata["parent"] = str(representation["parent"]) + metadata["representation"] = str(repre_doc["_id"]) + metadata["parent"] = str(repre_doc["parent"]) metadata["audio"] = new_audio def exec_remove(self, container: Dict) -> bool: diff --git a/client/ayon_core/hosts/blender/plugins/load/load_blend.py b/client/ayon_core/hosts/blender/plugins/load/load_blend.py index 84a4bd4398..fdae9c1b6b 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_blend.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_blend.py @@ -181,13 +181,14 @@ class BlendLoader(plugin.AssetLoader): self[:] = objects return objects - def exec_update(self, container: Dict, representation: Dict): + def exec_update(self, container: Dict, context: Dict): """ Update the loaded asset. """ + repre_doc = context["representation"] group_name = container["objectName"] asset_group = bpy.data.objects.get(group_name) - libpath = Path(get_representation_path(representation)).as_posix() + libpath = Path(get_representation_path(repre_doc)).as_posix() assert asset_group, ( f"The asset is not loaded: {container['objectName']}" @@ -234,8 +235,8 @@ class BlendLoader(plugin.AssetLoader): new_data = { "libpath": libpath, - "representation": str(representation["_id"]), - "parent": str(representation["parent"]), + "representation": str(repre_doc["_id"]), + "parent": str(repre_doc["parent"]), "members": members, } diff --git a/client/ayon_core/hosts/blender/plugins/load/load_blendscene.py b/client/ayon_core/hosts/blender/plugins/load/load_blendscene.py index ed9dcdeb09..52ecdd6a0a 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_blendscene.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_blendscene.py @@ -133,13 +133,14 @@ class BlendSceneLoader(plugin.AssetLoader): self[:] = objects return objects - def exec_update(self, container: Dict, representation: Dict): + def exec_update(self, container: Dict, context: Dict): """ Update the loaded asset. """ + repre_doc = context["representation"] group_name = container["objectName"] asset_group = bpy.data.collections.get(group_name) - libpath = Path(get_representation_path(representation)).as_posix() + libpath = Path(get_representation_path(repre_doc)).as_posix() assert asset_group, ( f"The asset is not loaded: {container['objectName']}" @@ -201,8 +202,8 @@ class BlendSceneLoader(plugin.AssetLoader): new_data = { "libpath": libpath, - "representation": str(representation["_id"]), - "parent": str(representation["parent"]), + "representation": str(repre_doc["_id"]), + "parent": str(repre_doc["parent"]), "members": members, } diff --git a/client/ayon_core/hosts/blender/plugins/load/load_camera_abc.py b/client/ayon_core/hosts/blender/plugins/load/load_camera_abc.py index 65c73b4168..da90f0b1ab 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_camera_abc.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_camera_abc.py @@ -130,7 +130,7 @@ class AbcCameraLoader(plugin.AssetLoader): self[:] = objects return objects - def exec_update(self, container: Dict, representation: Dict): + def exec_update(self, container: Dict, context: Dict): """Update the loaded asset. This will remove all objects of the current collection, load the new @@ -142,15 +142,16 @@ class AbcCameraLoader(plugin.AssetLoader): Warning: No nested collections are supported at the moment! """ + repre_doc = context["representation"] object_name = container["objectName"] asset_group = bpy.data.objects.get(object_name) - libpath = Path(get_representation_path(representation)) + libpath = Path(get_representation_path(repre_doc)) extension = libpath.suffix.lower() self.log.info( "Container: %s\nRepresentation: %s", pformat(container, indent=2), - pformat(representation, indent=2), + pformat(repre_doc, indent=2), ) assert asset_group, ( @@ -185,7 +186,7 @@ class AbcCameraLoader(plugin.AssetLoader): asset_group.matrix_basis = mat metadata["libpath"] = str(libpath) - metadata["representation"] = str(representation["_id"]) + metadata["representation"] = str(repre_doc["_id"]) def exec_remove(self, container: Dict) -> bool: """Remove an existing container from a Blender scene. diff --git a/client/ayon_core/hosts/blender/plugins/load/load_camera_fbx.py b/client/ayon_core/hosts/blender/plugins/load/load_camera_fbx.py index 3e5a4e6e75..2024356e70 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_camera_fbx.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_camera_fbx.py @@ -133,7 +133,7 @@ class FbxCameraLoader(plugin.AssetLoader): self[:] = objects return objects - def exec_update(self, container: Dict, representation: Dict): + def exec_update(self, container: Dict, context: Dict): """Update the loaded asset. This will remove all objects of the current collection, load the new @@ -145,15 +145,16 @@ class FbxCameraLoader(plugin.AssetLoader): Warning: No nested collections are supported at the moment! """ + repre_doc = context["representation"] object_name = container["objectName"] asset_group = bpy.data.objects.get(object_name) - libpath = Path(get_representation_path(representation)) + libpath = Path(get_representation_path(repre_doc)) extension = libpath.suffix.lower() self.log.info( "Container: %s\nRepresentation: %s", pformat(container, indent=2), - pformat(representation, indent=2), + pformat(repre_doc, indent=2), ) assert asset_group, ( @@ -195,7 +196,7 @@ class FbxCameraLoader(plugin.AssetLoader): asset_group.matrix_basis = mat metadata["libpath"] = str(libpath) - metadata["representation"] = str(representation["_id"]) + metadata["representation"] = str(repre_doc["_id"]) def exec_remove(self, container: Dict) -> bool: """Remove an existing container from a Blender scene. diff --git a/client/ayon_core/hosts/blender/plugins/load/load_fbx.py b/client/ayon_core/hosts/blender/plugins/load/load_fbx.py index e9d5522568..7b4acfed9a 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_fbx.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_fbx.py @@ -177,7 +177,7 @@ class FbxModelLoader(plugin.AssetLoader): self[:] = objects return objects - def exec_update(self, container: Dict, representation: Dict): + def exec_update(self, container: Dict, context: Dict): """Update the loaded asset. This will remove all objects of the current collection, load the new @@ -189,15 +189,16 @@ class FbxModelLoader(plugin.AssetLoader): Warning: No nested collections are supported at the moment! """ + repre_doc = context["representation"] object_name = container["objectName"] asset_group = bpy.data.objects.get(object_name) - libpath = Path(get_representation_path(representation)) + libpath = Path(get_representation_path(repre_doc)) extension = libpath.suffix.lower() self.log.info( "Container: %s\nRepresentation: %s", pformat(container, indent=2), - pformat(representation, indent=2), + pformat(repre_doc, indent=2), ) assert asset_group, ( @@ -250,7 +251,7 @@ class FbxModelLoader(plugin.AssetLoader): asset_group.matrix_basis = mat metadata["libpath"] = str(libpath) - metadata["representation"] = str(representation["_id"]) + metadata["representation"] = str(repre_doc["_id"]) def exec_remove(self, container: Dict) -> bool: """Remove an existing container from a Blender scene. diff --git a/client/ayon_core/hosts/blender/plugins/load/load_layout_json.py b/client/ayon_core/hosts/blender/plugins/load/load_layout_json.py index 126291464b..84793775e5 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_layout_json.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_layout_json.py @@ -188,7 +188,7 @@ class JsonLayoutLoader(plugin.AssetLoader): self[:] = asset_group.children return asset_group.children - def exec_update(self, container: Dict, representation: Dict): + def exec_update(self, container: Dict, context: Dict): """Update the loaded asset. This will remove all objects of the current collection, load the new @@ -197,15 +197,16 @@ class JsonLayoutLoader(plugin.AssetLoader): will not be removed, only unlinked. Normally this should not be the case though. """ + repre_doc = context["representation"] object_name = container["objectName"] asset_group = bpy.data.objects.get(object_name) - libpath = Path(get_representation_path(representation)) + libpath = Path(get_representation_path(repre_doc)) extension = libpath.suffix.lower() self.log.info( "Container: %s\nRepresentation: %s", pformat(container, indent=2), - pformat(representation, indent=2), + pformat(repre_doc, indent=2), ) assert asset_group, ( @@ -269,7 +270,7 @@ class JsonLayoutLoader(plugin.AssetLoader): asset_group.matrix_basis = mat metadata["libpath"] = str(libpath) - metadata["representation"] = str(representation["_id"]) + metadata["representation"] = str(repre_doc["_id"]) def exec_remove(self, container: Dict) -> bool: """Remove an existing container from a Blender scene. diff --git a/client/ayon_core/hosts/blender/plugins/load/load_look.py b/client/ayon_core/hosts/blender/plugins/load/load_look.py index 27632f5705..59896e0ae0 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_look.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_look.py @@ -138,15 +138,16 @@ class BlendLookLoader(plugin.AssetLoader): self[:] = nodes return nodes - def update(self, container: Dict, representation: Dict): + def update(self, container: Dict, context: Dict): collection = bpy.data.collections.get(container["objectName"]) - libpath = Path(get_representation_path(representation)) + repre_doc = context["representation"] + libpath = Path(get_representation_path(repre_doc)) extension = libpath.suffix.lower() self.log.info( "Container: %s\nRepresentation: %s", pformat(container, indent=2), - pformat(representation, indent=2), + pformat(repre_doc, indent=2), ) assert collection, ( @@ -201,7 +202,7 @@ class BlendLookLoader(plugin.AssetLoader): collection_metadata["objects"] = objects collection_metadata["materials"] = materials collection_metadata["libpath"] = str(libpath) - collection_metadata["representation"] = str(representation["_id"]) + collection_metadata["representation"] = str(repre_doc["_id"]) def remove(self, container: Dict) -> bool: collection = bpy.data.collections.get(container["objectName"]) diff --git a/client/ayon_core/hosts/flame/plugins/load/load_clip.py b/client/ayon_core/hosts/flame/plugins/load/load_clip.py index 84f63b3177..72a6f2a585 100644 --- a/client/ayon_core/hosts/flame/plugins/load/load_clip.py +++ b/client/ayon_core/hosts/flame/plugins/load/load_clip.py @@ -180,27 +180,27 @@ class LoadClip(opfapi.ClipLoader): # unwrapping segment from input clip pass - # def switch(self, container, representation): - # self.update(container, representation) + # def switch(self, container, context): + # self.update(container, context) - # def update(self, container, representation): + # def update(self, container, context): # """ Updating previously loaded clips # """ - # # load clip to timeline and get main variables + # repre_doc = context['representation'] # name = container['name'] # namespace = container['namespace'] # track_item = phiero.get_track_items( # track_item_name=namespace) # version = io.find_one({ # "type": "version", - # "_id": representation["parent"] + # "_id": repre_doc["parent"] # }) # version_data = version.get("data", {}) # version_name = version.get("name", None) # colorspace = version_data.get("colorspace", None) # object_name = "{}_{}".format(name, namespace) - # file = get_representation_path(representation).replace("\\", "/") + # file = get_representation_path(repre_doc).replace("\\", "/") # clip = track_item.source() # # reconnect media to new path @@ -225,7 +225,7 @@ class LoadClip(opfapi.ClipLoader): # # add variables related to version context # data_imprint.update({ - # "representation": str(representation["_id"]), + # "representation": str(repre_doc["_id"]), # "version": version_name, # "colorspace": colorspace, # "objectName": object_name diff --git a/client/ayon_core/hosts/fusion/plugins/load/load_alembic.py b/client/ayon_core/hosts/fusion/plugins/load/load_alembic.py index 0bc7ffd180..17f043bb34 100644 --- a/client/ayon_core/hosts/fusion/plugins/load/load_alembic.py +++ b/client/ayon_core/hosts/fusion/plugins/load/load_alembic.py @@ -44,23 +44,24 @@ class FusionLoadAlembicMesh(load.LoaderPlugin): context=context, loader=self.__class__.__name__) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) - def update(self, container, representation): + def update(self, container, context): """Update Alembic path""" tool = container["_tool"] assert tool.ID == self.tool_type, f"Must be {self.tool_type}" comp = tool.Comp() - path = get_representation_path(representation) + repre_doc = context["representation"] + path = get_representation_path(repre_doc) with comp_lock_and_undo_chunk(comp, "Update tool"): tool["Filename"] = path # Update the imprinted representation - tool.SetData("avalon.representation", str(representation["_id"])) + tool.SetData("avalon.representation", str(repre_doc["_id"])) def remove(self, container): tool = container["_tool"] diff --git a/client/ayon_core/hosts/fusion/plugins/load/load_fbx.py b/client/ayon_core/hosts/fusion/plugins/load/load_fbx.py index 3751d7cc39..75320431a8 100644 --- a/client/ayon_core/hosts/fusion/plugins/load/load_fbx.py +++ b/client/ayon_core/hosts/fusion/plugins/load/load_fbx.py @@ -59,23 +59,24 @@ class FusionLoadFBXMesh(load.LoaderPlugin): loader=self.__class__.__name__, ) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) - def update(self, container, representation): + def update(self, container, context): """Update path""" tool = container["_tool"] assert tool.ID == self.tool_type, f"Must be {self.tool_type}" comp = tool.Comp() - path = get_representation_path(representation) + repre_doc = context["representation"] + path = get_representation_path(repre_doc) with comp_lock_and_undo_chunk(comp, "Update tool"): tool["ImportFile"] = path # Update the imprinted representation - tool.SetData("avalon.representation", str(representation["_id"])) + tool.SetData("avalon.representation", str(repre_doc["_id"])) def remove(self, container): tool = container["_tool"] diff --git a/client/ayon_core/hosts/fusion/plugins/load/load_sequence.py b/client/ayon_core/hosts/fusion/plugins/load/load_sequence.py index ad737aabed..678da54ad6 100644 --- a/client/ayon_core/hosts/fusion/plugins/load/load_sequence.py +++ b/client/ayon_core/hosts/fusion/plugins/load/load_sequence.py @@ -175,10 +175,10 @@ class FusionLoadSequence(load.LoaderPlugin): loader=self.__class__.__name__, ) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) - def update(self, container, representation): + def update(self, container, context): """Update the Loader's path Fusion automatically tries to reset some variables when changing @@ -224,7 +224,8 @@ class FusionLoadSequence(load.LoaderPlugin): assert tool.ID == "Loader", "Must be Loader" comp = tool.Comp() - context = get_representation_context(representation) + repre_doc = context["representation"] + context = get_representation_context(repre_doc) path = self.filepath_from_context(context) # Get start frame from version data @@ -255,7 +256,7 @@ class FusionLoadSequence(load.LoaderPlugin): ) # Update the imprinted representation - tool.SetData("avalon.representation", str(representation["_id"])) + tool.SetData("avalon.representation", str(repre_doc["_id"])) def remove(self, container): tool = container["_tool"] diff --git a/client/ayon_core/hosts/fusion/plugins/load/load_usd.py b/client/ayon_core/hosts/fusion/plugins/load/load_usd.py index de56d040ac..e315c84713 100644 --- a/client/ayon_core/hosts/fusion/plugins/load/load_usd.py +++ b/client/ayon_core/hosts/fusion/plugins/load/load_usd.py @@ -60,22 +60,23 @@ class FusionLoadUSD(load.LoaderPlugin): context=context, loader=self.__class__.__name__) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) - def update(self, container, representation): + def update(self, container, context): tool = container["_tool"] assert tool.ID == self.tool_type, f"Must be {self.tool_type}" comp = tool.Comp() - path = get_representation_path(representation) + repre_doc = context["representation"] + path = get_representation_path(repre_doc) with comp_lock_and_undo_chunk(comp, "Update tool"): tool["Filename"] = path # Update the imprinted representation - tool.SetData("avalon.representation", str(representation["_id"])) + tool.SetData("avalon.representation", str(repre_doc["_id"])) def remove(self, container): tool = container["_tool"] diff --git a/client/ayon_core/hosts/harmony/api/README.md b/client/ayon_core/hosts/harmony/api/README.md index 5b90d45f98..151b2bce9e 100644 --- a/client/ayon_core/hosts/harmony/api/README.md +++ b/client/ayon_core/hosts/harmony/api/README.md @@ -611,11 +611,12 @@ class ImageSequenceLoader(load.LoaderPlugin): self.__class__.__name__ ) - def update(self, container, representation): + def update(self, container, context): node = container.pop("node") + repre_doc = context["representation"] project_name = get_current_project_name() - version = get_version_by_id(project_name, representation["parent"]) + version = get_version_by_id(project_name, repre_doc["parent"]) files = [] for f in version["data"]["files"]: files.append( @@ -632,7 +633,7 @@ class ImageSequenceLoader(load.LoaderPlugin): ) harmony.imprint( - node, {"representation": str(representation["_id"])} + node, {"representation": str(repre_doc["_id"])} ) def remove(self, container): @@ -648,8 +649,8 @@ class ImageSequenceLoader(load.LoaderPlugin): {"function": func, "args": [node]} ) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) ``` ## Resources diff --git a/client/ayon_core/hosts/harmony/plugins/load/load_audio.py b/client/ayon_core/hosts/harmony/plugins/load/load_audio.py index 1017d6c2a2..b73c82197a 100644 --- a/client/ayon_core/hosts/harmony/plugins/load/load_audio.py +++ b/client/ayon_core/hosts/harmony/plugins/load/load_audio.py @@ -55,7 +55,7 @@ class ImportAudioLoader(load.LoaderPlugin): self.__class__.__name__ ) - def update(self, container, representation): + def update(self, container, context): pass def remove(self, container): diff --git a/client/ayon_core/hosts/harmony/plugins/load/load_background.py b/client/ayon_core/hosts/harmony/plugins/load/load_background.py index cc664bb24f..bf454a9ec7 100644 --- a/client/ayon_core/hosts/harmony/plugins/load/load_background.py +++ b/client/ayon_core/hosts/harmony/plugins/load/load_background.py @@ -280,8 +280,9 @@ class BackgroundLoader(load.LoaderPlugin): nodes=container_nodes ) - def update(self, container, representation): - path = get_representation_path(representation) + def update(self, container, context): + repre_doc = context["representation"] + path = get_representation_path(repre_doc) with open(path) as json_file: data = json.load(json_file) @@ -301,7 +302,7 @@ class BackgroundLoader(load.LoaderPlugin): print(container) - is_latest = is_representation_from_latest(representation) + is_latest = is_representation_from_latest(repre_doc) for layer in sorted(layers): file_to_import = [ os.path.join(bg_folder, layer).replace("\\", "/") @@ -351,8 +352,11 @@ class BackgroundLoader(load.LoaderPlugin): harmony.send({"function": func, "args": [node, "red"]}) harmony.imprint( - container['name'], {"representation": str(representation["_id"]), - "nodes": container['nodes']} + container['name'], + { + "representation": str(repre_doc["_id"]), + "nodes": container["nodes"] + } ) def remove(self, container): @@ -369,5 +373,5 @@ class BackgroundLoader(load.LoaderPlugin): ) harmony.imprint(container['name'], {}, remove=True) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) diff --git a/client/ayon_core/hosts/harmony/plugins/load/load_imagesequence.py b/client/ayon_core/hosts/harmony/plugins/load/load_imagesequence.py index db67ff1123..60b90fe42d 100644 --- a/client/ayon_core/hosts/harmony/plugins/load/load_imagesequence.py +++ b/client/ayon_core/hosts/harmony/plugins/load/load_imagesequence.py @@ -72,18 +72,19 @@ class ImageSequenceLoader(load.LoaderPlugin): nodes=[read_node] ) - def update(self, container, representation): + def update(self, container, context): """Update loaded containers. Args: container (dict): Container data. - representation (dict): Representation data. + context (dict): Representation context data. """ self_name = self.__class__.__name__ node = container.get("nodes").pop() - path = get_representation_path(representation) + repre_doc = context["representation"] + path = get_representation_path(repre_doc) collections, remainder = clique.assemble( os.listdir(os.path.dirname(path)) ) @@ -110,7 +111,7 @@ class ImageSequenceLoader(load.LoaderPlugin): ) # Colour node. - if is_representation_from_latest(representation): + if is_representation_from_latest(repre_doc): harmony.send( { "function": "PypeHarmony.setColor", @@ -124,7 +125,7 @@ class ImageSequenceLoader(load.LoaderPlugin): }) harmony.imprint( - node, {"representation": str(representation["_id"])} + node, {"representation": str(repre_doc["_id"])} ) def remove(self, container): @@ -140,6 +141,6 @@ class ImageSequenceLoader(load.LoaderPlugin): ) harmony.imprint(node, {}, remove=True) - def switch(self, container, representation): + def switch(self, container, context): """Switch loaded representations.""" - self.update(container, representation) + self.update(container, context) diff --git a/client/ayon_core/hosts/harmony/plugins/load/load_palette.py b/client/ayon_core/hosts/harmony/plugins/load/load_palette.py index 1794ffda5e..f9ce888f93 100644 --- a/client/ayon_core/hosts/harmony/plugins/load/load_palette.py +++ b/client/ayon_core/hosts/harmony/plugins/load/load_palette.py @@ -26,15 +26,17 @@ class ImportPaletteLoader(load.LoaderPlugin): self.__class__.__name__ ) - def load_palette(self, representation): - product_name = representation["context"]["subset"] + def load_palette(self, context): + subset_doc = context["subset"] + repre_doc = context["representation"] + product_name = subset_doc["name"] name = product_name.replace("palette", "") # Overwrite palette on disk. scene_path = harmony.send( {"function": "scene.currentProjectPath"} )["result"] - src = get_representation_path(representation) + src = get_representation_path(repre_doc) dst = os.path.join( scene_path, "palette-library", @@ -59,13 +61,14 @@ class ImportPaletteLoader(load.LoaderPlugin): def remove(self, container): harmony.remove(container["name"]) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) - def update(self, container, representation): + def update(self, container, context): self.remove(container) - name = self.load_palette(representation) + name = self.load_palette(context) - container["representation"] = str(representation["_id"]) + repre_doc = context["representation"] + container["representation"] = str(repre_doc["_id"]) container["name"] = name harmony.imprint(name, container) diff --git a/client/ayon_core/hosts/harmony/plugins/load/load_template.py b/client/ayon_core/hosts/harmony/plugins/load/load_template.py index d26f148c09..e981340c68 100644 --- a/client/ayon_core/hosts/harmony/plugins/load/load_template.py +++ b/client/ayon_core/hosts/harmony/plugins/load/load_template.py @@ -70,19 +70,20 @@ class TemplateLoader(load.LoaderPlugin): self_name ) - def update(self, container, representation): + def update(self, container, context): """Update loaded containers. Args: container (dict): Container data. - representation (dict): Representation data. + context (dict): Representation context data. """ node_name = container["name"] node = harmony.find_node_by_name(node_name, "GROUP") self_name = self.__class__.__name__ - if is_representation_from_latest(representation): + repre_doc = context["representation"] + if is_representation_from_latest(repre_doc): self._set_green(node) else: self._set_red(node) @@ -110,7 +111,7 @@ class TemplateLoader(load.LoaderPlugin): None, container["data"]) harmony.imprint( - node, {"representation": str(representation["_id"])} + node, {"representation": str(repre_doc["_id"])} ) def remove(self, container): @@ -125,9 +126,9 @@ class TemplateLoader(load.LoaderPlugin): {"function": "PypeHarmony.deleteNode", "args": [node]} ) - def switch(self, container, representation): + def switch(self, container, context): """Switch representation containers.""" - self.update(container, representation) + self.update(container, context) def _set_green(self, node): """Set node color to green `rgba(0, 255, 0, 255)`.""" diff --git a/client/ayon_core/hosts/harmony/plugins/load/load_template_workfile.py b/client/ayon_core/hosts/harmony/plugins/load/load_template_workfile.py index 65f4fe6d0a..1b127c5bc4 100644 --- a/client/ayon_core/hosts/harmony/plugins/load/load_template_workfile.py +++ b/client/ayon_core/hosts/harmony/plugins/load/load_template_workfile.py @@ -50,7 +50,7 @@ class ImportTemplateLoader(load.LoaderPlugin): self.__class__.__name__ ) - def update(self, container, representation): + def update(self, container, context): pass def remove(self, container): diff --git a/client/ayon_core/hosts/hiero/api/plugin.py b/client/ayon_core/hosts/hiero/api/plugin.py index 8f1be97680..51601a7ee0 100644 --- a/client/ayon_core/hosts/hiero/api/plugin.py +++ b/client/ayon_core/hosts/hiero/api/plugin.py @@ -363,7 +363,7 @@ class SequenceLoader(LoaderPlugin): ): pass - def update(self, container, representation): + def update(self, container, context): """Update an existing `container` """ pass diff --git a/client/ayon_core/hosts/hiero/plugins/load/load_clip.py b/client/ayon_core/hosts/hiero/plugins/load/load_clip.py index 686edaa01f..e5ef977c42 100644 --- a/client/ayon_core/hosts/hiero/plugins/load/load_clip.py +++ b/client/ayon_core/hosts/hiero/plugins/load/load_clip.py @@ -146,27 +146,25 @@ class LoadClip(phiero.SequenceLoader): self.__class__.__name__, data_imprint) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) - def update(self, container, representation): + def update(self, container, context): """ Updating previously loaded clips """ - + version_doc = context["version"] + repre_doc = context["representation"] # load clip to timeline and get main variables name = container['name'] namespace = container['namespace'] track_item = phiero.get_track_items( track_item_name=namespace).pop() - project_name = get_current_project_name() - version_doc = get_version_by_id(project_name, representation["parent"]) - version_data = version_doc.get("data", {}) version_name = version_doc.get("name", None) colorspace = version_data.get("colorspace", None) object_name = "{}_{}".format(name, namespace) - file = get_representation_path(representation).replace("\\", "/") + file = get_representation_path(repre_doc).replace("\\", "/") clip = track_item.source() # reconnect media to new path @@ -191,7 +189,7 @@ class LoadClip(phiero.SequenceLoader): # add variables related to version context data_imprint.update({ - "representation": str(representation["_id"]), + "representation": str(repre_doc["_id"]), "version": version_name, "colorspace": colorspace, "objectName": object_name diff --git a/client/ayon_core/hosts/hiero/plugins/load/load_effects.py b/client/ayon_core/hosts/hiero/plugins/load/load_effects.py index 809080e87e..9a5e659451 100644 --- a/client/ayon_core/hosts/hiero/plugins/load/load_effects.py +++ b/client/ayon_core/hosts/hiero/plugins/load/load_effects.py @@ -157,19 +157,19 @@ class LoadEffects(load.LoaderPlugin): return loaded - def update(self, container, representation): + def update(self, container, context): """ Updating previously loaded effects """ + version_doc = context["version"] + repre_doc = context["representation"] active_track = container["_item"] - file = get_representation_path(representation).replace("\\", "/") + file = get_representation_path(repre_doc).replace("\\", "/") # get main variables name = container['name'] namespace = container['namespace'] # get timeline in out data - project_name = get_current_project_name() - version_doc = get_version_by_id(project_name, representation["parent"]) version_data = version_doc["data"] clip_in = version_data["clipIn"] clip_out = version_data["clipOut"] @@ -197,7 +197,7 @@ class LoadEffects(load.LoaderPlugin): data_imprint = { "objectName": object_name, "name": name, - "representation": str(representation["_id"]), + "representation": str(repre_doc["_id"]), "children_names": [] } @@ -256,8 +256,8 @@ class LoadEffects(load.LoaderPlugin): else: return input - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def remove(self, container): pass diff --git a/client/ayon_core/hosts/houdini/plugins/load/load_alembic.py b/client/ayon_core/hosts/houdini/plugins/load/load_alembic.py index 6996b0d117..5e138cde83 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_alembic.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_alembic.py @@ -81,8 +81,8 @@ class AbcLoader(load.LoaderPlugin): suffix="", ) - def update(self, container, representation): - + def update(self, container, context): + repre_doc = context["representation"] node = container["node"] try: alembic_node = next( @@ -93,18 +93,18 @@ class AbcLoader(load.LoaderPlugin): return # Update the file path - file_path = get_representation_path(representation) + file_path = get_representation_path(repre_doc) file_path = file_path.replace("\\", "/") alembic_node.setParms({"fileName": file_path}) # Update attribute - node.setParms({"representation": str(representation["_id"])}) + node.setParms({"representation": str(repre_doc["_id"])}) def remove(self, container): node = container["node"] node.destroy() - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) diff --git a/client/ayon_core/hosts/houdini/plugins/load/load_alembic_archive.py b/client/ayon_core/hosts/houdini/plugins/load/load_alembic_archive.py index cfe3b16ebb..0d505806ff 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_alembic_archive.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_alembic_archive.py @@ -55,17 +55,17 @@ class AbcArchiveLoader(load.LoaderPlugin): self.__class__.__name__, suffix="") - def update(self, container, representation): - + def update(self, container, context): + repre_doc = context["representation"] node = container["node"] # Update the file path - file_path = get_representation_path(representation) + file_path = get_representation_path(repre_doc) file_path = file_path.replace("\\", "/") # Update attributes node.setParms({"fileName": file_path, - "representation": str(representation["_id"])}) + "representation": str(repre_doc["_id"])}) # Rebuild node.parm("buildHierarchy").pressButton() @@ -75,5 +75,5 @@ class AbcArchiveLoader(load.LoaderPlugin): node = container["node"] node.destroy() - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) diff --git a/client/ayon_core/hosts/houdini/plugins/load/load_ass.py b/client/ayon_core/hosts/houdini/plugins/load/load_ass.py index 6fbe315adb..396eb3a9f7 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_ass.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_ass.py @@ -48,13 +48,14 @@ class AssLoader(load.LoaderPlugin): suffix="", ) - def update(self, container, representation): + def update(self, container, context): # Update the file path + repre_doc = context["representation"] procedural = container["node"] - procedural.setParms({"ar_filename": self.format_path(representation)}) + procedural.setParms({"ar_filename": self.format_path(repre_doc)}) # Update attribute - procedural.setParms({"representation": str(representation["_id"])}) + procedural.setParms({"representation": str(repre_doc["_id"])}) def remove(self, container): node = container["node"] @@ -86,5 +87,5 @@ class AssLoader(load.LoaderPlugin): return os.path.normpath(path).replace("\\", "/") - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) diff --git a/client/ayon_core/hosts/houdini/plugins/load/load_bgeo.py b/client/ayon_core/hosts/houdini/plugins/load/load_bgeo.py index afcf82562c..4817e40961 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_bgeo.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_bgeo.py @@ -82,8 +82,8 @@ class BgeoLoader(load.LoaderPlugin): return filename - def update(self, container, representation): - + def update(self, container, context): + repre_doc = context["representation"] node = container["node"] try: file_node = next( @@ -94,18 +94,18 @@ class BgeoLoader(load.LoaderPlugin): return # Update the file path - file_path = get_representation_path(representation) - file_path = self.format_path(file_path, representation) + file_path = get_representation_path(repre_doc) + file_path = self.format_path(file_path, repre_doc) file_node.setParms({"file": file_path}) # Update attribute - node.setParms({"representation": str(representation["_id"])}) + node.setParms({"representation": str(repre_doc["_id"])}) def remove(self, container): node = container["node"] node.destroy() - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) diff --git a/client/ayon_core/hosts/houdini/plugins/load/load_camera.py b/client/ayon_core/hosts/houdini/plugins/load/load_camera.py index 11826fb30d..6f6560facc 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_camera.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_camera.py @@ -132,17 +132,17 @@ class CameraLoader(load.LoaderPlugin): self.__class__.__name__, suffix="") - def update(self, container, representation): - + def update(self, container, context): + repre_doc = context["representation"] node = container["node"] # Update the file path - file_path = get_representation_path(representation) + file_path = get_representation_path(repre_doc) file_path = file_path.replace("\\", "/") # Update attributes node.setParms({"fileName": file_path, - "representation": str(representation["_id"])}) + "representation": str(repre_doc["_id"])}) # Store the cam temporarily next to the Alembic Archive # so that we can preserve parm values the user set on it diff --git a/client/ayon_core/hosts/houdini/plugins/load/load_fbx.py b/client/ayon_core/hosts/houdini/plugins/load/load_fbx.py index c750874719..4857dbb900 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_fbx.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_fbx.py @@ -47,8 +47,8 @@ class FbxLoader(load.LoaderPlugin): return containerised_nodes - def update(self, container, representation): - + def update(self, container, context): + repre_doc = context["representation"] node = container["node"] try: file_node = next( @@ -59,21 +59,21 @@ class FbxLoader(load.LoaderPlugin): return # Update the file path from representation - file_path = get_representation_path(representation) + file_path = get_representation_path(repre_doc) file_path = file_path.replace("\\", "/") file_node.setParms({"file": file_path}) # Update attribute - node.setParms({"representation": str(representation["_id"])}) + node.setParms({"representation": str(repre_doc["_id"])}) def remove(self, container): node = container["node"] node.destroy() - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def get_node_name(self, context, name=None, namespace=None): """Define node name.""" diff --git a/client/ayon_core/hosts/houdini/plugins/load/load_hda.py b/client/ayon_core/hosts/houdini/plugins/load/load_hda.py index 288152f2bd..ffe9e55036 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_hda.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_hda.py @@ -48,11 +48,12 @@ class HdaLoader(load.LoaderPlugin): suffix="", ) - def update(self, container, representation): + def update(self, container, context): import hou + repre_doc = context["representation"] hda_node = container["node"] - file_path = get_representation_path(representation) + file_path = get_representation_path(repre_doc) file_path = file_path.replace("\\", "/") hou.hda.installFile(file_path) defs = hda_node.type().allInstalledDefinitions() @@ -60,7 +61,7 @@ class HdaLoader(load.LoaderPlugin): new = def_paths.index(file_path) defs[new].setIsPreferred(True) hda_node.setParms({ - "representation": str(representation["_id"]) + "representation": str(repre_doc["_id"]) }) def remove(self, container): diff --git a/client/ayon_core/hosts/houdini/plugins/load/load_image.py b/client/ayon_core/hosts/houdini/plugins/load/load_image.py index 20fe2f87ca..c89cc3b173 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_image.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_image.py @@ -87,12 +87,12 @@ class ImageLoader(load.LoaderPlugin): return node - def update(self, container, representation): - + def update(self, container, context): + repre_doc = context["representation"] node = container["node"] # Update the file path - file_path = get_representation_path(representation) + file_path = get_representation_path(repre_doc) file_path = file_path.replace("\\", "/") file_path = self._get_file_sequence(file_path) @@ -100,7 +100,7 @@ class ImageLoader(load.LoaderPlugin): node.setParms( { "filename1": file_path, - "representation": str(representation["_id"]), + "representation": str(repre_doc["_id"]), } ) @@ -128,5 +128,5 @@ class ImageLoader(load.LoaderPlugin): fname = ".".join([prefix, "$F{}".format(len(padding)), suffix]) return os.path.join(root, fname).replace("\\", "/") - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) diff --git a/client/ayon_core/hosts/houdini/plugins/load/load_redshift_proxy.py b/client/ayon_core/hosts/houdini/plugins/load/load_redshift_proxy.py index dd6e78b3bc..3e9ce1ff2e 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_redshift_proxy.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_redshift_proxy.py @@ -72,19 +72,19 @@ class RedshiftProxyLoader(load.LoaderPlugin): suffix="", ) - def update(self, container, representation): - + def update(self, container, context): + repre_doc = context["representation"] # Update the file path - file_path = get_representation_path(representation) + file_path = get_representation_path(repre_doc) node = container["node"] node.setParms({ "RS_objprop_proxy_file": self.format_path( - file_path, representation) + file_path, repre_doc) }) # Update attribute - node.setParms({"representation": str(representation["_id"])}) + node.setParms({"representation": str(repre_doc["_id"])}) def remove(self, container): diff --git a/client/ayon_core/hosts/houdini/plugins/load/load_usd_layer.py b/client/ayon_core/hosts/houdini/plugins/load/load_usd_layer.py index 2c37c24884..f4f8a718ad 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_usd_layer.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_usd_layer.py @@ -57,19 +57,19 @@ class USDSublayerLoader(load.LoaderPlugin): return container - def update(self, container, representation): - + def update(self, container, context): + repre_doc = context["representation"] node = container["node"] # Update the file path - file_path = get_representation_path(representation) + file_path = get_representation_path(repre_doc) file_path = file_path.replace("\\", "/") # Update attributes node.setParms( { "filepath1": file_path, - "representation": str(representation["_id"]), + "representation": str(repre_doc["_id"]), } ) @@ -81,5 +81,5 @@ class USDSublayerLoader(load.LoaderPlugin): node = container["node"] node.destroy() - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) diff --git a/client/ayon_core/hosts/houdini/plugins/load/load_usd_reference.py b/client/ayon_core/hosts/houdini/plugins/load/load_usd_reference.py index 9396f00cce..cb83a9a22e 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_usd_reference.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_usd_reference.py @@ -57,19 +57,19 @@ class USDReferenceLoader(load.LoaderPlugin): return container - def update(self, container, representation): - + def update(self, container, context): + repre_doc = context["representation"] node = container["node"] # Update the file path - file_path = get_representation_path(representation) + file_path = get_representation_path(repre_doc) file_path = file_path.replace("\\", "/") # Update attributes node.setParms( { "filepath1": file_path, - "representation": str(representation["_id"]), + "representation": str(repre_doc["_id"]), } ) @@ -81,5 +81,5 @@ class USDReferenceLoader(load.LoaderPlugin): node = container["node"] node.destroy() - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) diff --git a/client/ayon_core/hosts/houdini/plugins/load/load_vdb.py b/client/ayon_core/hosts/houdini/plugins/load/load_vdb.py index c3e374ee8d..ed38e5a5d9 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_vdb.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_vdb.py @@ -79,8 +79,8 @@ class VdbLoader(load.LoaderPlugin): return filename - def update(self, container, representation): - + def update(self, container, context): + repre_doc = context["representation"] node = container["node"] try: file_node = next( @@ -91,18 +91,18 @@ class VdbLoader(load.LoaderPlugin): return # Update the file path - file_path = get_representation_path(representation) - file_path = self.format_path(file_path, representation) + file_path = get_representation_path(repre_doc) + file_path = self.format_path(file_path, repre_doc) file_node.setParms({"file": file_path}) # Update attribute - node.setParms({"representation": str(representation["_id"])}) + node.setParms({"representation": str(repre_doc["_id"])}) def remove(self, container): node = container["node"] node.destroy() - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) diff --git a/client/ayon_core/hosts/max/plugins/load/load_camera_fbx.py b/client/ayon_core/hosts/max/plugins/load/load_camera_fbx.py index 8387d7a837..d56445c695 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_camera_fbx.py +++ b/client/ayon_core/hosts/max/plugins/load/load_camera_fbx.py @@ -51,10 +51,11 @@ class FbxLoader(load.LoaderPlugin): name, selections, context, namespace, loader=self.__class__.__name__) - def update(self, container, representation): + def update(self, container, context): from pymxs import runtime as rt - path = get_representation_path(representation) + repre_doc = context["representation"] + path = get_representation_path(repre_doc) node_name = container["instance_node"] node = rt.getNodeByName(node_name) namespace, _ = get_namespace(node_name) @@ -87,11 +88,11 @@ class FbxLoader(load.LoaderPlugin): update_custom_attribute_data(node, fbx_objects) lib.imprint(container["instance_node"], { - "representation": str(representation["_id"]) + "representation": str(repre_doc["_id"]) }) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def remove(self, container): from pymxs import runtime as rt diff --git a/client/ayon_core/hosts/max/plugins/load/load_max_scene.py b/client/ayon_core/hosts/max/plugins/load/load_max_scene.py index ead77cd2f2..39bb3b568d 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_max_scene.py +++ b/client/ayon_core/hosts/max/plugins/load/load_max_scene.py @@ -48,10 +48,11 @@ class MaxSceneLoader(load.LoaderPlugin): name, max_container, context, namespace, loader=self.__class__.__name__) - def update(self, container, representation): + def update(self, container, context): from pymxs import runtime as rt - path = get_representation_path(representation) + repre_doc = context["representation"] + path = get_representation_path(repre_doc) node_name = container["instance_node"] node = rt.getNodeByName(node_name) namespace, _ = get_namespace(node_name) @@ -86,11 +87,11 @@ class MaxSceneLoader(load.LoaderPlugin): update_custom_attribute_data(node, max_objects) lib.imprint(container["instance_node"], { - "representation": str(representation["_id"]) + "representation": str(repre_doc["_id"]) }) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def remove(self, container): from pymxs import runtime as rt diff --git a/client/ayon_core/hosts/max/plugins/load/load_model.py b/client/ayon_core/hosts/max/plugins/load/load_model.py index cf35e107c2..e0241bdb73 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_model.py +++ b/client/ayon_core/hosts/max/plugins/load/load_model.py @@ -70,10 +70,11 @@ class ModelAbcLoader(load.LoaderPlugin): namespace, loader=self.__class__.__name__ ) - def update(self, container, representation): + def update(self, container, context): from pymxs import runtime as rt - path = get_representation_path(representation) + repre_doc = context["representation"] + path = get_representation_path(repre_doc) node = rt.GetNodeByName(container["instance_node"]) node_list = [n for n in get_previous_loaded_object(node) if rt.ClassOf(n) == rt.AlembicContainer] @@ -90,11 +91,11 @@ class ModelAbcLoader(load.LoaderPlugin): abc_obj.source = path lib.imprint( container["instance_node"], - {"representation": str(representation["_id"])}, + {"representation": str(repre_doc["_id"])}, ) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def remove(self, container): from pymxs import runtime as rt diff --git a/client/ayon_core/hosts/max/plugins/load/load_model_fbx.py b/client/ayon_core/hosts/max/plugins/load/load_model_fbx.py index c0bacca33a..03ba901b32 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_model_fbx.py +++ b/client/ayon_core/hosts/max/plugins/load/load_model_fbx.py @@ -47,10 +47,11 @@ class FbxModelLoader(load.LoaderPlugin): name, selections, context, namespace, loader=self.__class__.__name__) - def update(self, container, representation): + def update(self, container, context): from pymxs import runtime as rt - path = get_representation_path(representation) + repre_doc = context["representation"] + path = get_representation_path(repre_doc) node_name = container["instance_node"] node = rt.getNodeByName(node_name) if not node: @@ -85,11 +86,11 @@ class FbxModelLoader(load.LoaderPlugin): rt.Select(node) update_custom_attribute_data(node, fbx_objects) lib.imprint(container["instance_node"], { - "representation": str(representation["_id"]) + "representation": str(repre_doc["_id"]) }) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def remove(self, container): from pymxs import runtime as rt diff --git a/client/ayon_core/hosts/max/plugins/load/load_model_obj.py b/client/ayon_core/hosts/max/plugins/load/load_model_obj.py index 1023b67f0c..a6c3d2a2fe 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_model_obj.py +++ b/client/ayon_core/hosts/max/plugins/load/load_model_obj.py @@ -47,10 +47,11 @@ class ObjLoader(load.LoaderPlugin): name, selections, context, namespace, loader=self.__class__.__name__) - def update(self, container, representation): + def update(self, container, context): from pymxs import runtime as rt - path = get_representation_path(representation) + repre_doc = context["representation"] + path = get_representation_path(repre_doc) node_name = container["instance_node"] node = rt.getNodeByName(node_name) namespace, _ = get_namespace(node_name) @@ -77,11 +78,11 @@ class ObjLoader(load.LoaderPlugin): rt.Select(node) lib.imprint(node_name, { - "representation": str(representation["_id"]) + "representation": str(repre_doc["_id"]) }) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def remove(self, container): from pymxs import runtime as rt diff --git a/client/ayon_core/hosts/max/plugins/load/load_model_usd.py b/client/ayon_core/hosts/max/plugins/load/load_model_usd.py index 0ec6e5e8e7..6673a2e48b 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_model_usd.py +++ b/client/ayon_core/hosts/max/plugins/load/load_model_usd.py @@ -65,8 +65,9 @@ class ModelUSDLoader(load.LoaderPlugin): name, usd_objects, context, namespace, loader=self.__class__.__name__) - def update(self, container, representation): - path = get_representation_path(representation) + def update(self, container, context): + repre_doc = context["representation"] + path = get_representation_path(repre_doc) node_name = container["instance_node"] node = rt.GetNodeByName(node_name) namespace, name = get_namespace(node_name) @@ -107,11 +108,11 @@ class ModelUSDLoader(load.LoaderPlugin): rt.Select(node) lib.imprint(node_name, { - "representation": str(representation["_id"]) + "representation": str(repre_doc["_id"]) }) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def remove(self, container): from pymxs import runtime as rt diff --git a/client/ayon_core/hosts/max/plugins/load/load_pointcache.py b/client/ayon_core/hosts/max/plugins/load/load_pointcache.py index e9cde4c654..6f79caea42 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_pointcache.py +++ b/client/ayon_core/hosts/max/plugins/load/load_pointcache.py @@ -76,10 +76,11 @@ class AbcLoader(load.LoaderPlugin): namespace, loader=self.__class__.__name__ ) - def update(self, container, representation): + def update(self, container, context): from pymxs import runtime as rt - path = get_representation_path(representation) + repre_doc = context["representation"] + path = get_representation_path(repre_doc) node = rt.GetNodeByName(container["instance_node"]) abc_container = [n for n in get_previous_loaded_object(node) if rt.ClassOf(n) == rt.AlembicContainer] @@ -96,11 +97,11 @@ class AbcLoader(load.LoaderPlugin): abc_obj.source = path lib.imprint( container["instance_node"], - {"representation": str(representation["_id"])}, + {"representation": str(repre_doc["_id"])}, ) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def remove(self, container): from pymxs import runtime as rt diff --git a/client/ayon_core/hosts/max/plugins/load/load_pointcache_ornatrix.py b/client/ayon_core/hosts/max/plugins/load/load_pointcache_ornatrix.py index 338cbfafb9..67d1374266 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_pointcache_ornatrix.py +++ b/client/ayon_core/hosts/max/plugins/load/load_pointcache_ornatrix.py @@ -62,8 +62,9 @@ class OxAbcLoader(load.LoaderPlugin): namespace, loader=self.__class__.__name__ ) - def update(self, container, representation): - path = get_representation_path(representation) + def update(self, container, context): + repre_doc = context["representation"] + path = get_representation_path(repre_doc) node_name = container["instance_node"] namespace, name = get_namespace(node_name) node = rt.getNodeByName(node_name) @@ -98,11 +99,11 @@ class OxAbcLoader(load.LoaderPlugin): update_custom_attribute_data(node, ox_abc_objects) lib.imprint( container["instance_node"], - {"representation": str(representation["_id"])}, + {"representation": str(repre_doc["_id"])}, ) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def remove(self, container): from pymxs import runtime as rt diff --git a/client/ayon_core/hosts/max/plugins/load/load_pointcloud.py b/client/ayon_core/hosts/max/plugins/load/load_pointcloud.py index 7f4fba50b3..894648ff23 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_pointcloud.py +++ b/client/ayon_core/hosts/max/plugins/load/load_pointcloud.py @@ -41,11 +41,12 @@ class PointCloudLoader(load.LoaderPlugin): name, [obj], context, namespace, loader=self.__class__.__name__) - def update(self, container, representation): + def update(self, container, context): """update the container""" from pymxs import runtime as rt - path = get_representation_path(representation) + repre_doc = context["representation"] + path = get_representation_path(repre_doc) node = rt.GetNodeByName(container["instance_node"]) node_list = get_previous_loaded_object(node) update_custom_attribute_data( @@ -55,11 +56,11 @@ class PointCloudLoader(load.LoaderPlugin): for prt in rt.Selection: prt.filename = path lib.imprint(container["instance_node"], { - "representation": str(representation["_id"]) + "representation": str(repre_doc["_id"]) }) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def remove(self, container): """remove the container""" diff --git a/client/ayon_core/hosts/max/plugins/load/load_redshift_proxy.py b/client/ayon_core/hosts/max/plugins/load/load_redshift_proxy.py index 5f2f5ec1ad..7395a6eca5 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_redshift_proxy.py +++ b/client/ayon_core/hosts/max/plugins/load/load_redshift_proxy.py @@ -52,10 +52,11 @@ class RedshiftProxyLoader(load.LoaderPlugin): name, [rs_proxy], context, namespace, loader=self.__class__.__name__) - def update(self, container, representation): + def update(self, container, context): from pymxs import runtime as rt - path = get_representation_path(representation) + repre_doc = context["representation"] + path = get_representation_path(repre_doc) node = rt.getNodeByName(container["instance_node"]) node_list = get_previous_loaded_object(node) rt.Select(node_list) @@ -65,11 +66,11 @@ class RedshiftProxyLoader(load.LoaderPlugin): proxy.file = path lib.imprint(container["instance_node"], { - "representation": str(representation["_id"]) + "representation": str(repre_doc["_id"]) }) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def remove(self, container): from pymxs import runtime as rt diff --git a/client/ayon_core/hosts/max/plugins/load/load_tycache.py b/client/ayon_core/hosts/max/plugins/load/load_tycache.py index 7ae1aea72c..5acc759b4a 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_tycache.py +++ b/client/ayon_core/hosts/max/plugins/load/load_tycache.py @@ -39,11 +39,12 @@ class TyCacheLoader(load.LoaderPlugin): name, [obj], context, namespace, loader=self.__class__.__name__) - def update(self, container, representation): + def update(self, container, context): """update the container""" from pymxs import runtime as rt - path = get_representation_path(representation) + repre_doc = context["representation"] + path = get_representation_path(repre_doc) node = rt.GetNodeByName(container["instance_node"]) node_list = get_previous_loaded_object(node) update_custom_attribute_data(node, node_list) @@ -51,11 +52,11 @@ class TyCacheLoader(load.LoaderPlugin): for tyc in node_list: tyc.filename = path lib.imprint(container["instance_node"], { - "representation": str(representation["_id"]) + "representation": str(repre_doc["_id"]) }) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def remove(self, container): """remove the container""" diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py b/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py new file mode 100644 index 0000000000..109b7fe0b5 --- /dev/null +++ b/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py @@ -0,0 +1,58 @@ + +import pyblish.api +from ayon_core.hosts.max.api.action import SelectInvalidAction +from ayon_core.pipeline.publish import ( + ValidateMeshOrder, + OptionalPyblishPluginMixin, + PublishValidationError +) +from pymxs import runtime as rt + + +class ValidateMeshHasUVs(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): + + """Validate the current mesh has UVs. + + This validator only checks if the mesh has UVs but not + whether all the individual faces of the mesh have UVs. + + It validates whether the current mesh has texture vertices. + If the mesh does not have texture vertices, it does not + have UVs in Max. + + """ + + order = ValidateMeshOrder + hosts = ['max'] + families = ['model'] + label = 'Validate Mesh Has UVs' + actions = [SelectInvalidAction] + optional = True + + @classmethod + def get_invalid(cls, instance): + meshes = [member for member in instance.data["members"] + if rt.isProperty(member, "mesh")] + invalid = [member for member in meshes + if member.mesh.numTVerts == 0] + return invalid + + def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + bullet_point_invalid_statement = "\n".join( + "- {}".format(invalid.name) for invalid + in invalid + ) + report = ( + "Model meshes are required to have UVs.\n\n" + "Meshes detected with invalid or missing UVs:\n" + f"{bullet_point_invalid_statement}\n" + ) + raise PublishValidationError( + report, + description=( + "Model meshes are required to have UVs.\n\n" + "Meshes detected with no texture vertice or missing UVs"), + title="Non-mesh objects found or mesh has missing UVs") diff --git a/client/ayon_core/hosts/maya/api/plugin.py b/client/ayon_core/hosts/maya/api/plugin.py index 1d72353116..1151b0e248 100644 --- a/client/ayon_core/hosts/maya/api/plugin.py +++ b/client/ayon_core/hosts/maya/api/plugin.py @@ -793,14 +793,17 @@ class ReferenceLoader(Loader): """To be implemented by subclass""" raise NotImplementedError("Must be implemented by subclass") - def update(self, container, representation): + def update(self, container, context): from maya import cmds from ayon_core.hosts.maya.api.lib import get_container_members node = container["objectName"] - path = get_representation_path(representation) + project_name = context["project"]["name"] + repre_doc = context["representation"] + + path = get_representation_path(repre_doc) # Get reference node from container members members = get_container_members(node) @@ -813,9 +816,9 @@ class ReferenceLoader(Loader): "abc": "Alembic", "fbx": "FBX", "usd": "USD Import" - }.get(representation["name"]) + }.get(repre_doc["name"]) - assert file_type, "Unsupported representation: %s" % representation + assert file_type, "Unsupported representation: %s" % repre_doc assert os.path.exists(path), "%s does not exist." % path @@ -823,7 +826,7 @@ class ReferenceLoader(Loader): # them to incoming data. alembic_attrs = ["speed", "offset", "cycleType", "time"] alembic_data = {} - if representation["name"] == "abc": + if repre_doc["name"] == "abc": alembic_nodes = cmds.ls( "{}:*".format(namespace), type="AlembicNode" ) @@ -840,10 +843,7 @@ class ReferenceLoader(Loader): self.log.debug("No alembic nodes found in {}".format(members)) try: - path = self.prepare_root_value(path, - representation["context"] - ["project"] - ["name"]) + path = self.prepare_root_value(path, project_name) content = cmds.file(path, loadReference=reference_node, type=file_type, @@ -867,7 +867,7 @@ class ReferenceLoader(Loader): self._organize_containers(content, container["objectName"]) # Reapply alembic settings. - if representation["name"] == "abc" and alembic_data: + if repre_doc["name"] == "abc" and alembic_data: alembic_nodes = cmds.ls( "{}:*".format(namespace), type="AlembicNode" ) @@ -901,7 +901,7 @@ class ReferenceLoader(Loader): # Update metadata cmds.setAttr("{}.representation".format(node), - str(representation["_id"]), + str(repre_doc["_id"]), type="string") # When an animation or pointcache gets connected to an Xgen container, diff --git a/client/ayon_core/hosts/maya/api/setdress.py b/client/ayon_core/hosts/maya/api/setdress.py index 8d09716bf6..913e12fd3f 100644 --- a/client/ayon_core/hosts/maya/api/setdress.py +++ b/client/ayon_core/hosts/maya/api/setdress.py @@ -315,16 +315,27 @@ def update_package_version(container, version): new_representation = get_representation_by_name( project_name, current_representation["name"], new_version["_id"] ) - - update_package(container, new_representation) + # TODO there is 'get_representation_context' to get the context which + # could be possible to use here + new_context = { + "project": { + "name": project_doc["name"], + "code": project_doc["data"].get("code", "") + }, + "asset": asset_doc, + "subset": subset_doc, + "version": version_doc, + "representation": new_representation, + } + update_package(container, new_context) -def update_package(set_container, representation): +def update_package(set_container, context): """Update any matrix changes in the scene based on the new data Args: set_container (dict): container data from `ls()` - representation (dict): the representation document from the database + context (dict): the representation document from the database Returns: None @@ -332,7 +343,8 @@ def update_package(set_container, representation): """ # Load the original package data - project_name = get_current_project_name() + project_name = context["project"]["name"] + repre_doc = context["representation"] current_representation = get_representation_by_id( project_name, set_container["representation"] ) @@ -343,7 +355,7 @@ def update_package(set_container, representation): current_data = json.load(fp) # Load the new package data - new_file = get_representation_path(representation) + new_file = get_representation_path(repre_doc) assert new_file.endswith(".json") with open(new_file, "r") as fp: new_data = json.load(fp) @@ -354,7 +366,7 @@ def update_package(set_container, representation): # TODO: This should be handled by the pipeline itself cmds.setAttr(set_container['objectName'] + ".representation", - str(representation['_id']), type="string") + str(repre_doc['_id']), type="string") def update_scene(set_container, containers, current_data, new_data, new_file): diff --git a/client/ayon_core/hosts/maya/plugins/load/load_arnold_standin.py b/client/ayon_core/hosts/maya/plugins/load/load_arnold_standin.py index 312cc3bd6b..8fd3ad4979 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_arnold_standin.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_arnold_standin.py @@ -177,7 +177,7 @@ class ArnoldStandinLoader(load.LoaderPlugin): return proxy_path, string_replace_operator - def update(self, container, representation): + def update(self, container, context): # Update the standin members = cmds.sets(container['objectName'], query=True) for member in members: @@ -190,7 +190,8 @@ class ArnoldStandinLoader(load.LoaderPlugin): if cmds.nodeType(shapes[0]) == "aiStandIn": standin = shapes[0] - path = get_representation_path(representation) + repre_doc = context["representation"] + path = get_representation_path(repre_doc) proxy_basename, proxy_path = self._get_proxy_path(path) # Whether there is proxy or so, we still update the string operator. @@ -216,12 +217,12 @@ class ArnoldStandinLoader(load.LoaderPlugin): cmds.setAttr( container["objectName"] + ".representation", - str(representation["_id"]), + str(repre_doc["_id"]), type="string" ) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def remove(self, container): members = cmds.sets(container['objectName'], query=True) diff --git a/client/ayon_core/hosts/maya/plugins/load/load_assembly.py b/client/ayon_core/hosts/maya/plugins/load/load_assembly.py index e119dfe1c3..1f06655dad 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_assembly.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_assembly.py @@ -49,9 +49,9 @@ class AssemblyLoader(load.LoaderPlugin): context=context, loader=self.__class__.__name__) - def update(self, container, representation): + def update(self, container, context): - return setdress.update_package(container, representation) + return setdress.update_package(container, context) def remove(self, container): """Remove all sub containers""" diff --git a/client/ayon_core/hosts/maya/plugins/load/load_audio.py b/client/ayon_core/hosts/maya/plugins/load/load_audio.py index deeeac66f2..df811a585c 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_audio.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_audio.py @@ -45,7 +45,8 @@ class AudioLoader(load.LoaderPlugin): loader=self.__class__.__name__ ) - def update(self, container, representation): + def update(self, container, context): + repre_doc = context["representation"] members = get_container_members(container) audio_nodes = cmds.ls(members, type="audio") @@ -60,7 +61,7 @@ class AudioLoader(load.LoaderPlugin): ) activate_sound = current_sound == audio_node - path = get_representation_path(representation) + path = get_representation_path(repre_doc) cmds.sound( audio_node, @@ -93,12 +94,12 @@ class AudioLoader(load.LoaderPlugin): cmds.setAttr( container["objectName"] + ".representation", - str(representation["_id"]), + str(repre_doc["_id"]), type="string" ) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def remove(self, container): members = cmds.sets(container['objectName'], query=True) diff --git a/client/ayon_core/hosts/maya/plugins/load/load_gpucache.py b/client/ayon_core/hosts/maya/plugins/load/load_gpucache.py index 38f9d1b7cb..9453c9c9c6 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_gpucache.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_gpucache.py @@ -74,8 +74,9 @@ class GpuCacheLoader(load.LoaderPlugin): context=context, loader=self.__class__.__name__) - def update(self, container, representation): - path = get_representation_path(representation) + def update(self, container, context): + repre_doc = context["representation"] + path = get_representation_path(repre_doc) # Update the cache members = cmds.sets(container['objectName'], query=True) @@ -87,11 +88,11 @@ class GpuCacheLoader(load.LoaderPlugin): cmds.setAttr(cache + ".cacheFileName", path, type="string") cmds.setAttr(container["objectName"] + ".representation", - str(representation["_id"]), + str(repre_doc["_id"]), type="string") - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def remove(self, container): members = cmds.sets(container['objectName'], query=True) diff --git a/client/ayon_core/hosts/maya/plugins/load/load_image.py b/client/ayon_core/hosts/maya/plugins/load/load_image.py index aedeb63e3d..7b324986f0 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_image.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_image.py @@ -146,23 +146,23 @@ class FileNodeLoader(load.LoaderPlugin): loader=self.__class__.__name__ ) - def update(self, container, representation): + def update(self, container, context): + repre_doc = context["representation"] members = cmds.sets(container['objectName'], query=True) file_node = cmds.ls(members, type="file")[0] - context = get_representation_context(representation) self._apply_representation_context(context, file_node) # Update representation cmds.setAttr( container["objectName"] + ".representation", - str(representation["_id"]), + str(repre_doc["_id"]), type="string" ) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def remove(self, container): members = cmds.sets(container['objectName'], query=True) diff --git a/client/ayon_core/hosts/maya/plugins/load/load_image_plane.py b/client/ayon_core/hosts/maya/plugins/load/load_image_plane.py index a685a4a41c..2366f6edd7 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_image_plane.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_image_plane.py @@ -205,32 +205,24 @@ class ImagePlaneLoader(load.LoaderPlugin): loader=self.__class__.__name__ ) - def update(self, container, representation): + def update(self, container, context): + asset_doc = context["asset"] + repre_doc = context["representation"] members = get_container_members(container) image_planes = cmds.ls(members, type="imagePlane") assert image_planes, "Image plane not found." image_plane_shape = image_planes[0] - path = get_representation_path(representation) + path = get_representation_path(repre_doc) cmds.setAttr("{}.imageName".format(image_plane_shape), path, type="string") cmds.setAttr("{}.representation".format(container["objectName"]), - str(representation["_id"]), + str(repre_doc["_id"]), type="string") # Set frame range. - project_name = get_current_project_name() - version = get_version_by_id( - project_name, representation["parent"], fields=["parent"] - ) - subset_doc = get_subset_by_id( - project_name, version["parent"], fields=["parent"] - ) - asset_doc = get_asset_by_id( - project_name, subset_doc["parent"], fields=["parent"] - ) start_frame = asset_doc["data"]["frameStart"] end_frame = asset_doc["data"]["frameEnd"] @@ -243,8 +235,8 @@ class ImagePlaneLoader(load.LoaderPlugin): plug = "{}.{}".format(image_plane_shape, attr) cmds.setAttr(plug, value) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def remove(self, container): members = cmds.sets(container['objectName'], query=True) diff --git a/client/ayon_core/hosts/maya/plugins/load/load_look.py b/client/ayon_core/hosts/maya/plugins/load/load_look.py index ba5891469d..fb5be14aa1 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_look.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_look.py @@ -43,10 +43,10 @@ class LookLoader(ayon_core.hosts.maya.api.plugin.ReferenceLoader): self[:] = nodes - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) - def update(self, container, representation): + def update(self, container, context): """ Called by Scene Inventory when look should be updated to current version. @@ -56,7 +56,7 @@ class LookLoader(ayon_core.hosts.maya.api.plugin.ReferenceLoader): Args: container: object that has look to be updated - representation: (dict): relationship data to get proper + context: (dict): relationship data to get proper representation from DB and persisted data in .json Returns: @@ -72,15 +72,16 @@ class LookLoader(ayon_core.hosts.maya.api.plugin.ReferenceLoader): orig_nodes = set(self._get_nodes_with_shader(shader_nodes)) # Trigger the regular reference update on the ReferenceLoader - super(LookLoader, self).update(container, representation) + super(LookLoader, self).update(container, context) # get new applied shaders and nodes from new version shader_nodes = cmds.ls(members, type='shadingEngine') nodes = set(self._get_nodes_with_shader(shader_nodes)) + version_doc = context["version"] project_name = get_current_project_name() json_representation = get_representation_by_name( - project_name, "json", representation["parent"] + project_name, "json", version_doc["_id"] ) # Load relationships diff --git a/client/ayon_core/hosts/maya/plugins/load/load_maya_usd.py b/client/ayon_core/hosts/maya/plugins/load/load_maya_usd.py index c2bea1501c..cb9fde7b33 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_maya_usd.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_maya_usd.py @@ -69,7 +69,7 @@ class MayaUsdLoader(load.LoaderPlugin): context=context, loader=self.__class__.__name__) - def update(self, container, representation): + def update(self, container, context): # type: (dict, dict) -> None """Update container with specified representation.""" node = container['objectName'] @@ -78,16 +78,17 @@ class MayaUsdLoader(load.LoaderPlugin): members = cmds.sets(node, query=True) or [] shapes = cmds.ls(members, type="mayaUsdProxyShape") - path = get_representation_path(representation) + repre_doc = context["representation"] + path = get_representation_path(repre_doc) for shape in shapes: cmds.setAttr("{}.filePath".format(shape), path, type="string") cmds.setAttr("{}.representation".format(node), - str(representation["_id"]), + str(repre_doc["_id"]), type="string") - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def remove(self, container): # type: (dict) -> None diff --git a/client/ayon_core/hosts/maya/plugins/load/load_multiverse_usd.py b/client/ayon_core/hosts/maya/plugins/load/load_multiverse_usd.py index a9ba2b8773..64e6048c31 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_multiverse_usd.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_multiverse_usd.py @@ -60,7 +60,7 @@ class MultiverseUsdLoader(load.LoaderPlugin): context=context, loader=self.__class__.__name__) - def update(self, container, representation): + def update(self, container, context): # type: (dict, dict) -> None """Update container with specified representation.""" node = container['objectName'] @@ -70,7 +70,9 @@ class MultiverseUsdLoader(load.LoaderPlugin): shapes = cmds.ls(members, type="mvUsdCompoundShape") assert shapes, "Cannot find mvUsdCompoundShape in container" - project_name = representation["context"]["project"]["name"] + project_name = context["project"]["name"] + repre_doc = context["representation"] + path = get_representation_path(repre_doc) prev_representation_id = cmds.getAttr("{}.representation".format(node)) prev_representation = get_representation_by_id(project_name, prev_representation_id) @@ -89,18 +91,17 @@ class MultiverseUsdLoader(load.LoaderPlugin): "Couldn't find matching path (or too many)" prev_path_idx = asset_paths.index(prev_path) - path = get_representation_path(representation) asset_paths[prev_path_idx] = path multiverse.SetUsdCompoundAssetPaths(shape, asset_paths) cmds.setAttr("{}.representation".format(node), - str(representation["_id"]), + str(repre_doc["_id"]), type="string") mel.eval('refreshEditorTemplates;') - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def remove(self, container): # type: (dict) -> None diff --git a/client/ayon_core/hosts/maya/plugins/load/load_multiverse_usd_over.py b/client/ayon_core/hosts/maya/plugins/load/load_multiverse_usd_over.py index d448dc74a8..6de03fe306 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_multiverse_usd_over.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_multiverse_usd_over.py @@ -71,7 +71,7 @@ class MultiverseUsdOverLoader(load.LoaderPlugin): return container - def update(self, container, representation): + def update(self, container, context): # type: (dict, dict) -> None """Update container with specified representation.""" @@ -88,13 +88,14 @@ class MultiverseUsdOverLoader(load.LoaderPlugin): mvShape = container['mvUsdCompoundShape'] assert mvShape, "Missing mv source" - project_name = representation["context"]["project"]["name"] + project_name = context["project"]["name"] + repre_doc = context["representation"] prev_representation_id = cmds.getAttr("{}.representation".format(node)) prev_representation = get_representation_by_id(project_name, prev_representation_id) prev_path = os.path.normpath(prev_representation["data"]["path"]) - path = get_representation_path(representation) + path = get_representation_path(repre_doc) for shape in shapes: asset_paths = multiverse.GetUsdCompoundAssetPaths(shape) @@ -107,12 +108,12 @@ class MultiverseUsdOverLoader(load.LoaderPlugin): multiverse.SetUsdCompoundAssetPaths(shape, asset_paths) cmds.setAttr("{}.representation".format(node), - str(representation["_id"]), + str(repre_doc["_id"]), type="string") mel.eval('refreshEditorTemplates;') - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def remove(self, container): # type: (dict) -> None diff --git a/client/ayon_core/hosts/maya/plugins/load/load_redshift_proxy.py b/client/ayon_core/hosts/maya/plugins/load/load_redshift_proxy.py index eb7e0957ac..feb63ae4be 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_redshift_proxy.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_redshift_proxy.py @@ -75,7 +75,7 @@ class RedshiftProxyLoader(load.LoaderPlugin): context=context, loader=self.__class__.__name__) - def update(self, container, representation): + def update(self, container, context): node = container['objectName'] assert cmds.objExists(node), "Missing container" @@ -83,8 +83,8 @@ class RedshiftProxyLoader(load.LoaderPlugin): members = cmds.sets(node, query=True) or [] rs_meshes = cmds.ls(members, type="RedshiftProxyMesh") assert rs_meshes, "Cannot find RedshiftProxyMesh in container" - - filename = get_representation_path(representation) + repre_doc = context["representation"] + filename = get_representation_path(repre_doc) for rs_mesh in rs_meshes: cmds.setAttr("{}.fileName".format(rs_mesh), @@ -93,7 +93,7 @@ class RedshiftProxyLoader(load.LoaderPlugin): # Update metadata cmds.setAttr("{}.representation".format(node), - str(representation["_id"]), + str(repre_doc["_id"]), type="string") def remove(self, container): @@ -113,8 +113,8 @@ class RedshiftProxyLoader(load.LoaderPlugin): self.log.warning("Namespace not deleted because it " "still has members: %s", namespace) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def create_rs_proxy(self, name, path): """Creates Redshift Proxies showing a proxy object. diff --git a/client/ayon_core/hosts/maya/plugins/load/load_reference.py b/client/ayon_core/hosts/maya/plugins/load/load_reference.py index eee3d92641..75f5cee5a5 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_reference.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_reference.py @@ -231,12 +231,12 @@ class ReferenceLoader(plugin.ReferenceLoader): *options["translate"]) return new_nodes - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) - def update(self, container, representation): + def update(self, container, context): with preserve_modelpanel_cameras(container, log=self.log): - super(ReferenceLoader, self).update(container, representation) + super(ReferenceLoader, self).update(container, context) # We also want to lock camera transforms on any new cameras in the # reference or for a camera which might have changed names. diff --git a/client/ayon_core/hosts/maya/plugins/load/load_rendersetup.py b/client/ayon_core/hosts/maya/plugins/load/load_rendersetup.py index e77e270663..58f161afc1 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_rendersetup.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_rendersetup.py @@ -84,14 +84,15 @@ class RenderSetupLoader(load.LoaderPlugin): # Already implicitly deleted by Maya upon removing reference pass - def update(self, container, representation): + def update(self, container, context): """Update RenderSetup setting by overwriting existing settings.""" lib.show_message( "Render setup update", "Render setup setting will be overwritten by new version. All " "setting specified by user not included in loaded version " "will be lost.") - path = get_representation_path(representation) + repre_doc = context["representation"] + path = get_representation_path(repre_doc) with open(path, "r") as file: try: renderSetup.instance().decode( @@ -103,10 +104,10 @@ class RenderSetupLoader(load.LoaderPlugin): # Update metadata node = container["objectName"] cmds.setAttr("{}.representation".format(node), - str(representation["_id"]), + str(repre_doc["_id"]), type="string") self.log.info("... updated") - def switch(self, container, representation): + def switch(self, container, context): """Switch representations.""" - self.update(container, representation) + self.update(container, context) diff --git a/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_arnold.py b/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_arnold.py index 80a7fa6006..3eec09eb7d 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_arnold.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_arnold.py @@ -81,11 +81,13 @@ class LoadVDBtoArnold(load.LoaderPlugin): context=context, loader=self.__class__.__name__) - def update(self, container, representation): + def update(self, container, context): from maya import cmds - path = get_representation_path(representation) + repre_doc = context["representation"] + + path = get_representation_path(repre_doc) # Find VRayVolumeGrid members = cmds.sets(container['objectName'], query=True) @@ -93,15 +95,15 @@ class LoadVDBtoArnold(load.LoaderPlugin): assert len(grid_nodes) == 1, "This is a bug" # Update the VRayVolumeGrid - self._set_path(grid_nodes[0], path=path, representation=representation) + self._set_path(grid_nodes[0], path=path, representation=repre_doc) # Update container representation cmds.setAttr(container["objectName"] + ".representation", - str(representation["_id"]), + str(repre_doc["_id"]), type="string") - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def remove(self, container): diff --git a/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_redshift.py b/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_redshift.py index 65bef51ec6..f58d9e5565 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_redshift.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_redshift.py @@ -95,10 +95,11 @@ class LoadVDBtoRedShift(load.LoaderPlugin): context=context, loader=self.__class__.__name__) - def update(self, container, representation): + def update(self, container, context): from maya import cmds - path = get_representation_path(representation) + repre_doc = context["representation"] + path = get_representation_path(repre_doc) # Find VRayVolumeGrid members = cmds.sets(container['objectName'], query=True) @@ -106,11 +107,11 @@ class LoadVDBtoRedShift(load.LoaderPlugin): assert len(grid_nodes) == 1, "This is a bug" # Update the VRayVolumeGrid - self._set_path(grid_nodes[0], path=path, representation=representation) + self._set_path(grid_nodes[0], path=path, representation=repre_doc) # Update container representation cmds.setAttr(container["objectName"] + ".representation", - str(representation["_id"]), + str(repre_doc["_id"]), type="string") def remove(self, container): @@ -129,8 +130,8 @@ class LoadVDBtoRedShift(load.LoaderPlugin): except RuntimeError: pass - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) @staticmethod def _set_path(grid_node, diff --git a/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_vray.py b/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_vray.py index 4b18e60c9d..6e82bbd5e2 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_vray.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_vray.py @@ -254,9 +254,10 @@ class LoadVDBtoVRay(load.LoaderPlugin): restored_mapping, type="string") - def update(self, container, representation): + def update(self, container, context): + repre_doc = context["representation"] - path = get_representation_path(representation) + path = get_representation_path(repre_doc) # Find VRayVolumeGrid members = cmds.sets(container['objectName'], query=True) @@ -269,11 +270,11 @@ class LoadVDBtoVRay(load.LoaderPlugin): # Update container representation cmds.setAttr(container["objectName"] + ".representation", - str(representation["_id"]), + str(repre_doc["_id"]), type="string") - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def remove(self, container): diff --git a/client/ayon_core/hosts/maya/plugins/load/load_vrayproxy.py b/client/ayon_core/hosts/maya/plugins/load/load_vrayproxy.py index d4aad10762..86ea8004ba 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_vrayproxy.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_vrayproxy.py @@ -96,7 +96,7 @@ class VRayProxyLoader(load.LoaderPlugin): context=context, loader=self.__class__.__name__) - def update(self, container, representation): + def update(self, container, context): # type: (dict, dict) -> None """Update container with specified representation.""" node = container['objectName'] @@ -107,9 +107,10 @@ class VRayProxyLoader(load.LoaderPlugin): assert vraymeshes, "Cannot find VRayMesh in container" # get all representations for this version + repre_doc = context["representation"] filename = ( - self._get_abc(representation["parent"]) - or get_representation_path(representation) + self._get_abc(repre_doc["parent"]) + or get_representation_path(repre_doc) ) for vray_mesh in vraymeshes: @@ -119,7 +120,7 @@ class VRayProxyLoader(load.LoaderPlugin): # Update metadata cmds.setAttr("{}.representation".format(node), - str(representation["_id"]), + str(repre_doc["_id"]), type="string") def remove(self, container): @@ -140,10 +141,10 @@ class VRayProxyLoader(load.LoaderPlugin): self.log.warning("Namespace not deleted because it " "still has members: %s", namespace) - def switch(self, container, representation): + def switch(self, container, context): # type: (dict, dict) -> None """Switch loaded representation.""" - self.update(container, representation) + self.update(container, context) def create_vray_proxy(self, name, filename): # type: (str, str) -> (list, str) diff --git a/client/ayon_core/hosts/maya/plugins/load/load_vrayscene.py b/client/ayon_core/hosts/maya/plugins/load/load_vrayscene.py index 04ccf57808..a5bfd9dcc3 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_vrayscene.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_vrayscene.py @@ -71,7 +71,7 @@ class VRaySceneLoader(load.LoaderPlugin): context=context, loader=self.__class__.__name__) - def update(self, container, representation): + def update(self, container, context): node = container['objectName'] assert cmds.objExists(node), "Missing container" @@ -80,7 +80,8 @@ class VRaySceneLoader(load.LoaderPlugin): vraymeshes = cmds.ls(members, type="VRayScene") assert vraymeshes, "Cannot find VRayScene in container" - filename = get_representation_path(representation) + repre_doc = context["representation"] + filename = get_representation_path(repre_doc) for vray_mesh in vraymeshes: cmds.setAttr("{}.FilePath".format(vray_mesh), @@ -89,7 +90,7 @@ class VRaySceneLoader(load.LoaderPlugin): # Update metadata cmds.setAttr("{}.representation".format(node), - str(representation["_id"]), + str(repre_doc["_id"]), type="string") def remove(self, container): @@ -109,8 +110,8 @@ class VRaySceneLoader(load.LoaderPlugin): self.log.warning("Namespace not deleted because it " "still has members: %s", namespace) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def create_vray_scene(self, name, filename): """Re-create the structure created by VRay to support vrscenes diff --git a/client/ayon_core/hosts/maya/plugins/load/load_xgen.py b/client/ayon_core/hosts/maya/plugins/load/load_xgen.py index 4c38835350..fdac62a250 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_xgen.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_xgen.py @@ -113,7 +113,7 @@ class XgenLoader(ayon_core.hosts.maya.api.plugin.ReferenceLoader): ) cmds.setAttr("{}.xgExportAsDelta".format(xgen_palette), True) - def update(self, container, representation): + def update(self, container, context): """Workflow for updating Xgen. - Export changes to delta file. @@ -147,7 +147,8 @@ class XgenLoader(ayon_core.hosts.maya.api.plugin.ReferenceLoader): self.set_palette_attributes(xgen_palette, xgen_file, xgd_file) - maya_file = get_representation_path(representation) + repre_doc = context["representation"] + maya_file = get_representation_path(repre_doc) _, extension = os.path.splitext(maya_file) new_xgen_file = maya_file.replace(extension, ".xgen") data_path = "" @@ -173,7 +174,7 @@ class XgenLoader(ayon_core.hosts.maya.api.plugin.ReferenceLoader): "{}.xgExportAsDelta".format(xgen_palette): False } with attribute_values(attribute_data): - super().update(container, representation) + super().update(container, context) xgenm.applyDelta(xgen_palette.replace("|", ""), xgd_file) diff --git a/client/ayon_core/hosts/maya/plugins/load/load_yeti_cache.py b/client/ayon_core/hosts/maya/plugins/load/load_yeti_cache.py index 372727d400..1c6423a8d7 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_yeti_cache.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_yeti_cache.py @@ -122,12 +122,12 @@ class YetiCacheLoader(load.LoaderPlugin): cmds.namespace(removeNamespace=namespace, deleteNamespaceContent=True) - def update(self, container, representation): - + def update(self, container, context): + repre_doc = context["representation"] namespace = container["namespace"] container_node = container["objectName"] - path = get_representation_path(representation) + path = get_representation_path(repre_doc) settings = self.read_settings(path) # Collect scene information of asset @@ -216,11 +216,11 @@ class YetiCacheLoader(load.LoaderPlugin): set_attribute(attr, value, yeti_node) cmds.setAttr("{}.representation".format(container_node), - str(representation["_id"]), + str(repre_doc["_id"]), typ="string") - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) # helper functions def create_namespace(self, asset): diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_backdrop.py b/client/ayon_core/hosts/nuke/plugins/load/load_backdrop.py index ed512c86ab..642e20c979 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_backdrop.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_backdrop.py @@ -178,7 +178,7 @@ class LoadBackdropNodes(load.LoaderPlugin): loader=self.__class__.__name__, data=data_imprint) - def update(self, container, representation): + def update(self, container, context): """Update the Loader's path Nuke automatically tries to reset some variables when changing @@ -189,13 +189,14 @@ class LoadBackdropNodes(load.LoaderPlugin): # get main variables # Get version from io - project_name = get_current_project_name() - version_doc = get_version_by_id(project_name, representation["parent"]) + project_name = context["project"]["name"] + version_doc = context["version"] + repre_doc = context["representation"] # get corresponding node GN = container["node"] - file = get_representation_path(representation).replace("\\", "/") + file = get_representation_path(repre_doc).replace("\\", "/") name = container['name'] version_data = version_doc.get("data", {}) @@ -207,7 +208,7 @@ class LoadBackdropNodes(load.LoaderPlugin): add_keys = ["source", "author", "fps"] data_imprint = { - "representation": str(representation["_id"]), + "representation": str(repre_doc["_id"]), "version": vname, "colorspaceInput": colorspace, } @@ -248,8 +249,8 @@ class LoadBackdropNodes(load.LoaderPlugin): return update_container(GN, data_imprint) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def remove(self, container): node = container["node"] diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_camera_abc.py b/client/ayon_core/hosts/nuke/plugins/load/load_camera_abc.py index 919a3beb06..e3511a4e8b 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_camera_abc.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_camera_abc.py @@ -92,7 +92,7 @@ class AlembicCameraLoader(load.LoaderPlugin): loader=self.__class__.__name__, data=data_imprint) - def update(self, container, representation): + def update(self, container, context): """ Called by Scene Inventory when look should be updated to current version. @@ -109,8 +109,8 @@ class AlembicCameraLoader(load.LoaderPlugin): None """ # Get version from io - project_name = get_current_project_name() - version_doc = get_version_by_id(project_name, representation["parent"]) + version_doc = context["version"] + repre_doc = context["representation"] # get main variables version_data = version_doc.get("data", {}) @@ -124,7 +124,7 @@ class AlembicCameraLoader(load.LoaderPlugin): add_keys = ["source", "author", "fps"] data_imprint = { - "representation": str(representation["_id"]), + "representation": str(repre_doc["_id"]), "frameStart": first, "frameEnd": last, "version": vname @@ -134,7 +134,7 @@ class AlembicCameraLoader(load.LoaderPlugin): data_imprint.update({k: version_data[k]}) # getting file path - file = get_representation_path(representation).replace("\\", "/") + file = get_representation_path(repre_doc).replace("\\", "/") with maintained_selection(): camera_node = container["node"] @@ -191,8 +191,8 @@ class AlembicCameraLoader(load.LoaderPlugin): color_value = "0xd88467ff" node["tile_color"].setValue(int(color_value, 16)) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def remove(self, container): node = container["node"] diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_clip.py b/client/ayon_core/hosts/nuke/plugins/load/load_clip.py index 31b523fbc8..e9e71baa76 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_clip.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_clip.py @@ -209,8 +209,8 @@ class LoadClip(plugin.NukeLoader): return container - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def _representation_with_hash_in_frame(self, representation): """Convert frame key value to padded hash @@ -241,7 +241,7 @@ class LoadClip(plugin.NukeLoader): representation["context"]["frame"] = hashed_frame return representation - def update(self, container, representation): + def update(self, container, context): """Update the Loader's path Nuke automatically tries to reset some variables when changing @@ -250,16 +250,18 @@ class LoadClip(plugin.NukeLoader): """ - is_sequence = len(representation["files"]) > 1 + repre_doc = context["representation"] + + is_sequence = len(repre_doc["files"]) > 1 read_node = container["node"] if is_sequence: - representation = self._representation_with_hash_in_frame( - representation + repre_doc = self._representation_with_hash_in_frame( + repre_doc ) - filepath = get_representation_path(representation).replace("\\", "/") + filepath = get_representation_path(repre_doc).replace("\\", "/") self.log.debug("_ filepath: {}".format(filepath)) start_at_workfile = "start at" in read_node['frame_mode'].value() @@ -270,13 +272,13 @@ class LoadClip(plugin.NukeLoader): ] project_name = get_current_project_name() - version_doc = get_version_by_id(project_name, representation["parent"]) + version_doc = get_version_by_id(project_name, repre_doc["parent"]) version_data = version_doc.get("data", {}) - repre_id = representation["_id"] + repre_id = repre_doc["_id"] # colorspace profile - colorspace = representation["data"].get("colorspace") + colorspace = repre_doc["data"].get("colorspace") colorspace = colorspace or version_data.get("colorspace") self.handle_start = version_data.get("handleStart", 0) @@ -303,12 +305,12 @@ class LoadClip(plugin.NukeLoader): # we will switch off undo-ing with viewer_update_and_undo_stop(): used_colorspace = self._set_colorspace( - read_node, version_data, representation["data"], filepath) + read_node, version_data, repre_doc["data"], filepath) self._set_range_to_node(read_node, first, last, start_at_workfile) updated_dict = { - "representation": str(representation["_id"]), + "representation": str(repre_doc["_id"]), "frameStart": str(first), "frameEnd": str(last), "version": str(version_doc.get("name")), diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_effects.py b/client/ayon_core/hosts/nuke/plugins/load/load_effects.py index 0b5f31033e..3e87c9cf60 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_effects.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_effects.py @@ -146,7 +146,7 @@ class LoadEffects(load.LoaderPlugin): loader=self.__class__.__name__, data=data_imprint) - def update(self, container, representation): + def update(self, container, context): """Update the Loader's path Nuke automatically tries to reset some variables when changing @@ -156,13 +156,14 @@ class LoadEffects(load.LoaderPlugin): """ # get main variables # Get version from io - project_name = get_current_project_name() - version_doc = get_version_by_id(project_name, representation["parent"]) + project_name = context["project"]["name"] + version_doc = context["version"] + repre_doc = context["representation"] # get corresponding node GN = container["node"] - file = get_representation_path(representation).replace("\\", "/") + file = get_representation_path(repre_doc).replace("\\", "/") name = container['name'] version_data = version_doc.get("data", {}) vname = version_doc.get("name", None) @@ -177,7 +178,7 @@ class LoadEffects(load.LoaderPlugin): "source", "author", "fps"] data_imprint = { - "representation": str(representation["_id"]), + "representation": str(repre_doc["_id"]), "frameStart": first, "frameEnd": last, "version": vname, @@ -344,8 +345,8 @@ class LoadEffects(load.LoaderPlugin): else: return input - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def remove(self, container): node = container["node"] diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_effects_ip.py b/client/ayon_core/hosts/nuke/plugins/load/load_effects_ip.py index 4d8a8518f2..5c363cddc4 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_effects_ip.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_effects_ip.py @@ -150,7 +150,7 @@ class LoadEffectsInputProcess(load.LoaderPlugin): loader=self.__class__.__name__, data=data_imprint) - def update(self, container, representation): + def update(self, container, context): """Update the Loader's path Nuke automatically tries to reset some variables when changing @@ -161,13 +161,14 @@ class LoadEffectsInputProcess(load.LoaderPlugin): # get main variables # Get version from io - project_name = get_current_project_name() - version_doc = get_version_by_id(project_name, representation["parent"]) + project_name = context["project"]["name"] + version_doc = context["version"] + repre_doc = context["representation"] # get corresponding node GN = container["node"] - file = get_representation_path(representation).replace("\\", "/") + file = get_representation_path(repre_doc).replace("\\", "/") version_data = version_doc.get("data", {}) vname = version_doc.get("name", None) first = version_data.get("frameStart", None) @@ -179,7 +180,7 @@ class LoadEffectsInputProcess(load.LoaderPlugin): "source", "author", "fps"] data_imprint = { - "representation": str(representation["_id"]), + "representation": str(repre_doc["_id"]), "frameStart": first, "frameEnd": last, "version": vname, @@ -355,8 +356,8 @@ class LoadEffectsInputProcess(load.LoaderPlugin): else: return input - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def remove(self, container): node = container["node"] diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_gizmo.py b/client/ayon_core/hosts/nuke/plugins/load/load_gizmo.py index 54daa74405..058228a145 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_gizmo.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_gizmo.py @@ -97,7 +97,7 @@ class LoadGizmo(load.LoaderPlugin): loader=self.__class__.__name__, data=data_imprint) - def update(self, container, representation): + def update(self, container, context): """Update the Loader's path Nuke automatically tries to reset some variables when changing @@ -108,13 +108,14 @@ class LoadGizmo(load.LoaderPlugin): # get main variables # Get version from io - project_name = get_current_project_name() - version_doc = get_version_by_id(project_name, representation["parent"]) + project_name = context["project"]["name"] + version_doc = context["version"] + repre_doc = context["representation"] # get corresponding node group_node = container["node"] - file = get_representation_path(representation).replace("\\", "/") + file = get_representation_path(repre_doc).replace("\\", "/") name = container['name'] version_data = version_doc.get("data", {}) vname = version_doc.get("name", None) @@ -128,7 +129,7 @@ class LoadGizmo(load.LoaderPlugin): "source", "author", "fps"] data_imprint = { - "representation": str(representation["_id"]), + "representation": str(repre_doc["_id"]), "frameStart": first, "frameEnd": last, "version": vname, @@ -173,8 +174,8 @@ class LoadGizmo(load.LoaderPlugin): return update_container(new_group_node, data_imprint) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def remove(self, container): node = container["node"] diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_gizmo_ip.py b/client/ayon_core/hosts/nuke/plugins/load/load_gizmo_ip.py index 677d9868f1..61e1c34028 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_gizmo_ip.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_gizmo_ip.py @@ -104,7 +104,7 @@ class LoadGizmoInputProcess(load.LoaderPlugin): loader=self.__class__.__name__, data=data_imprint) - def update(self, container, representation): + def update(self, container, context): """Update the Loader's path Nuke automatically tries to reset some variables when changing @@ -115,13 +115,14 @@ class LoadGizmoInputProcess(load.LoaderPlugin): # get main variables # Get version from io - project_name = get_current_project_name() - version_doc = get_version_by_id(project_name, representation["parent"]) + project_name = context["project"]["name"] + version_doc = context["version"] + repre_doc = context["representation"] # get corresponding node group_node = container["node"] - file = get_representation_path(representation).replace("\\", "/") + file = get_representation_path(repre_doc).replace("\\", "/") name = container['name'] version_data = version_doc.get("data", {}) vname = version_doc.get("name", None) @@ -135,7 +136,7 @@ class LoadGizmoInputProcess(load.LoaderPlugin): "source", "author", "fps"] data_imprint = { - "representation": str(representation["_id"]), + "representation": str(repre_doc["_id"]), "frameStart": first, "frameEnd": last, "version": vname, @@ -254,8 +255,8 @@ class LoadGizmoInputProcess(load.LoaderPlugin): else: return input - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def remove(self, container): node = container["node"] diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_image.py b/client/ayon_core/hosts/nuke/plugins/load/load_image.py index e9435ec10a..4f7a5ccc27 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_image.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_image.py @@ -155,10 +155,10 @@ class LoadImage(load.LoaderPlugin): loader=self.__class__.__name__, data=data_imprint) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) - def update(self, container, representation): + def update(self, container, context): """Update the Loader's path Nuke automatically tries to reset some variables when changing @@ -171,12 +171,16 @@ class LoadImage(load.LoaderPlugin): assert node.Class() == "Read", "Must be Read" - repr_cont = representation["context"] + project_name = context["project"]["name"] + version_doc = context["version"] + repre_doc = context["representation"] - file = get_representation_path(representation) + repr_cont = repre_doc["context"] + + file = get_representation_path(repre_doc) if not file: - repr_id = representation["_id"] + repr_id = repre_doc["_id"] self.log.warning( "Representation id `{}` is failing to load".format(repr_id)) return @@ -191,8 +195,6 @@ class LoadImage(load.LoaderPlugin): format(frame_number, "0{}".format(padding))) # Get start frame from version data - project_name = get_current_project_name() - version_doc = get_version_by_id(project_name, representation["parent"]) last_version_doc = get_last_version_by_subset_id( project_name, version_doc["parent"], fields=["_id"] ) @@ -210,7 +212,7 @@ class LoadImage(load.LoaderPlugin): updated_dict = {} updated_dict.update({ - "representation": str(representation["_id"]), + "representation": str(repre_doc["_id"]), "frameStart": str(first), "frameEnd": str(last), "version": str(version_doc.get("name")), diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_model.py b/client/ayon_core/hosts/nuke/plugins/load/load_model.py index 125cb28e27..cd4b72df91 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_model.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_model.py @@ -96,7 +96,7 @@ class AlembicModelLoader(load.LoaderPlugin): loader=self.__class__.__name__, data=data_imprint) - def update(self, container, representation): + def update(self, container, context): """ Called by Scene Inventory when look should be updated to current version. @@ -106,15 +106,15 @@ class AlembicModelLoader(load.LoaderPlugin): Args: container: object that has look to be updated - representation: (dict): relationship data to get proper + context: (dict): relationship data to get proper representation from DB and persisted data in .json Returns: None """ # Get version from io - project_name = get_current_project_name() - version_doc = get_version_by_id(project_name, representation["parent"]) + version_doc = context["version"] + repre_doc = context["representation"] # get corresponding node model_node = container["node"] @@ -131,7 +131,7 @@ class AlembicModelLoader(load.LoaderPlugin): add_keys = ["source", "author", "fps"] data_imprint = { - "representation": str(representation["_id"]), + "representation": str(repre_doc["_id"]), "frameStart": first, "frameEnd": last, "version": vname @@ -141,7 +141,7 @@ class AlembicModelLoader(load.LoaderPlugin): data_imprint.update({k: version_data[k]}) # getting file path - file = get_representation_path(representation).replace("\\", "/") + file = get_representation_path(repre_doc).replace("\\", "/") with maintained_selection(): model_node['selected'].setValue(True) @@ -202,8 +202,8 @@ class AlembicModelLoader(load.LoaderPlugin): color_value = "0xd88467ff" node["tile_color"].setValue(int(color_value, 16)) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def remove(self, container): node = nuke.toNode(container['objectName']) diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_ociolook.py b/client/ayon_core/hosts/nuke/plugins/load/load_ociolook.py index e168c2bac1..e2e7cd3262 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_ociolook.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_ociolook.py @@ -219,14 +219,13 @@ class LoadOcioLookNodes(load.LoaderPlugin): return group_node - def update(self, container, representation): - - project_name = get_current_project_name() - version_doc = get_version_by_id(project_name, representation["parent"]) + def update(self, container, context): + version_doc = context["version"] + repre_doc = context["representation"] group_node = container["node"] - filepath = get_representation_path(representation) + filepath = get_representation_path(repre_doc) json_f = self._load_json_data(filepath) @@ -242,7 +241,7 @@ class LoadOcioLookNodes(load.LoaderPlugin): group_node["name"].value())) return update_container( - group_node, {"representation": str(representation["_id"])}) + group_node, {"representation": str(repre_doc["_id"])}) def _load_json_data(self, filepath): # getting data from json file with unicode conversion @@ -280,8 +279,8 @@ class LoadOcioLookNodes(load.LoaderPlugin): else: return input - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def remove(self, container): node = nuke.toNode(container['objectName']) diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_script_precomp.py b/client/ayon_core/hosts/nuke/plugins/load/load_script_precomp.py index 1c91e51a09..5d62a7ca0f 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_script_precomp.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_script_precomp.py @@ -104,10 +104,10 @@ class LinkAsGroup(load.LoaderPlugin): loader=self.__class__.__name__, data=data_imprint) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) - def update(self, container, representation): + def update(self, container, context): """Update the Loader's path Nuke automatically tries to reset some variables when changing @@ -117,11 +117,13 @@ class LinkAsGroup(load.LoaderPlugin): """ node = container["node"] - root = get_representation_path(representation).replace("\\", "/") + project_name = context["project"]["name"] + version_doc = context["version"] + repre_doc = context["representation"] + + root = get_representation_path(repre_doc).replace("\\", "/") # Get start frame from version data - project_name = get_current_project_name() - version_doc = get_version_by_id(project_name, representation["parent"]) last_version_doc = get_last_version_by_subset_id( project_name, version_doc["parent"], fields=["_id"] ) @@ -129,7 +131,7 @@ class LinkAsGroup(load.LoaderPlugin): updated_dict = {} version_data = version_doc["data"] updated_dict.update({ - "representation": str(representation["_id"]), + "representation": str(repre_doc["_id"]), "frameEnd": version_data.get("frameEnd"), "version": version_doc.get("name"), "colorspace": version_data.get("colorspace"), diff --git a/client/ayon_core/hosts/photoshop/api/README.md b/client/ayon_core/hosts/photoshop/api/README.md index 51a9b9ad5e..9383bdaade 100644 --- a/client/ayon_core/hosts/photoshop/api/README.md +++ b/client/ayon_core/hosts/photoshop/api/README.md @@ -224,23 +224,23 @@ class ImageLoader(load.LoaderPlugin): self.__class__.__name__ ) - def update(self, container, representation): + def update(self, container, context): layer = container.pop("layer") - + repre_doc = context["representation"] with photoshop.maintained_selection(): stub.replace_smart_object( - layer, get_representation_path(representation) + layer, get_representation_path(repre_doc) ) stub.imprint( - layer, {"representation": str(representation["_id"])} + layer, {"representation": str(repre_doc["_id"])} ) def remove(self, container): container["layer"].Delete() - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) ``` For easier debugging of Javascript: https://community.adobe.com/t5/download-install/adobe-extension-debuger-problem/td-p/10911704?page=1 diff --git a/client/ayon_core/hosts/photoshop/plugins/load/load_image.py b/client/ayon_core/hosts/photoshop/plugins/load/load_image.py index 0fa6bca901..ec6392bade 100644 --- a/client/ayon_core/hosts/photoshop/plugins/load/load_image.py +++ b/client/ayon_core/hosts/photoshop/plugins/load/load_image.py @@ -36,13 +36,13 @@ class ImageLoader(photoshop.PhotoshopLoader): self.__class__.__name__ ) - def update(self, container, representation): + def update(self, container, context): """ Switch asset or change version """ stub = self.get_stub() layer = container.pop("layer") - context = representation.get("context", {}) + repre_doc = context["representation"] namespace_from_container = re.sub(r'_\d{3}$', '', container["namespace"]) @@ -55,14 +55,14 @@ class ImageLoader(photoshop.PhotoshopLoader): else: # switching version - keep same name layer_name = container["namespace"] - path = get_representation_path(representation) + path = get_representation_path(repre_doc) with photoshop.maintained_selection(): stub.replace_smart_object( layer, path, layer_name ) stub.imprint( - layer.id, {"representation": str(representation["_id"])} + layer.id, {"representation": str(repre_doc["_id"])} ) def remove(self, container): @@ -77,8 +77,8 @@ class ImageLoader(photoshop.PhotoshopLoader): stub.imprint(layer.id, {}) stub.delete_layer(layer.id) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def import_layer(self, file_name, layer_name, stub): return stub.import_smart_object(file_name, layer_name) diff --git a/client/ayon_core/hosts/photoshop/plugins/load/load_image_from_sequence.py b/client/ayon_core/hosts/photoshop/plugins/load/load_image_from_sequence.py index 06ac70041e..49ca513bd2 100644 --- a/client/ayon_core/hosts/photoshop/plugins/load/load_image_from_sequence.py +++ b/client/ayon_core/hosts/photoshop/plugins/load/load_image_from_sequence.py @@ -86,7 +86,7 @@ class ImageFromSequenceLoader(photoshop.PhotoshopLoader): ) ] - def update(self, container, representation): + def update(self, container, context): """No update possible, not containerized.""" pass diff --git a/client/ayon_core/hosts/photoshop/plugins/load/load_reference.py b/client/ayon_core/hosts/photoshop/plugins/load/load_reference.py index e2fec039d0..f83272f97d 100644 --- a/client/ayon_core/hosts/photoshop/plugins/load/load_reference.py +++ b/client/ayon_core/hosts/photoshop/plugins/load/load_reference.py @@ -37,32 +37,37 @@ class ReferenceLoader(photoshop.PhotoshopLoader): self.__class__.__name__ ) - def update(self, container, representation): + def update(self, container, context): """ Switch asset or change version """ stub = self.get_stub() layer = container.pop("layer") - context = representation.get("context", {}) + asset_doc = context["asset"] + subset_doc = context["subset"] + repre_doc = context["representation"] + + folder_name = asset_doc["name"] + product_name = subset_doc["name"] namespace_from_container = re.sub(r'_\d{3}$', '', container["namespace"]) - layer_name = "{}_{}".format(context["asset"], context["subset"]) + layer_name = "{}_{}".format(folder_name, product_name) # switching assets if namespace_from_container != layer_name: layer_name = get_unique_layer_name( - stub.get_layers(), context["asset"], context["subset"] + stub.get_layers(), folder_name, product_name ) else: # switching version - keep same name layer_name = container["namespace"] - path = get_representation_path(representation) + path = get_representation_path(repre_doc) with photoshop.maintained_selection(): stub.replace_smart_object( layer, path, layer_name ) stub.imprint( - layer.id, {"representation": str(representation["_id"])} + layer.id, {"representation": str(repre_doc["_id"])} ) def remove(self, container): @@ -76,8 +81,8 @@ class ReferenceLoader(photoshop.PhotoshopLoader): stub.imprint(layer.id, {}) stub.delete_layer(layer.id) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) def import_layer(self, file_name, layer_name, stub): return stub.import_smart_object( diff --git a/client/ayon_core/hosts/resolve/api/plugin.py b/client/ayon_core/hosts/resolve/api/plugin.py index 0c63dead32..157b8de363 100644 --- a/client/ayon_core/hosts/resolve/api/plugin.py +++ b/client/ayon_core/hosts/resolve/api/plugin.py @@ -538,7 +538,7 @@ class TimelineItemLoader(LoaderPlugin): ): pass - def update(self, container, representation): + def update(self, container, context): """Update an existing `container` """ pass diff --git a/client/ayon_core/hosts/resolve/plugins/load/load_clip.py b/client/ayon_core/hosts/resolve/plugins/load/load_clip.py index 4d1f8f1f7c..04b2aaaf15 100644 --- a/client/ayon_core/hosts/resolve/plugins/load/load_clip.py +++ b/client/ayon_core/hosts/resolve/plugins/load/load_clip.py @@ -59,21 +59,21 @@ class LoadClip(plugin.TimelineItemLoader): self.__class__.__name__, data_imprint) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) - def update(self, container, representation): + def update(self, container, context): """ Updating previously loaded clips """ - context = get_representation_context(representation) + repre_doc = context["representation"] name = container['name'] namespace = container['namespace'] timeline_item = container["_timeline_item"] media_pool_item = timeline_item.GetMediaPoolItem() - files = plugin.get_representation_files(representation) + files = plugin.get_representation_files(repre_doc) loader = plugin.ClipLoader(self, context) timeline_item = loader.update(timeline_item, files) @@ -92,10 +92,10 @@ class LoadClip(plugin.TimelineItemLoader): def get_tag_data(self, context, name, namespace): """Return data to be imprinted on the timeline item marker""" - representation = context["representation"] - version = context['version'] - version_data = version.get("data", {}) - version_name = version.get("name", None) + repre_doc = context["representation"] + version_doc = context["version"] + version_data = version_doc.get("data", {}) + version_name = version_doc.get("name", None) colorspace = version_data.get("colorspace", None) object_name = "{}_{}".format(name, namespace) @@ -111,7 +111,7 @@ class LoadClip(plugin.TimelineItemLoader): # add variables related to version context data.update({ - "representation": str(representation["_id"]), + "representation": str(repre_doc["_id"]), "version": version_name, "colorspace": colorspace, "objectName": object_name diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index 48aa99d357..810fecb8e5 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -97,12 +97,13 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): set_container_metadata(project_mesh_object_name, container) - def switch(self, container, representation): - self.update(container, representation) + def switch(self, container, context): + self.update(container, context) - def update(self, container, representation): + def update(self, container, context): + repre_doc = context["representation"] - path = get_representation_path(representation) + path = get_representation_path(repre_doc) # Reload the mesh container_options = container.get("options", {}) @@ -121,7 +122,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): # Update container representation object_name = container["objectName"] - update_data = {"representation": str(representation["_id"])} + update_data = {"representation": str(repre_doc["_id"])} set_container_metadata(object_name, update_data, update=True) def remove(self, container): diff --git a/client/ayon_core/hosts/tvpaint/plugins/load/load_reference_image.py b/client/ayon_core/hosts/tvpaint/plugins/load/load_reference_image.py index 856bf69845..5e629f7b7f 100644 --- a/client/ayon_core/hosts/tvpaint/plugins/load/load_reference_image.py +++ b/client/ayon_core/hosts/tvpaint/plugins/load/load_reference_image.py @@ -210,15 +210,17 @@ class LoadImage(plugin.Loader): def switch(self, container, representation): self.update(container, representation) - def update(self, container, representation): + def update(self, container, context): """Replace container with different version. New layers are loaded as first step. Then is tried to change data in new layers with data from old layers. When that is done old layers are removed. """ + + repre_doc = context["representation"] # Create new containers first - context = get_representation_context(representation) + context = get_representation_context(repre_doc) # Get layer ids from previous container old_layer_names = self.get_members_from_container(container) diff --git a/client/ayon_core/hosts/unreal/plugins/load/load_alembic_animation.py b/client/ayon_core/hosts/unreal/plugins/load/load_alembic_animation.py index fe13e5989b..a4fcda6ade 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_alembic_animation.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_alembic_animation.py @@ -126,12 +126,15 @@ class AnimationAlembicLoader(plugin.Loader): return asset_content - def update(self, container, representation): - name = container["asset_name"] - source_path = get_representation_path(representation) + def update(self, container, context): + folder_name = container["asset_name"] + repre_doc = context["representation"] + source_path = get_representation_path(repre_doc) destination_path = container["namespace"] - task = self.get_task(source_path, destination_path, name, True) + task = self.get_task( + source_path, destination_path, folder_name, True + ) # do import fbx and replace existing data asset_tools = unreal.AssetToolsHelpers.get_asset_tools() @@ -143,8 +146,8 @@ class AnimationAlembicLoader(plugin.Loader): unreal_pipeline.imprint( container_path, { - "representation": str(representation["_id"]), - "parent": str(representation["parent"]) + "representation": str(repre_doc["_id"]), + "parent": str(repre_doc["parent"]) }) asset_content = unreal.EditorAssetLibrary.list_assets( diff --git a/client/ayon_core/hosts/unreal/plugins/load/load_animation.py b/client/ayon_core/hosts/unreal/plugins/load/load_animation.py index ee5b8d9244..3aad6886be 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_animation.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_animation.py @@ -246,9 +246,10 @@ class AnimationFBXLoader(plugin.Loader): unreal.EditorLevelLibrary.save_current_level() unreal.EditorLevelLibrary.load_level(master_level) - def update(self, container, representation): - name = container["asset_name"] - source_path = get_representation_path(representation) + def update(self, container, context): + repre_doc = context["representation"] + folder_name = container["asset_name"] + source_path = get_representation_path(repre_doc) asset_doc = get_current_project_asset(fields=["data.fps"]) destination_path = container["namespace"] @@ -258,7 +259,7 @@ class AnimationFBXLoader(plugin.Loader): task.set_editor_property('filename', source_path) task.set_editor_property('destination_path', destination_path) # strip suffix - task.set_editor_property('destination_name', name) + task.set_editor_property('destination_name', folder_name) task.set_editor_property('replace_existing', True) task.set_editor_property('automated', True) task.set_editor_property('save', True) @@ -305,8 +306,8 @@ class AnimationFBXLoader(plugin.Loader): unreal_pipeline.imprint( container_path, { - "representation": str(representation["_id"]), - "parent": str(representation["parent"]) + "representation": str(repre_doc["_id"]), + "parent": str(repre_doc["parent"]) }) asset_content = EditorAssetLibrary.list_assets( diff --git a/client/ayon_core/hosts/unreal/plugins/load/load_camera.py b/client/ayon_core/hosts/unreal/plugins/load/load_camera.py index 257cc2c720..34c1e3e023 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_camera.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_camera.py @@ -260,7 +260,7 @@ class CameraLoader(plugin.Loader): return asset_content - def update(self, container, representation): + def update(self, container, context): ar = unreal.AssetRegistryHelpers.get_asset_registry() curr_level_sequence = LevelSequenceLib.get_current_level_sequence() @@ -379,12 +379,13 @@ class CameraLoader(plugin.Loader): sub_scene.set_sequence(new_sequence) + repre_doc = context["representation"] self._import_camera( EditorLevelLibrary.get_editor_world(), new_sequence, new_sequence.get_bindings(), settings, - str(representation["data"]["path"]) + str(repre_doc["data"]["path"]) ) # Set range of all sections @@ -412,8 +413,8 @@ class CameraLoader(plugin.Loader): key.set_time(unreal.FrameNumber(value=new_time)) data = { - "representation": str(representation["_id"]), - "parent": str(representation["parent"]) + "representation": str(repre_doc["_id"]), + "parent": str(repre_doc["parent"]) } imprint(f"{asset_dir}/{container.get('container_name')}", data) diff --git a/client/ayon_core/hosts/unreal/plugins/load/load_geometrycache_abc.py b/client/ayon_core/hosts/unreal/plugins/load/load_geometrycache_abc.py index d7a4df1b03..bca99f202f 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_geometrycache_abc.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_geometrycache_abc.py @@ -163,25 +163,30 @@ class PointCacheAlembicLoader(plugin.Loader): return asset_content - def update(self, container, representation): - context = representation.get("context", {}) - - unreal.log_warning(context) - - if not context: - raise RuntimeError("No context found in representation") + def update(self, container, context): + asset_doc = context["asset"] + subset_doc = context["subset"] + version_doc = context["version"] + repre_doc = context["representation"] # Create directory for asset and Ayon container - asset = context.get('asset') - name = context.get('subset') + folder_name = asset_doc["name"] + product_name = subset_doc["name"] + suffix = "_CON" - asset_name = f"{asset}_{name}" if asset else f"{name}" - version = context.get('version') + asset_name = product_name + if folder_name: + asset_name = f"{folder_name}_{product_name}" + # Check if version is hero version and use different name - name_version = f"{name}_v{version:03d}" if version else f"{name}_hero" + version = version_doc.get("name", -1) + if version < 0: + name_version = f"{product_name}_hero" + else: + name_version = f"{product_name}_v{version:03d}" tools = unreal.AssetToolsHelpers().get_asset_tools() asset_dir, container_name = tools.create_unique_asset_name( - f"{self.root}/{asset}/{name_version}", suffix="") + f"{self.root}/{folder_name}/{name_version}", suffix="") container_name += suffix @@ -189,14 +194,14 @@ class PointCacheAlembicLoader(plugin.Loader): frame_end = int(container.get("frame_end")) if not unreal.EditorAssetLibrary.does_directory_exist(asset_dir): - path = get_representation_path(representation) + path = get_representation_path(repre_doc) self.import_and_containerize( path, asset_dir, asset_name, container_name, frame_start, frame_end) self.imprint( - asset, asset_dir, container_name, asset_name, representation, + folder_name, asset_dir, container_name, asset_name, repre_doc, frame_start, frame_end) asset_content = unreal.EditorAssetLibrary.list_assets( diff --git a/client/ayon_core/hosts/unreal/plugins/load/load_layout.py b/client/ayon_core/hosts/unreal/plugins/load/load_layout.py index 1a17268d1c..f78dba9e57 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_layout.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_layout.py @@ -661,7 +661,7 @@ class LayoutLoader(plugin.Loader): return asset_content - def update(self, container, representation): + def update(self, container, context): data = get_current_project_settings() create_sequences = data["unreal"]["level_sequences_for_layouts"] @@ -677,9 +677,11 @@ class LayoutLoader(plugin.Loader): root = "/Game/Ayon" asset_dir = container.get('namespace') - context = representation.get("context") - hierarchy = context.get('hierarchy').split("/") + asset_doc = context["asset"] + repre_doc = context["representation"] + + hierarchy = list(asset_doc["data"]["parents"]) sequence = None master_level = None @@ -728,13 +730,13 @@ class LayoutLoader(plugin.Loader): EditorAssetLibrary.delete_directory(f"{asset_dir}/animations/") - source_path = get_representation_path(representation) + source_path = get_representation_path(repre_doc) loaded_assets = self._process(source_path, asset_dir, sequence) data = { - "representation": str(representation["_id"]), - "parent": str(representation["parent"]), + "representation": str(repre_doc["_id"]), + "parent": str(repre_doc["parent"]), "loaded_assets": loaded_assets } imprint( diff --git a/client/ayon_core/hosts/unreal/plugins/load/load_layout_existing.py b/client/ayon_core/hosts/unreal/plugins/load/load_layout_existing.py index 94686e2a5b..cf987765f4 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_layout_existing.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_layout_existing.py @@ -407,16 +407,18 @@ class ExistingLayoutLoader(plugin.Loader): } upipeline.imprint(f"{curr_level_path}/{container_name}", data) - def update(self, container, representation): + def update(self, container, context): asset_dir = container.get('namespace') - source_path = get_representation_path(representation) - project_name = get_current_project_name() + project_name = context["project"]["name"] + repre_doc = context["representation"] + + source_path = get_representation_path(repre_doc) containers = self._process(source_path, project_name) data = { - "representation": str(representation["_id"]), - "parent": str(representation["parent"]), + "representation": str(repre_doc["_id"]), + "parent": str(repre_doc["parent"]), "loaded_assets": containers } upipeline.imprint( diff --git a/client/ayon_core/hosts/unreal/plugins/load/load_skeletalmesh_abc.py b/client/ayon_core/hosts/unreal/plugins/load/load_skeletalmesh_abc.py index 3ab6ea8ebd..58fbda491c 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_skeletalmesh_abc.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_skeletalmesh_abc.py @@ -144,34 +144,40 @@ class SkeletalMeshAlembicLoader(plugin.Loader): return asset_content - def update(self, container, representation): - context = representation.get("context", {}) + def update(self, container, context): + asset_doc = context["asset"] + subset_doc = context["subset"] + version_doc = context["version"] + repre_doc = context["representation"] - if not context: - raise RuntimeError("No context found in representation") + folder_name = asset_doc["name"] + product_name = subset_doc["name"] - # Create directory for asset and Ayon container - asset = context.get('asset') - name = context.get('subset') + # Create directory for folder and Ayon container suffix = "_CON" - asset_name = f"{asset}_{name}" if asset else f"{name}" - version = context.get('version') + asset_name = product_name + if folder_name: + asset_name = f"{folder_name}_{product_name}" # Check if version is hero version and use different name - name_version = f"{name}_v{version:03d}" if version else f"{name}_hero" + version = version_doc.get("name", -1) + if version < 0: + name_version = f"{product_name}_hero" + else: + name_version = f"{product_name}_v{version:03d}" tools = unreal.AssetToolsHelpers().get_asset_tools() asset_dir, container_name = tools.create_unique_asset_name( - f"{self.root}/{asset}/{name_version}", suffix="") + f"{self.root}/{folder_name}/{name_version}", suffix="") container_name += suffix if not unreal.EditorAssetLibrary.does_directory_exist(asset_dir): - path = get_representation_path(representation) + path = get_representation_path(repre_doc) self.import_and_containerize(path, asset_dir, asset_name, container_name) self.imprint( - asset, asset_dir, container_name, asset_name, representation) + folder_name, asset_dir, container_name, asset_name, repre_doc) asset_content = unreal.EditorAssetLibrary.list_assets( asset_dir, recursive=True, include_folder=False diff --git a/client/ayon_core/hosts/unreal/plugins/load/load_skeletalmesh_fbx.py b/client/ayon_core/hosts/unreal/plugins/load/load_skeletalmesh_fbx.py index cbdb4901f8..436d4c8a58 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_skeletalmesh_fbx.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_skeletalmesh_fbx.py @@ -146,34 +146,40 @@ class SkeletalMeshFBXLoader(plugin.Loader): return asset_content - def update(self, container, representation): - context = representation.get("context", {}) + def update(self, container, context): + asset_doc = context["asse"] + subset_doc = context["subset"] + version_doc = context["version"] + repre_doc = context["representation"] - if not context: - raise RuntimeError("No context found in representation") + folder_name = asset_doc["name"] + product_name = subset_doc["name"] # Create directory for asset and Ayon container - asset = context.get('asset') - name = context.get('subset') suffix = "_CON" - asset_name = f"{asset}_{name}" if asset else f"{name}" - version = context.get('version') + asset_name = product_name + if folder_name: + asset_name = f"{folder_name}_{product_name}" # Check if version is hero version and use different name - name_version = f"{name}_v{version:03d}" if version else f"{name}_hero" + version = version_doc.get("name", -1) + if version < 0: + name_version = f"{product_name}_hero" + else: + name_version = f"{product_name}_v{version:03d}" tools = unreal.AssetToolsHelpers().get_asset_tools() asset_dir, container_name = tools.create_unique_asset_name( - f"{self.root}/{asset}/{name_version}", suffix="") + f"{self.root}/{folder_name}/{name_version}", suffix="") container_name += suffix if not unreal.EditorAssetLibrary.does_directory_exist(asset_dir): - path = get_representation_path(representation) + path = get_representation_path(repre_doc) self.import_and_containerize( path, asset_dir, asset_name, container_name) self.imprint( - asset, asset_dir, container_name, asset_name, representation) + folder_name, asset_dir, container_name, asset_name, repre_doc) asset_content = unreal.EditorAssetLibrary.list_assets( asset_dir, recursive=True, include_folder=False diff --git a/client/ayon_core/hosts/unreal/plugins/load/load_staticmesh_abc.py b/client/ayon_core/hosts/unreal/plugins/load/load_staticmesh_abc.py index c60ad8814c..6ff4bcd4f2 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_staticmesh_abc.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_staticmesh_abc.py @@ -145,34 +145,36 @@ class StaticMeshAlembicLoader(plugin.Loader): return asset_content - def update(self, container, representation): - context = representation.get("context", {}) + def update(self, container, context): + asset_doc = context["asset"] + subset_doc = context["subset"] + repre_doc = context["representation"] - if not context: - raise RuntimeError("No context found in representation") + folder_name = asset_doc["name"] + product_name = subset_doc["name"] # Create directory for asset and Ayon container - asset = context.get('asset') - name = context.get('subset') suffix = "_CON" - asset_name = f"{asset}_{name}" if asset else f"{name}" + asset_name = product_name + if folder_name: + asset_name = f"{folder_name}_{product_name}" version = context.get('version') # Check if version is hero version and use different name - name_version = f"{name}_v{version:03d}" if version else f"{name}_hero" + name_version = f"{product_name}_v{version:03d}" if version else f"{product_name}_hero" tools = unreal.AssetToolsHelpers().get_asset_tools() asset_dir, container_name = tools.create_unique_asset_name( - f"{self.root}/{asset}/{name_version}", suffix="") + f"{self.root}/{folder_name}/{name_version}", suffix="") container_name += suffix if not unreal.EditorAssetLibrary.does_directory_exist(asset_dir): - path = get_representation_path(representation) + path = get_representation_path(repre_doc) self.import_and_containerize(path, asset_dir, asset_name, container_name) self.imprint( - asset, asset_dir, container_name, asset_name, representation) + folder_name, asset_dir, container_name, asset_name, repre_doc) asset_content = unreal.EditorAssetLibrary.list_assets( asset_dir, recursive=True, include_folder=False diff --git a/client/ayon_core/hosts/unreal/plugins/load/load_staticmesh_fbx.py b/client/ayon_core/hosts/unreal/plugins/load/load_staticmesh_fbx.py index c9271159c4..d2c6fc5566 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_staticmesh_fbx.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_staticmesh_fbx.py @@ -134,34 +134,40 @@ class StaticMeshFBXLoader(plugin.Loader): return asset_content - def update(self, container, representation): - context = representation.get("context", {}) + def update(self, container, context): + asset_doc = context["asset"] + subset_doc = context["subset"] + version_doc = context["version"] + repre_doc = context["representation"] - if not context: - raise RuntimeError("No context found in representation") + folder_name = asset_doc["name"] + product_name = subset_doc["name"] # Create directory for asset and Ayon container - asset = context.get('asset') - name = context.get('subset') suffix = "_CON" - asset_name = f"{asset}_{name}" if asset else f"{name}" - version = context.get('version') + asset_name = product_name + if folder_name: + asset_name = f"{folder_name}_{product_name}" # Check if version is hero version and use different name - name_version = f"{name}_v{version:03d}" if version else f"{name}_hero" + version = version_doc.get("name", -1) + if version < 0: + name_version = f"{product_name}_hero" + else: + name_version = f"{product_name}_v{version:03d}" tools = unreal.AssetToolsHelpers().get_asset_tools() asset_dir, container_name = tools.create_unique_asset_name( - f"{self.root}/{asset}/{name_version}", suffix="") + f"{self.root}/{folder_name}/{name_version}", suffix="") container_name += suffix if not unreal.EditorAssetLibrary.does_directory_exist(asset_dir): - path = get_representation_path(representation) + path = get_representation_path(repre_doc) self.import_and_containerize( path, asset_dir, asset_name, container_name) self.imprint( - asset, asset_dir, container_name, asset_name, representation) + folder_name, asset_dir, container_name, asset_name, repre_doc) asset_content = unreal.EditorAssetLibrary.list_assets( asset_dir, recursive=True, include_folder=False diff --git a/client/ayon_core/hosts/unreal/plugins/load/load_uasset.py b/client/ayon_core/hosts/unreal/plugins/load/load_uasset.py index 0898035985..9710d213ee 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_uasset.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_uasset.py @@ -96,11 +96,15 @@ class UAssetLoader(plugin.Loader): return asset_content - def update(self, container, representation): + def update(self, container, context): ar = unreal.AssetRegistryHelpers.get_asset_registry() asset_dir = container["namespace"] - name = representation["context"]["subset"] + + subset_doc = context["subset"] + repre_doc = context["representation"] + + product_name = subset_doc["name"] unique_number = container["container_name"].split("_")[-2] @@ -116,19 +120,20 @@ class UAssetLoader(plugin.Loader): if obj.get_class().get_name() != "AyonAssetContainer": unreal.EditorAssetLibrary.delete_asset(asset) - update_filepath = get_representation_path(representation) + update_filepath = get_representation_path(repre_doc) shutil.copy( update_filepath, - f"{destination_path}/{name}_{unique_number}.{self.extension}") + f"{destination_path}/{product_name}_{unique_number}.{self.extension}" + ) container_path = f'{container["namespace"]}/{container["objectName"]}' # update metadata unreal_pipeline.imprint( container_path, { - "representation": str(representation["_id"]), - "parent": str(representation["parent"]), + "representation": str(repre_doc["_id"]), + "parent": str(repre_doc["parent"]), } ) diff --git a/client/ayon_core/hosts/unreal/plugins/load/load_yeticache.py b/client/ayon_core/hosts/unreal/plugins/load/load_yeticache.py index d21c6205fc..c6e275c844 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_yeticache.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_yeticache.py @@ -139,9 +139,10 @@ class YetiLoader(plugin.Loader): return asset_content - def update(self, container, representation): + def update(self, container, context): + repre_doc = context["representation"] name = container["asset_name"] - source_path = get_representation_path(representation) + source_path = get_representation_path(repre_doc) destination_path = container["namespace"] task = self.get_task(source_path, destination_path, name, True) @@ -154,8 +155,8 @@ class YetiLoader(plugin.Loader): unreal_pipeline.imprint( container_path, { - "representation": str(representation["_id"]), - "parent": str(representation["parent"]) + "representation": str(repre_doc["_id"]), + "parent": str(repre_doc["parent"]) }) asset_content = unreal.EditorAssetLibrary.list_assets( diff --git a/client/ayon_core/pipeline/load/plugins.py b/client/ayon_core/pipeline/load/plugins.py index 5ac8038e66..3b60e357af 100644 --- a/client/ayon_core/pipeline/load/plugins.py +++ b/client/ayon_core/pipeline/load/plugins.py @@ -192,13 +192,13 @@ class LoaderPlugin(list): raise NotImplementedError("Loader.load() must be " "implemented by subclass") - def update(self, container, representation): + def update(self, container, context): """Update `container` to `representation` - Arguments: + Args: container (avalon-core:container-1.0): Container to update, from `host.ls()`. - representation (dict): Update the container to this representation. + context (dict): Update the container to this representation. """ raise NotImplementedError("Loader.update() must be " diff --git a/client/ayon_core/pipeline/load/utils.py b/client/ayon_core/pipeline/load/utils.py index 056836d712..e2bc1c7a26 100644 --- a/client/ayon_core/pipeline/load/utils.py +++ b/client/ayon_core/pipeline/load/utils.py @@ -1,7 +1,6 @@ import os import platform import copy -import getpass import logging import inspect import collections @@ -11,7 +10,9 @@ from ayon_core.host import ILoadHost from ayon_core.client import ( get_project, get_assets, + get_asset_by_id, get_subsets, + get_subset_by_id, get_versions, get_version_by_id, get_last_version_by_subset_id, @@ -481,6 +482,8 @@ def update_container(container, version=-1): new_version = get_version_by_name( project_name, version, current_version["parent"], fields=["_id"] ) + subset_doc = get_subset_by_id(project_name, current_version["parent"]) + asset_doc = get_asset_by_id(project_name, subset_doc["parent"]) assert new_version is not None, "This is a bug" @@ -499,8 +502,19 @@ def update_container(container, version=-1): "Can't update container because loader '{}' was not found." .format(container.get("loader")) ) + project_doc = get_project(project_name) + context = { + "project": { + "name": project_doc["name"], + "code": project_doc["data"]["code"], + }, + "asset": asset_doc, + "subset": subset_doc, + "version": new_version, + "representation": new_representation, + } - return Loader().update(container, new_representation) + return Loader().update(container, context) def switch_container(container, representation, loader_plugin=None): @@ -549,7 +563,7 @@ def switch_container(container, representation, loader_plugin=None): loader = loader_plugin(new_context) - return loader.switch(container, new_representation) + return loader.switch(container, new_context) def get_representation_path_from_context(context): diff --git a/client/ayon_core/plugins/publish/collect_audio.py b/client/ayon_core/plugins/publish/collect_audio.py index 357dad76d4..58419e7284 100644 --- a/client/ayon_core/plugins/publish/collect_audio.py +++ b/client/ayon_core/plugins/publish/collect_audio.py @@ -40,7 +40,10 @@ class CollectAudio(pyblish.api.ContextPlugin): "webpublisher", "aftereffects", "flame", - "unreal" + "unreal", + "blender", + "houdini", + "max", ] audio_product_name = "audioMain" diff --git a/client/ayon_core/style/style.css b/client/ayon_core/style/style.css index ce467663fc..fcc76b0bff 100644 --- a/client/ayon_core/style/style.css +++ b/client/ayon_core/style/style.css @@ -1138,17 +1138,17 @@ ValidationArtistMessage QLabel { font-size: 13pt; } -#AssetNameInputWidget { +#FolderPathInputWidget { background: {color:bg-inputs}; border: 1px solid {color:border}; border-radius: 0.2em; } -#AssetNameInputWidget QWidget { +#FolderPathInputWidget QWidget { background: transparent; } -#AssetNameInputButton { +#FolderPathInputButton { border-bottom-left-radius: 0px; border-top-left-radius: 0px; padding: 0px; @@ -1159,23 +1159,23 @@ ValidationArtistMessage QLabel { border-bottom: none; } -#AssetNameInput { +#FolderPathInput { border-bottom-right-radius: 0px; border-top-right-radius: 0px; border: none; } -#AssetNameInputWidget:hover { +#FolderPathInputWidget:hover { border-color: {color:border-hover}; } -#AssetNameInputWidget:focus{ +#FolderPathInputWidget:focus{ border-color: {color:border-focus}; } -#AssetNameInputWidget:disabled { +#FolderPathInputWidget:disabled { background: {color:bg-inputs-disabled}; } -#TasksCombobox[state="invalid"], #AssetNameInputWidget[state="invalid"], #AssetNameInputButton[state="invalid"] { +#TasksCombobox[state="invalid"], #FolderPathInputWidget[state="invalid"], #FolderPathInputButton[state="invalid"] { border-color: {color:publisher:error}; } diff --git a/client/ayon_core/tools/ayon_utils/models/hierarchy.py b/client/ayon_core/tools/ayon_utils/models/hierarchy.py index 07773dfb78..10495cf10b 100644 --- a/client/ayon_core/tools/ayon_utils/models/hierarchy.py +++ b/client/ayon_core/tools/ayon_utils/models/hierarchy.py @@ -191,12 +191,12 @@ def _get_folder_item_from_hierarchy_item(item): name = item["name"] path_parts = list(item["parents"]) path_parts.append(name) - + path = "/" + "/".join(path_parts) return FolderItem( item["id"], item["parentId"], name, - "/".join(path_parts), + path, item["folderType"], item["label"], None, @@ -307,8 +307,44 @@ class HierarchyModel(object): }) return output + def get_folder_items_by_paths(self, project_name, folder_paths): + """Get folder items by ids. + + This function will query folders if they are not in cache. But the + queried items are not added to cache back. + + Args: + project_name (str): Name of project where to look for folders. + folder_paths (Iterable[str]): Folder paths. + + Returns: + dict[str, Union[FolderItem, None]]: Folder items by id. + """ + + folder_paths = set(folder_paths) + output = {folder_path: None for folder_path in folder_paths} + if not folder_paths: + return output + + if self._folders_items[project_name].is_valid: + cache_data = self._folders_items[project_name].get_data() + for folder_item in cache_data.values(): + if folder_item.path in folder_paths: + output[folder_item.path] = folder_item + return output + folders = ayon_api.get_folders( + project_name, + folder_paths=folder_paths, + fields=["id", "name", "label", "parentId", "path", "folderType"] + ) + # Make sure all folder ids are in output + for folder in folders: + item = _get_folder_item_from_entity(folder) + output[item.path] = item + return output + def get_folder_item(self, project_name, folder_id): - """Get folder items by id. + """Get folder item by id. This function will query folder if they is not in cache. But the queried items are not added to cache back. @@ -325,6 +361,25 @@ class HierarchyModel(object): ) return items.get(folder_id) + def get_folder_item_by_path(self, project_name, folder_path): + """Get folder item by path. + + This function will query folder if they is not in cache. But the + queried items are not added to cache back. + + Args: + project_name (str): Name of project where to look for folders. + folder_path (str): Folder path. + + Returns: + Union[FolderItem, None]: Folder item. + + """ + items = self.get_folder_items_by_paths( + project_name, [folder_path] + ) + return items.get(folder_path) + def get_task_items(self, project_name, folder_id, sender): if not project_name or not folder_id: return [] diff --git a/client/ayon_core/tools/ayon_utils/widgets/__init__.py b/client/ayon_core/tools/ayon_utils/widgets/__init__.py index b1b7dd7527..a62bab6751 100644 --- a/client/ayon_core/tools/ayon_utils/widgets/__init__.py +++ b/client/ayon_core/tools/ayon_utils/widgets/__init__.py @@ -3,6 +3,10 @@ from .projects_widget import ( ProjectsCombobox, ProjectsQtModel, ProjectSortFilterProxy, + PROJECT_NAME_ROLE, + PROJECT_IS_CURRENT_ROLE, + PROJECT_IS_ACTIVE_ROLE, + PROJECT_IS_LIBRARY_ROLE, ) from .folders_widget import ( @@ -28,6 +32,10 @@ __all__ = ( "ProjectsCombobox", "ProjectsQtModel", "ProjectSortFilterProxy", + "PROJECT_NAME_ROLE", + "PROJECT_IS_CURRENT_ROLE", + "PROJECT_IS_ACTIVE_ROLE", + "PROJECT_IS_LIBRARY_ROLE", "FoldersWidget", "FoldersQtModel", diff --git a/client/ayon_core/tools/ayon_utils/widgets/folders_widget.py b/client/ayon_core/tools/ayon_utils/widgets/folders_widget.py index cf81d1c8ff..e42a5b635c 100644 --- a/client/ayon_core/tools/ayon_utils/widgets/folders_widget.py +++ b/client/ayon_core/tools/ayon_utils/widgets/folders_widget.py @@ -91,6 +91,21 @@ class FoldersQtModel(QtGui.QStandardItemModel): return QtCore.QModelIndex() return self.indexFromItem(item) + def get_item_id_by_path(self, folder_path): + """Get folder id by path. + + Args: + folder_path (str): Folder path. + + Returns: + Union[str, None]: Folder id or None if folder is not available. + + """ + for folder_id, item in self._items_by_id.values(): + if item.data(FOLDER_PATH_ROLE) == folder_path: + return folder_id + return None + def get_project_name(self): """Project name which model currently use. @@ -431,8 +446,10 @@ class FoldersWidget(QtWidgets.QWidget): Args: folder_id (Union[str, None]): Folder id or None to deselect. - """ + Returns: + bool: Requested folder was selected. + """ if folder_id is None: self._folders_view.clearSelection() return True @@ -453,6 +470,25 @@ class FoldersWidget(QtWidgets.QWidget): ) return True + def set_selected_folder_path(self, folder_path): + """Set selected folder by path. + + Args: + folder_path (str): Folder path. + + Returns: + bool: Requested folder was selected. + + """ + if folder_path is None: + self._folders_view.clearSelection() + return True + + folder_id = self._folders_model.get_item_id_by_path(folder_path) + if folder_id is None: + return False + return self.set_selected_folder(folder_id) + def set_deselectable(self, enabled): """Set deselectable mode. diff --git a/client/ayon_core/tools/ayon_utils/widgets/projects_widget.py b/client/ayon_core/tools/ayon_utils/widgets/projects_widget.py index d3bebecfd6..79ffc77640 100644 --- a/client/ayon_core/tools/ayon_utils/widgets/projects_widget.py +++ b/client/ayon_core/tools/ayon_utils/widgets/projects_widget.py @@ -47,6 +47,22 @@ class ProjectsQtModel(QtGui.QStandardItemModel): def has_content(self): return len(self._project_items) > 0 + def get_index_by_project_name(self, project_name): + """Get index of project by name. + + Args: + project_name (str): Project name. + + Returns: + QtCore.QModelIndex: Index of project item. Index is not valid + if project is not found. + + """ + item = self._project_items.get(project_name) + if item is None: + return QtCore.QModelIndex() + return self.indexFromItem(item) + def set_select_item_visible(self, visible): if self._select_item_visible is visible: return diff --git a/client/ayon_core/tools/ayon_utils/widgets/tasks_widget.py b/client/ayon_core/tools/ayon_utils/widgets/tasks_widget.py index 3d6cc47fe3..b273d83fa6 100644 --- a/client/ayon_core/tools/ayon_utils/widgets/tasks_widget.py +++ b/client/ayon_core/tools/ayon_utils/widgets/tasks_widget.py @@ -214,6 +214,7 @@ class TasksQtModel(QtGui.QStandardItemModel): item.setData(task_item.label, QtCore.Qt.DisplayRole) item.setData(name, ITEM_NAME_ROLE) item.setData(task_item.id, ITEM_ID_ROLE) + item.setData(task_item.task_type, TASK_TYPE_ROLE) item.setData(task_item.parent_id, PARENT_ID_ROLE) item.setData(icon, QtCore.Qt.DecorationRole) @@ -358,6 +359,78 @@ class TasksWidget(QtWidgets.QWidget): self._tasks_model.refresh() + def get_selected_task_info(self): + """Get selected task info. + + Example output:: + + { + "task_id": "5e7e3e3e3e3e3e3e3e3e3e3e", + "task_name": "modeling", + "task_type": "Modeling" + } + + Returns: + dict[str, Union[str, None]]: Task info. + + """ + _, task_id, task_name, task_type = self._get_selected_item_ids() + return { + "task_id": task_id, + "task_name": task_name, + "task_type": task_type, + } + + def get_selected_task_name(self): + """Get selected task name. + + Returns: + Union[str, None]: Task name. + """ + + _, _, task_name, _ = self._get_selected_item_ids() + return task_name + + def get_selected_task_type(self): + """Get selected task type. + + Returns: + Union[str, None]: Task type. + + """ + _, _, _, task_type = self._get_selected_item_ids() + return task_type + + def set_selected_task(self, task_name): + """Set selected task by name. + + Args: + task_name (str): Task name. + + Returns: + bool: Task was selected. + + """ + if task_name is None: + self._tasks_view.clearSelection() + return True + + if task_name == self.get_selected_task_name(): + return True + index = self._tasks_model.get_index_by_name(task_name) + if not index.isValid(): + return False + + proxy_index = self._tasks_proxy_model.mapFromSource(index) + if not proxy_index.isValid(): + return False + + selection_model = self._folders_view.selectionModel() + selection_model.setCurrentIndex( + proxy_index, QtCore.QItemSelectionModel.SelectCurrent + ) + return True + def _on_tasks_refresh_finished(self, event): """Tasks were refreshed in controller. @@ -395,10 +468,11 @@ class TasksWidget(QtWidgets.QWidget): for index in selection_model.selectedIndexes(): task_id = index.data(ITEM_ID_ROLE) task_name = index.data(ITEM_NAME_ROLE) + task_type = index.data(TASK_TYPE_ROLE) parent_id = index.data(PARENT_ID_ROLE) if task_name is not None: - return parent_id, task_id, task_name - return self._selected_folder_id, None, None + return parent_id, task_id, task_name, task_type + return self._selected_folder_id, None, None, None def _on_selection_change(self): # Don't trigger task change during refresh @@ -407,7 +481,7 @@ class TasksWidget(QtWidgets.QWidget): if self._tasks_model.is_refreshing: return - parent_id, task_id, task_name = self._get_selected_item_ids() + parent_id, task_id, task_name, _ = self._get_selected_item_ids() self._controller.set_selected_task(task_id, task_name) self.selection_changed.emit() diff --git a/client/ayon_core/tools/publisher/control.py b/client/ayon_core/tools/publisher/control.py index c639a509d3..712142f662 100644 --- a/client/ayon_core/tools/publisher/control.py +++ b/client/ayon_core/tools/publisher/control.py @@ -12,14 +12,13 @@ from abc import ABCMeta, abstractmethod import six import arrow import pyblish.api +import ayon_api from ayon_core.client import ( - get_assets, - get_asset_by_id, + get_asset_by_name, get_subsets, - get_asset_name_identifier, ) -from ayon_core.lib.events import EventSystem +from ayon_core.lib.events import QueuedEventSystem from ayon_core.lib.attribute_definitions import ( UIDef, serialize_attr_defs, @@ -43,6 +42,7 @@ from ayon_core.pipeline.create.context import ( ConvertorsOperationFailed, ) from ayon_core.pipeline.publish import get_publish_instance_label +from ayon_core.tools.ayon_utils.models import HierarchyModel # Define constant for plugin orders offset PLUGIN_ORDER_OFFSET = 0.5 @@ -69,103 +69,19 @@ class MainThreadItem: class AssetDocsCache: """Cache asset documents for creation part.""" - projection = { - "_id": True, - "name": True, - "data.visualParent": True, - "data.tasks": True, - "data.parents": True, - } - def __init__(self, controller): self._controller = controller - self._asset_docs = None - self._asset_docs_hierarchy = None - self._task_names_by_asset_name = {} - self._asset_docs_by_name = {} - self._full_asset_docs_by_name = {} + self._asset_docs_by_path = {} def reset(self): - self._asset_docs = None - self._asset_docs_hierarchy = None - self._task_names_by_asset_name = {} - self._asset_docs_by_name = {} - self._full_asset_docs_by_name = {} + self._asset_docs_by_path = {} - def _query(self): - if self._asset_docs is not None: - return - - project_name = self._controller.project_name - asset_docs = list(get_assets( - project_name, fields=self.projection.keys() - )) - asset_docs_by_name = {} - task_names_by_asset_name = {} - for asset_doc in asset_docs: - if "data" not in asset_doc: - asset_doc["data"] = {"tasks": {}, "visualParent": None} - elif "tasks" not in asset_doc["data"]: - asset_doc["data"]["tasks"] = {} - - asset_name = get_asset_name_identifier(asset_doc) - asset_tasks = asset_doc["data"]["tasks"] - task_names_by_asset_name[asset_name] = list(asset_tasks.keys()) - asset_docs_by_name[asset_name] = asset_doc - - self._asset_docs = asset_docs - self._asset_docs_by_name = asset_docs_by_name - self._task_names_by_asset_name = task_names_by_asset_name - - def get_asset_docs(self): - self._query() - return copy.deepcopy(self._asset_docs) - - def get_asset_hierarchy(self): - """Prepare asset documents into hierarchy. - - Convert ObjectId to string. Asset id is not used during whole - process of publisher but asset name is used rather. - - Returns: - Dict[Union[str, None]: Any]: Mapping of parent id to it's children. - Top level assets have parent id 'None'. - """ - - if self._asset_docs_hierarchy is None: - _queue = collections.deque(self.get_asset_docs()) - - output = collections.defaultdict(list) - while _queue: - asset_doc = _queue.popleft() - asset_doc["_id"] = str(asset_doc["_id"]) - parent_id = asset_doc["data"]["visualParent"] - if parent_id is not None: - parent_id = str(parent_id) - asset_doc["data"]["visualParent"] = parent_id - output[parent_id].append(asset_doc) - self._asset_docs_hierarchy = output - return copy.deepcopy(self._asset_docs_hierarchy) - - def get_task_names_by_asset_name(self): - self._query() - return copy.deepcopy(self._task_names_by_asset_name) - - def get_asset_by_name(self, asset_name): - self._query() - asset_doc = self._asset_docs_by_name.get(asset_name) - if asset_doc is None: - return None - return copy.deepcopy(asset_doc) - - def get_full_asset_by_name(self, asset_name): - self._query() - if asset_name not in self._full_asset_docs_by_name: - asset_doc = self._asset_docs_by_name.get(asset_name) + def get_asset_doc_by_folder_path(self, folder_path): + if folder_path not in self._asset_docs_by_path: project_name = self._controller.project_name - full_asset_doc = get_asset_by_id(project_name, asset_doc["_id"]) - self._full_asset_docs_by_name[asset_name] = full_asset_doc - return copy.deepcopy(self._full_asset_docs_by_name[asset_name]) + asset_doc = get_asset_by_name(project_name, folder_path) + self._asset_docs_by_path[folder_path] = asset_doc + return copy.deepcopy(self._asset_docs_by_path[folder_path]) class PublishReportMaker: @@ -1036,13 +952,13 @@ class AbstractPublisherController(object): @property @abstractmethod - def current_asset_name(self): - """Current context asset name. + def current_folder_path(self): + """Current context folder path. Returns: - Union[str, None]: Name of asset. - """ + Union[str, None]: Folder path. + """ pass @property @@ -1106,19 +1022,7 @@ class AbstractPublisherController(object): pass @abstractmethod - def get_asset_docs(self): - pass - - @abstractmethod - def get_asset_hierarchy(self): - pass - - @abstractmethod - def get_task_names_by_asset_names(self, asset_names): - pass - - @abstractmethod - def get_existing_product_names(self, asset_name): + def get_existing_product_names(self, folder_path): pass @abstractmethod @@ -1158,7 +1062,7 @@ class AbstractPublisherController(object): creator_identifier, variant, task_name, - asset_name, + folder_path, instance_id=None ): """Get product name based on passed data. @@ -1168,7 +1072,7 @@ class AbstractPublisherController(object): responsible for product name creation. variant (str): Variant value from user's input. task_name (str): Name of task for which is instance created. - asset_name (str): Name of asset for which is instance created. + folder_path (str): Folder path for which is instance created. instance_id (Union[str, None]): Existing instance id when product name is updated. """ @@ -1187,7 +1091,7 @@ class AbstractPublisherController(object): creator_identifier (str): Identifier of Creator plugin. product_name (str): Calculated product name. instance_data (Dict[str, Any]): Base instance data with variant, - asset name and task name. + folder path and task name. options (Dict[str, Any]): Data from pre-create attributes. """ @@ -1500,13 +1404,22 @@ class BasePublisherController(AbstractPublisherController): """ if self._event_system is None: - self._event_system = EventSystem() + self._event_system = QueuedEventSystem() return self._event_system - def _emit_event(self, topic, data=None): + # Events system + def emit_event(self, topic, data=None, source=None): + """Use implemented event system to trigger event.""" + if data is None: data = {} - self.event_system.emit(topic, data, "controller") + self.event_system.emit(topic, data, source) + + def register_event_callback(self, topic, callback): + self.event_system.add_callback(topic, callback) + + def _emit_event(self, topic, data=None): + self.emit_event(topic, data, "controller") def _get_host_is_valid(self): return self._host_is_valid @@ -1739,6 +1652,7 @@ class PublisherController(BasePublisherController): self._resetting_instances = False # Cacher of avalon documents + self._hierarchy_model = HierarchyModel(self) self._asset_docs_cache = AssetDocsCache(self) @property @@ -1752,11 +1666,11 @@ class PublisherController(BasePublisherController): return self._create_context.get_current_project_name() @property - def current_asset_name(self): - """Current context asset name defined by host. + def current_folder_path(self): + """Current context folder path defined by host. Returns: - Union[str, None]: Asset name or None if asset is not set. + Union[str, None]: Folder path or None if folder is not set. """ return self._create_context.get_current_asset_name() @@ -1795,11 +1709,69 @@ class PublisherController(BasePublisherController): """Publish plugins.""" return self._create_context.publish_plugins - # --- Publish specific callbacks --- - def get_asset_docs(self): - """Get asset documents from cache for whole project.""" - return self._asset_docs_cache.get_asset_docs() + # Hierarchy model + def get_folder_items(self, project_name, sender=None): + return self._hierarchy_model.get_folder_items(project_name, sender) + def get_task_items(self, project_name, folder_id, sender=None): + return self._hierarchy_model.get_task_items( + project_name, folder_id, sender + ) + + def get_folder_entity(self, project_name, folder_id): + return self._hierarchy_model.get_folder_entity( + project_name, folder_id + ) + + def get_task_entity(self, project_name, task_id): + return self._hierarchy_model.get_task_entity(project_name, task_id) + + # Publisher custom method + def get_folder_id_from_path(self, folder_path): + if not folder_path: + return None + folder_item = self._hierarchy_model.get_folder_item_by_path( + self.project_name, folder_path + ) + if folder_item: + return folder_item.entity_id + return None + + def get_task_names_by_folder_paths(self, folder_paths): + if not folder_paths: + return {} + folder_items = self._hierarchy_model.get_folder_items_by_paths( + self.project_name, folder_paths + ) + output = { + folder_path: set() + for folder_path in folder_paths + } + project_name = self.project_name + for folder_item in folder_items.values(): + task_items = self._hierarchy_model.get_task_items( + project_name, folder_item.entity_id, None + ) + output[folder_item.path] = { + task_item.name + for task_item in task_items + } + + return output + + def are_folder_paths_valid(self, folder_paths): + if not folder_paths: + return True + folder_paths = set(folder_paths) + folder_items = self._hierarchy_model.get_folder_items_by_paths( + self.project_name, folder_paths + ) + for folder_item in folder_items.values(): + if folder_item is None: + return False + return True + + # --- Publish specific callbacks --- def get_context_title(self): """Get context title for artist shown at the top of main window.""" @@ -1814,32 +1786,20 @@ class PublisherController(BasePublisherController): return context_title - def get_asset_hierarchy(self): - """Prepare asset documents into hierarchy.""" - - return self._asset_docs_cache.get_asset_hierarchy() - - def get_task_names_by_asset_names(self, asset_names): - """Prepare task names by asset name.""" - task_names_by_asset_name = ( - self._asset_docs_cache.get_task_names_by_asset_name() - ) - result = {} - for asset_name in asset_names: - result[asset_name] = set( - task_names_by_asset_name.get(asset_name) or [] - ) - return result - - def get_existing_product_names(self, asset_name): + def get_existing_product_names(self, folder_path): + if not folder_path: + return None project_name = self.project_name - asset_doc = self._asset_docs_cache.get_asset_by_name(asset_name) - if not asset_doc: + folder_item = self._hierarchy_model.get_folder_item_by_path( + project_name, folder_path + ) + if not folder_item: return None - asset_id = asset_doc["_id"] subset_docs = get_subsets( - project_name, asset_ids=[asset_id], fields=["name"] + project_name, + asset_ids=[folder_item.entity_id], + fields=["name"] ) return { subset_doc["name"] @@ -1859,6 +1819,7 @@ class PublisherController(BasePublisherController): # Reset avalon context self._create_context.reset_current_context() + self._hierarchy_model.reset() self._asset_docs_cache.reset() self._reset_plugins() @@ -2075,7 +2036,7 @@ class PublisherController(BasePublisherController): creator_identifier, variant, task_name, - asset_name, + folder_path, instance_id=None ): """Get product name based on passed data. @@ -2085,14 +2046,16 @@ class PublisherController(BasePublisherController): responsible for product name creation. variant (str): Variant value from user's input. task_name (str): Name of task for which is instance created. - asset_name (str): Name of asset for which is instance created. + folder_path (str): Folder path for which is instance created. instance_id (Union[str, None]): Existing instance id when product name is updated. """ creator = self._creators[creator_identifier] project_name = self.project_name - asset_doc = self._asset_docs_cache.get_full_asset_by_name(asset_name) + asset_doc = self._asset_docs_cache.get_asset_doc_by_folder_path( + folder_path + ) instance = None if instance_id: instance = self.instances[instance_id] diff --git a/client/ayon_core/tools/publisher/control_qt.py b/client/ayon_core/tools/publisher/control_qt.py index 2ddd676ec3..ee08899cac 100644 --- a/client/ayon_core/tools/publisher/control_qt.py +++ b/client/ayon_core/tools/publisher/control_qt.py @@ -212,13 +212,13 @@ class QtRemotePublishController(BasePublisherController): pass @abstractproperty - def current_asset_name(self): - """Current context asset name from client. + def current_folder_path(self): + """Current context folder path from host. Returns: - Union[str, None]: Name of asset. - """ + Union[str, None]: Folder path. + """ pass @abstractproperty @@ -251,16 +251,7 @@ class QtRemotePublishController(BasePublisherController): pass - def get_asset_docs(self): - pass - - def get_asset_hierarchy(self): - pass - - def get_task_names_by_asset_names(self, asset_names): - pass - - def get_existing_product_names(self, asset_name): + def get_existing_product_names(self, folder_path): pass @property @@ -305,7 +296,7 @@ class QtRemotePublishController(BasePublisherController): creator_identifier, variant, task_name, - asset_name, + folder_path, instance_id=None ): """Get product name based on passed data. @@ -315,7 +306,7 @@ class QtRemotePublishController(BasePublisherController): responsible for product name creation. variant (str): Variant value from user's input. task_name (str): Name of task for which is instance created. - asset_name (str): Name of asset for which is instance created. + folder_path (str): Folder path for which is instance created. instance_id (Union[str, None]): Existing instance id when product name is updated. """ @@ -334,7 +325,7 @@ class QtRemotePublishController(BasePublisherController): creator_identifier (str): Identifier of Creator plugin. product_name (str): Calculated product name. instance_data (Dict[str, Any]): Base instance data with variant, - asset name and task name. + folder path and task name. options (Dict[str, Any]): Data from pre-create attributes. """ diff --git a/client/ayon_core/tools/publisher/widgets/assets_widget.py b/client/ayon_core/tools/publisher/widgets/assets_widget.py deleted file mode 100644 index 1c5016de99..0000000000 --- a/client/ayon_core/tools/publisher/widgets/assets_widget.py +++ /dev/null @@ -1,363 +0,0 @@ -import collections - -from qtpy import QtWidgets, QtCore, QtGui - -from ayon_core.tools.utils import ( - PlaceholderLineEdit, - RecursiveSortFilterProxyModel, -) -from ayon_core.tools.utils.assets_widget import ( - SingleSelectAssetsWidget, - ASSET_ID_ROLE, - ASSET_NAME_ROLE, - ASSET_PATH_ROLE, - get_asset_icon, -) - - -class CreateWidgetAssetsWidget(SingleSelectAssetsWidget): - current_context_required = QtCore.Signal() - header_height_changed = QtCore.Signal(int) - - def __init__(self, controller, parent): - self._controller = controller - super(CreateWidgetAssetsWidget, self).__init__(parent) - - self.set_refresh_btn_visibility(False) - self.set_current_asset_btn_visibility(False) - - self._last_selection = None - self._enabled = None - - self._last_filter_height = None - - def get_project_name(self): - return self._controller.project_name - - def get_selected_asset_name(self): - selection_model = self._view.selectionModel() - indexes = selection_model.selectedRows() - for index in indexes: - return index.data(ASSET_PATH_ROLE) - return None - - def _check_header_height(self): - """Catch header height changes. - - Label on top of creaters should have same height so Creators view has - same offset. - """ - height = self.header_widget.height() - if height != self._last_filter_height: - self._last_filter_height = height - self.header_height_changed.emit(height) - - def resizeEvent(self, event): - super(CreateWidgetAssetsWidget, self).resizeEvent(event) - self._check_header_height() - - def showEvent(self, event): - super(CreateWidgetAssetsWidget, self).showEvent(event) - self._check_header_height() - - def _on_current_asset_click(self): - self.current_context_required.emit() - - def set_enabled(self, enabled): - if self._enabled == enabled: - return - self._enabled = enabled - if not enabled: - self._last_selection = self.get_selected_asset_id() - self._clear_selection() - elif self._last_selection is not None: - self.select_asset(self._last_selection) - - def _select_indexes(self, *args, **kwargs): - super(CreateWidgetAssetsWidget, self)._select_indexes(*args, **kwargs) - if self._enabled: - return - self._last_selection = self.get_selected_asset_id() - self._clear_selection() - - def update_current_asset(self): - # Hide set current asset if there is no one - asset_name = self._get_current_asset_name() - self.set_current_asset_btn_visibility(bool(asset_name)) - - def _get_current_asset_name(self): - return self._controller.current_asset_name - - def _create_source_model(self): - return AssetsHierarchyModel(self._controller) - - def _refresh_model(self): - self._model.reset() - self._on_model_refresh(self._model.rowCount() > 0) - - -class AssetsHierarchyModel(QtGui.QStandardItemModel): - """Assets hierarchy model. - - For selecting asset for which an instance should be created. - - Uses controller to load asset hierarchy. All asset documents are stored by - their parents. - """ - - def __init__(self, controller): - super(AssetsHierarchyModel, self).__init__() - self._controller = controller - - self._items_by_name = {} - self._items_by_path = {} - self._items_by_asset_id = {} - - def reset(self): - self.clear() - - self._items_by_name = {} - self._items_by_path = {} - self._items_by_asset_id = {} - assets_by_parent_id = self._controller.get_asset_hierarchy() - - items_by_name = {} - items_by_path = {} - items_by_asset_id = {} - _queue = collections.deque() - _queue.append((self.invisibleRootItem(), None, None)) - while _queue: - parent_item, parent_id, parent_path = _queue.popleft() - children = assets_by_parent_id.get(parent_id) - if not children: - continue - - children_by_name = { - child["name"]: child - for child in children - } - items = [] - for name in sorted(children_by_name.keys()): - child = children_by_name[name] - child_id = child["_id"] - if parent_path: - child_path = "{}/{}".format(parent_path, name) - else: - child_path = "/{}".format(name) - - has_children = bool(assets_by_parent_id.get(child_id)) - icon = get_asset_icon(child, has_children) - - item = QtGui.QStandardItem(name) - item.setFlags( - QtCore.Qt.ItemIsEnabled - | QtCore.Qt.ItemIsSelectable - ) - item.setData(icon, QtCore.Qt.DecorationRole) - item.setData(child_id, ASSET_ID_ROLE) - item.setData(name, ASSET_NAME_ROLE) - item.setData(child_path, ASSET_PATH_ROLE) - - items_by_name[name] = item - items_by_path[child_path] = item - items_by_asset_id[child_id] = item - items.append(item) - _queue.append((item, child_id, child_path)) - - parent_item.appendRows(items) - - self._items_by_name = items_by_name - self._items_by_path = items_by_path - self._items_by_asset_id = items_by_asset_id - - def get_index_by_asset_id(self, asset_id): - item = self._items_by_asset_id.get(asset_id) - if item is not None: - return item.index() - return QtCore.QModelIndex() - - def get_index_by_asset_name(self, asset_name): - item = self._items_by_path.get(asset_name) - if item is None: - item = self._items_by_name.get(asset_name) - - if item is None: - return QtCore.QModelIndex() - return item.index() - - def name_is_valid(self, item_name): - return item_name in self._items_by_path - - -class AssetDialogView(QtWidgets.QTreeView): - double_clicked = QtCore.Signal(QtCore.QModelIndex) - - def mouseDoubleClickEvent(self, event): - index = self.indexAt(event.pos()) - if index.isValid(): - self.double_clicked.emit(index) - event.accept() - - -class AssetsDialog(QtWidgets.QDialog): - """Dialog to select asset for a context of instance.""" - - def __init__(self, controller, parent): - super(AssetsDialog, self).__init__(parent) - self.setWindowTitle("Select asset") - - model = AssetsHierarchyModel(controller) - proxy_model = RecursiveSortFilterProxyModel() - proxy_model.setSourceModel(model) - proxy_model.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) - - filter_input = PlaceholderLineEdit(self) - filter_input.setPlaceholderText("Filter folders..") - - asset_view = AssetDialogView(self) - asset_view.setModel(proxy_model) - asset_view.setHeaderHidden(True) - asset_view.setFrameShape(QtWidgets.QFrame.NoFrame) - asset_view.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) - asset_view.setAlternatingRowColors(True) - asset_view.setSelectionBehavior(QtWidgets.QTreeView.SelectRows) - asset_view.setAllColumnsShowFocus(True) - - ok_btn = QtWidgets.QPushButton("OK", self) - cancel_btn = QtWidgets.QPushButton("Cancel", self) - - btns_layout = QtWidgets.QHBoxLayout() - btns_layout.addStretch(1) - btns_layout.addWidget(ok_btn) - btns_layout.addWidget(cancel_btn) - - layout = QtWidgets.QVBoxLayout(self) - layout.addWidget(filter_input, 0) - layout.addWidget(asset_view, 1) - layout.addLayout(btns_layout, 0) - - controller.event_system.add_callback( - "controller.reset.finished", self._on_controller_reset - ) - - asset_view.double_clicked.connect(self._on_ok_clicked) - filter_input.textChanged.connect(self._on_filter_change) - ok_btn.clicked.connect(self._on_ok_clicked) - cancel_btn.clicked.connect(self._on_cancel_clicked) - - self._filter_input = filter_input - self._ok_btn = ok_btn - self._cancel_btn = cancel_btn - - self._model = model - self._proxy_model = proxy_model - - self._asset_view = asset_view - - self._selected_asset = None - # Soft refresh is enabled - # - reset will happen at all cost if soft reset is enabled - # - adds ability to call reset on multiple places without repeating - self._soft_reset_enabled = True - - self._first_show = True - self._default_height = 500 - - def _on_first_show(self): - center = self.rect().center() - size = self.size() - size.setHeight(self._default_height) - - self.resize(size) - new_pos = self.mapToGlobal(center) - new_pos.setX(new_pos.x() - int(self.width() / 2)) - new_pos.setY(new_pos.y() - int(self.height() / 2)) - self.move(new_pos) - - def _on_controller_reset(self): - # Change reset enabled so model is reset on show event - self._soft_reset_enabled = True - - def showEvent(self, event): - """Refresh asset model on show.""" - super(AssetsDialog, self).showEvent(event) - if self._first_show: - self._first_show = False - self._on_first_show() - # Refresh on show - self.reset(False) - - def reset(self, force=True): - """Reset asset model.""" - if not force and not self._soft_reset_enabled: - return - - if self._soft_reset_enabled: - self._soft_reset_enabled = False - - self._model.reset() - - def name_is_valid(self, name): - """Is asset name valid. - - Args: - name(str): Asset name that should be checked. - """ - # Make sure we're reset - self.reset(False) - # Valid the name by model - return self._model.name_is_valid(name) - - def _on_filter_change(self, text): - """Trigger change of filter of assets.""" - self._proxy_model.setFilterFixedString(text) - - def _on_cancel_clicked(self): - self.done(0) - - def _on_ok_clicked(self): - index = self._asset_view.currentIndex() - asset_name = None - if index.isValid(): - asset_name = index.data(ASSET_PATH_ROLE) - self._selected_asset = asset_name - self.done(1) - - def set_selected_assets(self, asset_names): - """Change preselected asset before showing the dialog. - - This also resets model and clean filter. - """ - self.reset(False) - self._asset_view.collapseAll() - self._filter_input.setText("") - - indexes = [] - for asset_name in asset_names: - index = self._model.get_index_by_asset_name(asset_name) - if index.isValid(): - indexes.append(index) - - if not indexes: - return - - index_deque = collections.deque() - for index in indexes: - index_deque.append(index) - - all_indexes = [] - while index_deque: - index = index_deque.popleft() - all_indexes.append(index) - - parent_index = index.parent() - if parent_index.isValid(): - index_deque.append(parent_index) - - for index in all_indexes: - proxy_index = self._proxy_model.mapFromSource(index) - self._asset_view.expand(proxy_index) - - def get_selected_asset(self): - """Get selected asset name.""" - return self._selected_asset diff --git a/client/ayon_core/tools/publisher/widgets/create_context_widgets.py b/client/ayon_core/tools/publisher/widgets/create_context_widgets.py new file mode 100644 index 0000000000..d65a2ace8d --- /dev/null +++ b/client/ayon_core/tools/publisher/widgets/create_context_widgets.py @@ -0,0 +1,296 @@ +from qtpy import QtWidgets, QtCore, QtGui + +from ayon_core.lib.events import QueuedEventSystem +from ayon_core.tools.utils import PlaceholderLineEdit, GoToCurrentButton + +from ayon_core.tools.ayon_utils.models import HierarchyExpectedSelection +from ayon_core.tools.ayon_utils.widgets import FoldersWidget, TasksWidget + + +class CreateSelectionModel(object): + """Model handling selection changes. + + Triggering events: + - "selection.project.changed" + - "selection.folder.changed" + - "selection.task.changed" + """ + + event_source = "publisher.create.selection.model" + + def __init__(self, controller): + self._controller = controller + + self._project_name = None + self._folder_id = None + self._task_name = None + self._task_id = None + + def get_selected_project_name(self): + return self._project_name + + def set_selected_project(self, project_name): + if project_name == self._project_name: + return + + self._project_name = project_name + self._controller.emit_event( + "selection.project.changed", + {"project_name": project_name}, + self.event_source + ) + + def get_selected_folder_id(self): + return self._folder_id + + def set_selected_folder(self, folder_id): + if folder_id == self._folder_id: + return + + self._folder_id = folder_id + self._controller.emit_event( + "selection.folder.changed", + { + "project_name": self._project_name, + "folder_id": folder_id, + }, + self.event_source + ) + + def get_selected_task_name(self): + return self._task_name + + def get_selected_task_id(self): + return self._task_id + + def set_selected_task(self, task_id, task_name): + if task_id == self._task_id: + return + + self._task_name = task_name + self._task_id = task_id + self._controller.emit_event( + "selection.task.changed", + { + "project_name": self._project_name, + "folder_id": self._folder_id, + "task_name": task_name, + "task_id": task_id, + }, + self.event_source + ) + + +class CreateHierarchyController: + """Controller for hierarchy widgets. + + Helper for handling hierarchy widgets in create tab. It handles selection + of folder and task to properly propagate it to other widgets. + + At the same time handles expected selection so can pre-select folder and + task based on current context. + + Args: + controller (PublisherController): Publisher controller. + + """ + def __init__(self, controller): + self._event_system = QueuedEventSystem() + self._controller = controller + self._selection_model = CreateSelectionModel(self) + self._expected_selection = HierarchyExpectedSelection( + self, handle_project=False + ) + + # Events system + @property + def event_system(self): + return self._event_system + + def emit_event(self, topic, data=None, source=None): + """Use implemented event system to trigger event.""" + + if data is None: + data = {} + self.event_system.emit(topic, data, source) + + def register_event_callback(self, topic, callback): + self.event_system.add_callback(topic, callback) + + def get_project_name(self): + return self._controller.project_name + + def get_folder_items(self, project_name, sender=None): + return self._controller.get_folder_items(project_name, sender) + + def get_task_items(self, project_name, folder_id, sender=None): + return self._controller.get_task_items( + project_name, folder_id, sender + ) + + # Selection model + def set_selected_project(self, project_name): + self._selection_model.set_selected_project(project_name) + + def set_selected_folder(self, folder_id): + self._selection_model.set_selected_folder(folder_id) + + def set_selected_task(self, task_id, task_name): + self._selection_model.set_selected_task(task_id, task_name) + + # Expected selection + def get_expected_selection_data(self): + return self._expected_selection.get_expected_selection_data() + + def set_expected_selection(self, project_name, folder_id, task_name): + self._expected_selection.set_expected_selection( + project_name, folder_id, task_name + ) + + def expected_folder_selected(self, folder_id): + self._expected_selection.expected_folder_selected(folder_id) + + def expected_task_selected(self, folder_id, task_name): + self._expected_selection.expected_task_selected(folder_id, task_name) + + +class CreateContextWidget(QtWidgets.QWidget): + folder_changed = QtCore.Signal() + task_changed = QtCore.Signal() + + def __init__(self, controller, parent): + super(CreateContextWidget, self).__init__(parent) + + self._controller = controller + self._enabled = True + self._last_project_name = None + self._last_folder_id = None + self._last_selected_task_name = None + + headers_widget = QtWidgets.QWidget(self) + + folder_filter_input = PlaceholderLineEdit(headers_widget) + folder_filter_input.setPlaceholderText("Filter folders..") + + current_context_btn = GoToCurrentButton(headers_widget) + current_context_btn.setToolTip("Go to current context") + current_context_btn.setVisible(False) + + headers_layout = QtWidgets.QHBoxLayout(headers_widget) + headers_layout.setContentsMargins(0, 0, 0, 0) + headers_layout.addWidget(folder_filter_input, 1) + headers_layout.addWidget(current_context_btn, 0) + + hierarchy_controller = CreateHierarchyController(controller) + + folders_widget = FoldersWidget( + hierarchy_controller, self, handle_expected_selection=True + ) + folders_widget.set_deselectable(True) + + tasks_widget = TasksWidget( + hierarchy_controller, self, handle_expected_selection=True + ) + + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + main_layout.addWidget(headers_widget, 0) + main_layout.addWidget(folders_widget, 2) + main_layout.addWidget(tasks_widget, 1) + + folders_widget.selection_changed.connect(self._on_folder_change) + tasks_widget.selection_changed.connect(self._on_task_change) + current_context_btn.clicked.connect(self._on_current_context_click) + folder_filter_input.textChanged.connect(self._on_folder_filter_change) + + self._folder_filter_input = folder_filter_input + self._current_context_btn = current_context_btn + self._folders_widget = folders_widget + self._tasks_widget = tasks_widget + self._hierarchy_controller = hierarchy_controller + + def get_selected_folder_id(self): + return self._folders_widget.get_selected_folder_id() + + def get_selected_folder_path(self): + return self._folders_widget.get_selected_folder_path() + + def get_selected_task_name(self): + return self._tasks_widget.get_selected_task_name() + + def get_selected_task_type(self): + return self._tasks_widget.get_selected_task_type() + + def update_current_context_btn(self): + # Hide set current folder if there is no one + folder_path = self._controller.current_folder_path + self._current_context_btn.setVisible(bool(folder_path)) + + def set_selected_context(self, folder_id, task_name): + self._hierarchy_controller.set_expected_selection( + self._controller.project_name, + folder_id, + task_name + ) + + def is_enabled(self): + return self._enabled + + def set_enabled(self, enabled): + if enabled is self._enabled: + return + + self.setEnabled(enabled) + self._enabled = enabled + + if not enabled: + self._last_folder_id = self.get_selected_folder_id() + self._folders_widget.set_selected_folder(None) + last_selected_task_name = self.get_selected_task_name() + if last_selected_task_name: + self._last_selected_task_name = last_selected_task_name + self._clear_selection() + + elif self._last_selected_task_name is not None: + self._hierarchy_controller.set_expected_selection( + self._last_project_name, + self._last_folder_id, + self._last_selected_task_name + ) + + def refresh(self): + self._last_project_name = self._controller.project_name + folder_id = self._last_folder_id + task_name = self._last_selected_task_name + if folder_id is None: + folder_path = self._controller.current_folder_path + folder_id = self._controller.get_folder_id_from_path(folder_path) + task_name = self._controller.current_task_name + self._hierarchy_controller.set_selected_project( + self._last_project_name + ) + self._folders_widget.set_project_name(self._last_project_name) + self._hierarchy_controller.set_expected_selection( + self._last_project_name, folder_id, task_name + ) + + def _clear_selection(self): + self._folders_widget.set_selected_folder(None) + + def _on_folder_change(self): + self.folder_changed.emit() + + def _on_task_change(self): + self.task_changed.emit() + + def _on_current_context_click(self): + folder_path = self._controller.current_folder_path + task_name = self._controller.current_task_name + folder_id = self._controller.get_folder_id_from_path(folder_path) + self._hierarchy_controller.set_expected_selection( + self._last_project_name, folder_id, task_name + ) + + def _on_folder_filter_change(self, text): + self._folders_widget.set_name_filter(text) diff --git a/client/ayon_core/tools/publisher/widgets/create_widget.py b/client/ayon_core/tools/publisher/widgets/create_widget.py index e573e554d8..2e4ca34138 100644 --- a/client/ayon_core/tools/publisher/widgets/create_widget.py +++ b/client/ayon_core/tools/publisher/widgets/create_widget.py @@ -14,8 +14,7 @@ from .widgets import ( IconValuePixmapLabel, CreateBtn, ) -from .assets_widget import CreateWidgetAssetsWidget -from .tasks_widget import CreateWidgetTasksWidget +from .create_context_widgets import CreateContextWidget from .precreate_widget import PreCreateWidget from ..constants import ( VARIANT_TOOLTIP, @@ -109,7 +108,7 @@ class CreateWidget(QtWidgets.QWidget): self._controller = controller - self._asset_name = None + self._folder_path = None self._product_names = None self._selected_creator = None @@ -121,16 +120,7 @@ class CreateWidget(QtWidgets.QWidget): main_splitter_widget = QtWidgets.QSplitter(self) - context_widget = QtWidgets.QWidget(main_splitter_widget) - - assets_widget = CreateWidgetAssetsWidget(controller, context_widget) - tasks_widget = CreateWidgetTasksWidget(controller, context_widget) - - context_layout = QtWidgets.QVBoxLayout(context_widget) - context_layout.setContentsMargins(0, 0, 0, 0) - context_layout.setSpacing(0) - context_layout.addWidget(assets_widget, 2) - context_layout.addWidget(tasks_widget, 1) + context_widget = CreateContextWidget(controller, main_splitter_widget) # --- Creators view --- creators_widget = QtWidgets.QWidget(main_splitter_widget) @@ -279,11 +269,8 @@ class CreateWidget(QtWidgets.QWidget): ) variant_hints_btn.clicked.connect(self._on_variant_btn_click) variant_hints_menu.triggered.connect(self._on_variant_action) - assets_widget.selection_changed.connect(self._on_asset_change) - assets_widget.current_context_required.connect( - self._on_current_session_context_request - ) - tasks_widget.task_changed.connect(self._on_task_change) + context_widget.folder_changed.connect(self._on_folder_change) + context_widget.task_changed.connect(self._on_task_change) thumbnail_widget.thumbnail_created.connect(self._on_thumbnail_create) thumbnail_widget.thumbnail_cleared.connect(self._on_thumbnail_clear) @@ -299,8 +286,6 @@ class CreateWidget(QtWidgets.QWidget): self._creators_splitter = creators_splitter self._context_widget = context_widget - self._assets_widget = assets_widget - self._tasks_widget = tasks_widget self.product_name_input = product_name_input @@ -324,47 +309,51 @@ class CreateWidget(QtWidgets.QWidget): self._first_show = True self._last_thumbnail_path = None - self._last_current_context_asset = None + self._last_current_context_folder_path = None self._last_current_context_task = None self._use_current_context = True @property - def current_asset_name(self): - return self._controller.current_asset_name + def current_folder_path(self): + return self._controller.current_folder_path @property def current_task_name(self): return self._controller.current_task_name def _context_change_is_enabled(self): - return self._context_widget.isEnabled() + return self._context_widget.is_enabled() - def _get_asset_name(self): - asset_name = None + def _get_folder_path(self): + folder_path = None if self._context_change_is_enabled(): - asset_name = self._assets_widget.get_selected_asset_name() + folder_path = self._context_widget.get_selected_folder_path() - if asset_name is None: - asset_name = self.current_asset_name - return asset_name or None + if folder_path is None: + folder_path = self.current_folder_path + return folder_path or None + + def _get_folder_id(self): + folder_id = None + if self._context_widget.is_enabled(): + folder_id = self._context_widget.get_selected_folder_id() + return folder_id def _get_task_name(self): task_name = None if self._context_change_is_enabled(): - # Don't use selection of task if asset is not set - asset_name = self._assets_widget.get_selected_asset_name() - if asset_name: - task_name = self._tasks_widget.get_selected_task_name() + # Don't use selection of task if folder is not set + folder_path = self._context_widget.get_selected_folder_path() + if folder_path: + task_name = self._context_widget.get_selected_task_name() if not task_name: task_name = self.current_task_name return task_name def _set_context_enabled(self, enabled): - self._assets_widget.set_enabled(enabled) - self._tasks_widget.set_enabled(enabled) - check_prereq = self._context_widget.isEnabled() != enabled - self._context_widget.setEnabled(enabled) + check_prereq = self._context_widget.is_enabled() != enabled + self._context_widget.set_enabled(enabled) if check_prereq: self._invalidate_prereq() @@ -375,12 +364,12 @@ class CreateWidget(QtWidgets.QWidget): self._use_current_context = True def refresh(self): - current_asset_name = self._controller.current_asset_name + current_folder_path = self._controller.current_folder_path current_task_name = self._controller.current_task_name - # Get context before refresh to keep selection of asset and + # Get context before refresh to keep selection of folder and # task widgets - asset_name = self._get_asset_name() + folder_path = self._get_folder_path() task_name = self._get_task_name() # Replace by current context if last loaded context was @@ -388,37 +377,36 @@ class CreateWidget(QtWidgets.QWidget): if ( self._use_current_context or ( - self._last_current_context_asset - and asset_name == self._last_current_context_asset + self._last_current_context_folder_path + and folder_path == self._last_current_context_folder_path and task_name == self._last_current_context_task ) ): - asset_name = current_asset_name + folder_path = current_folder_path task_name = current_task_name # Store values for future refresh - self._last_current_context_asset = current_asset_name + self._last_current_context_folder_path = current_folder_path self._last_current_context_task = current_task_name self._use_current_context = False self._prereq_available = False - # Disable context widget so refresh of asset will use context asset - # name + # Disable context widget so refresh of folder will use context folder + # path self._set_context_enabled(False) - self._assets_widget.refresh() - # Refresh data before update of creators - self._refresh_asset() + self._context_widget.refresh() + self._refresh_product_name() + # Then refresh creators which may trigger callbacks using refreshed # data self._refresh_creators() - self._assets_widget.update_current_asset() - self._assets_widget.select_asset_by_name(asset_name) - self._tasks_widget.set_asset_name(asset_name) - self._tasks_widget.select_task_name(task_name) + folder_id = self._controller.get_folder_id_from_path(folder_path) + self._context_widget.update_current_context_btn() + self._context_widget.set_selected_context(folder_id, task_name) self._invalidate_prereq_deffered() @@ -439,9 +427,9 @@ class CreateWidget(QtWidgets.QWidget): if ( self._context_change_is_enabled() - and self._get_asset_name() is None + and self._get_folder_path() is None ): - # QUESTION how to handle invalid asset? + # QUESTION how to handle invalid folder? prereq_available = False creator_btn_tooltips.append("Context is not selected") @@ -460,24 +448,26 @@ class CreateWidget(QtWidgets.QWidget): self._on_variant_change() - def _refresh_asset(self): - asset_name = self._get_asset_name() + def _refresh_product_name(self): + folder_path = self._get_folder_path() - # Skip if asset did not change - if self._asset_name and self._asset_name == asset_name: + # Skip if folder did not change + if self._folder_path and self._folder_path == folder_path: return - # Make sure `_asset_name` and `_product_names` variables are reset - self._asset_name = asset_name + # Make sure `_folder_path` and `_product_names` variables are reset + self._folder_path = folder_path self._product_names = None - if asset_name is None: + if folder_path is None: return - product_names = self._controller.get_existing_product_names(asset_name) + product_names = self._controller.get_existing_product_names( + folder_path + ) self._product_names = product_names if product_names is None: - self.product_name_input.setText("< Asset is not set >") + self.product_name_input.setText("< Folder is not set >") def _refresh_creators(self): # Refresh creators and add their product types to list @@ -545,11 +535,8 @@ class CreateWidget(QtWidgets.QWidget): # Trigger refresh only if is visible self.refresh() - def _on_asset_change(self): - self._refresh_asset() - - asset_name = self._assets_widget.get_selected_asset_name() - self._tasks_widget.set_asset_name(asset_name) + def _on_folder_change(self): + self._refresh_product_name() if self._context_change_is_enabled(): self._invalidate_prereq_deffered() @@ -564,12 +551,6 @@ class CreateWidget(QtWidgets.QWidget): def _on_thumbnail_clear(self): self._last_thumbnail_path = None - def _on_current_session_context_request(self): - self._assets_widget.select_current_asset() - task_name = self.current_task_name - if task_name: - self._tasks_widget.select_task_name(task_name) - def _on_creator_item_change(self, new_index, _old_index): identifier = None if new_index.isValid(): @@ -616,7 +597,7 @@ class CreateWidget(QtWidgets.QWidget): != self._context_change_is_enabled() ): self._set_context_enabled(creator_item.create_allow_context_change) - self._refresh_asset() + self._refresh_product_name() self._thumbnail_widget.setVisible( creator_item.create_allow_thumbnail @@ -685,13 +666,13 @@ class CreateWidget(QtWidgets.QWidget): self.product_name_input.setText("< Valid variant >") return - asset_name = self._get_asset_name() + folder_path = self._get_folder_path() task_name = self._get_task_name() creator_idenfier = self._selected_creator.identifier # Calculate product name with Creator plugin try: product_name = self._controller.get_product_name( - creator_idenfier, variant_value, task_name, asset_name + creator_idenfier, variant_value, task_name, folder_path ) except TaskNotSetError: self._create_btn.setEnabled(False) @@ -705,7 +686,7 @@ class CreateWidget(QtWidgets.QWidget): self._validate_product_name(product_name, variant_value) def _validate_product_name(self, product_name, variant_value): - # Get all products of the current asset + # Get all products of the current folder if self._product_names: existing_product_names = set(self._product_names) else: @@ -798,11 +779,11 @@ class CreateWidget(QtWidgets.QWidget): variant = self.variant_input.text() # Care about product name only if context change is enabled product_name = None - asset_name = None + folder_path = None task_name = None if self._context_change_is_enabled(): product_name = self.product_name_input.text() - asset_name = self._get_asset_name() + folder_path = self._get_folder_path() task_name = self._get_task_name() pre_create_data = self._pre_create_widget.current_value() @@ -814,7 +795,7 @@ class CreateWidget(QtWidgets.QWidget): # Where to define these data? # - what data show be stored? instance_data = { - "folderPath": asset_name, + "folderPath": folder_path, "task": task_name, "variant": variant, "productType": product_type diff --git a/client/ayon_core/tools/publisher/widgets/folders_dialog.py b/client/ayon_core/tools/publisher/widgets/folders_dialog.py new file mode 100644 index 0000000000..8f93264b2e --- /dev/null +++ b/client/ayon_core/tools/publisher/widgets/folders_dialog.py @@ -0,0 +1,151 @@ +from qtpy import QtWidgets, QtCore, QtGui + +from ayon_core.lib.events import QueuedEventSystem +from ayon_core.tools.ayon_utils.widgets import FoldersWidget +from ayon_core.tools.utils import PlaceholderLineEdit + + +class FoldersDialogController: + def __init__(self, controller): + self._event_system = QueuedEventSystem() + self._controller = controller + + @property + def event_system(self): + return self._event_system + + def emit_event(self, topic, data=None, source=None): + """Use implemented event system to trigger event.""" + + if data is None: + data = {} + self.event_system.emit(topic, data, source) + + def register_event_callback(self, topic, callback): + self.event_system.add_callback(topic, callback) + + def get_folder_items(self, project_name, sender=None): + return self._controller.get_folder_items(project_name, sender) + + def set_selected_folder(self, folder_id): + pass + + +class FoldersDialog(QtWidgets.QDialog): + """Dialog to select folder for a context of instance.""" + + def __init__(self, controller, parent): + super(FoldersDialog, self).__init__(parent) + self.setWindowTitle("Select folder") + + filter_input = PlaceholderLineEdit(self) + filter_input.setPlaceholderText("Filter folders..") + + folders_controller = FoldersDialogController(controller) + folders_widget = FoldersWidget(folders_controller, self) + folders_widget.set_deselectable(True) + + ok_btn = QtWidgets.QPushButton("OK", self) + cancel_btn = QtWidgets.QPushButton("Cancel", self) + + btns_layout = QtWidgets.QHBoxLayout() + btns_layout.addStretch(1) + btns_layout.addWidget(ok_btn) + btns_layout.addWidget(cancel_btn) + + layout = QtWidgets.QVBoxLayout(self) + layout.addWidget(filter_input, 0) + layout.addWidget(folders_widget, 1) + layout.addLayout(btns_layout, 0) + + controller.event_system.add_callback( + "controller.reset.finished", self._on_controller_reset + ) + + folders_widget.double_clicked.connect(self._on_ok_clicked) + filter_input.textChanged.connect(self._on_filter_change) + ok_btn.clicked.connect(self._on_ok_clicked) + cancel_btn.clicked.connect(self._on_cancel_clicked) + + self._controller = controller + self._filter_input = filter_input + self._ok_btn = ok_btn + self._cancel_btn = cancel_btn + + self._folders_widget = folders_widget + + self._selected_folder_path = None + # Soft refresh is enabled + # - reset will happen at all cost if soft reset is enabled + # - adds ability to call reset on multiple places without repeating + self._soft_reset_enabled = True + + self._first_show = True + self._default_height = 500 + + def _on_first_show(self): + center = self.rect().center() + size = self.size() + size.setHeight(self._default_height) + + self.resize(size) + new_pos = self.mapToGlobal(center) + new_pos.setX(new_pos.x() - int(self.width() / 2)) + new_pos.setY(new_pos.y() - int(self.height() / 2)) + self.move(new_pos) + + def _on_controller_reset(self): + # Change reset enabled so model is reset on show event + self._soft_reset_enabled = True + + def showEvent(self, event): + """Refresh folders widget on show.""" + super(FoldersDialog, self).showEvent(event) + if self._first_show: + self._first_show = False + self._on_first_show() + # Refresh on show + self.reset(False) + + def reset(self, force=True): + """Reset widget.""" + if not force and not self._soft_reset_enabled: + return + + if self._soft_reset_enabled: + self._soft_reset_enabled = False + + self._folders_widget.set_project_name(self._controller.project_name) + + def _on_filter_change(self, text): + """Trigger change of filter of folders.""" + self._folders_widget.set_name_filter(text) + + def _on_cancel_clicked(self): + self.done(0) + + def _on_ok_clicked(self): + self._selected_folder_path = ( + self._folders_widget.get_selected_folder_path() + ) + self.done(1) + + def set_selected_folders(self, folder_paths): + """Change preselected folder before showing the dialog. + + This also resets model and clean filter. + """ + self.reset(False) + self._filter_input.setText("") + + folder_id = None + for folder_path in folder_paths: + folder_id = self._controller.get_folder_id_from_path(folder_path) + if folder_id: + break + if folder_id: + self._folders_widget.set_selected_folder(folder_id) + + def get_selected_folder_path(self): + """Get selected folder path.""" + return self._selected_folder_path diff --git a/client/ayon_core/tools/publisher/widgets/tasks_model.py b/client/ayon_core/tools/publisher/widgets/tasks_model.py new file mode 100644 index 0000000000..8f00dc37a2 --- /dev/null +++ b/client/ayon_core/tools/publisher/widgets/tasks_model.py @@ -0,0 +1,137 @@ +from qtpy import QtWidgets, QtCore, QtGui + +from ayon_core.tools.utils.lib import get_default_task_icon + +TASK_NAME_ROLE = QtCore.Qt.UserRole + 1 +TASK_TYPE_ROLE = QtCore.Qt.UserRole + 2 +TASK_ORDER_ROLE = QtCore.Qt.UserRole + 3 + + +class TasksModel(QtGui.QStandardItemModel): + """Tasks model. + + Task model must have set context of folder paths. + + Items in model are based on 0-infinite folders. Always contain + an interserction of context folder tasks. When no folders are in context + them model is empty if 2 or more are in context folders that don't have + tasks with same names then model is empty too. + + Args: + controller (PublisherController): Controller which handles creation and + publishing. + """ + def __init__(self, controller, allow_empty_task=False): + super(TasksModel, self).__init__() + + self._allow_empty_task = allow_empty_task + self._controller = controller + self._items_by_name = {} + self._folder_paths = [] + self._task_names_by_folder_path = {} + + def set_folder_paths(self, folder_paths): + """Set folders context.""" + self._folder_paths = folder_paths + self.reset() + + @staticmethod + def get_intersection_of_tasks(task_names_by_folder_path): + """Calculate intersection of task names from passed data. + + Example: + ``` + # Passed `task_names_by_folder_path` + { + "/folder_1": ["compositing", "animation"], + "/folder_2": ["compositing", "editorial"] + } + ``` + Result: + ``` + # Set + {"compositing"} + ``` + + Args: + task_names_by_folder_path (dict): Task names in iterable by parent. + """ + tasks = None + for task_names in task_names_by_folder_path.values(): + if tasks is None: + tasks = set(task_names) + else: + tasks &= set(task_names) + + if not tasks: + break + return tasks or set() + + def is_task_name_valid(self, folder_path, task_name): + """Is task name available for folder. + + Todos: + Move this method to PublisherController. + + Args: + folder_path (str): Fodler path where should look for task. + task_name (str): Name of task which should be available in folder + tasks. + """ + if folder_path not in self._task_names_by_folder_path: + return False + + if self._allow_empty_task and not task_name: + return True + + task_names = self._task_names_by_folder_path[folder_path] + if task_name in task_names: + return True + return False + + def reset(self): + """Update model by current context.""" + if not self._folder_paths: + self._items_by_name = {} + self._task_names_by_folder_path = {} + root_item = self.invisibleRootItem() + root_item.removeRows(0, self.rowCount()) + return + + task_names_by_folder_path = ( + self._controller.get_task_names_by_folder_paths( + self._folder_paths + ) + ) + + self._task_names_by_folder_path = task_names_by_folder_path + + new_task_names = self.get_intersection_of_tasks( + task_names_by_folder_path + ) + if self._allow_empty_task: + new_task_names.add("") + old_task_names = set(self._items_by_name.keys()) + if new_task_names == old_task_names: + return + + root_item = self.invisibleRootItem() + for task_name in old_task_names: + if task_name not in new_task_names: + item = self._items_by_name.pop(task_name) + root_item.removeRow(item.row()) + + new_items = [] + for task_name in new_task_names: + if task_name in self._items_by_name: + continue + + item = QtGui.QStandardItem(task_name) + item.setData(task_name, TASK_NAME_ROLE) + if task_name: + item.setData(get_default_task_icon(), QtCore.Qt.DecorationRole) + self._items_by_name[task_name] = item + new_items.append(item) + + if new_items: + root_item.appendRows(new_items) diff --git a/client/ayon_core/tools/publisher/widgets/tasks_widget.py b/client/ayon_core/tools/publisher/widgets/tasks_widget.py deleted file mode 100644 index 9a1b22b9a5..0000000000 --- a/client/ayon_core/tools/publisher/widgets/tasks_widget.py +++ /dev/null @@ -1,326 +0,0 @@ -from qtpy import QtWidgets, QtCore, QtGui - -from ayon_core.tools.utils.views import DeselectableTreeView -from ayon_core.tools.utils.lib import get_default_task_icon - -TASK_NAME_ROLE = QtCore.Qt.UserRole + 1 -TASK_TYPE_ROLE = QtCore.Qt.UserRole + 2 -TASK_ORDER_ROLE = QtCore.Qt.UserRole + 3 - - -class TasksModel(QtGui.QStandardItemModel): - """Tasks model. - - Task model must have set context of asset documents. - - Items in model are based on 0-infinite asset documents. Always contain - an interserction of context asset tasks. When no assets are in context - them model is empty if 2 or more are in context assets that don't have - tasks with same names then model is empty too. - - Args: - controller (PublisherController): Controller which handles creation and - publishing. - """ - def __init__(self, controller, allow_empty_task=False): - super(TasksModel, self).__init__() - - self._allow_empty_task = allow_empty_task - self._controller = controller - self._items_by_name = {} - self._asset_names = [] - self._task_names_by_asset_name = {} - - def set_asset_names(self, asset_names): - """Set assets context.""" - self._asset_names = asset_names - self.reset() - - @staticmethod - def get_intersection_of_tasks(task_names_by_asset_name): - """Calculate intersection of task names from passed data. - - Example: - ``` - # Passed `task_names_by_asset_name` - { - "asset_1": ["compositing", "animation"], - "asset_2": ["compositing", "editorial"] - } - ``` - Result: - ``` - # Set - {"compositing"} - ``` - - Args: - task_names_by_asset_name (dict): Task names in iterable by parent. - """ - tasks = None - for task_names in task_names_by_asset_name.values(): - if tasks is None: - tasks = set(task_names) - else: - tasks &= set(task_names) - - if not tasks: - break - return tasks or set() - - def is_task_name_valid(self, asset_name, task_name): - """Is task name available for asset. - - Args: - asset_name (str): Name of asset where should look for task. - task_name (str): Name of task which should be available in asset's - tasks. - """ - if asset_name not in self._task_names_by_asset_name: - return False - - if self._allow_empty_task and not task_name: - return True - - task_names = self._task_names_by_asset_name[asset_name] - if task_name in task_names: - return True - return False - - def reset(self): - """Update model by current context.""" - if not self._asset_names: - self._items_by_name = {} - self._task_names_by_asset_name = {} - self.clear() - return - - task_names_by_asset_name = ( - self._controller.get_task_names_by_asset_names(self._asset_names) - ) - - self._task_names_by_asset_name = task_names_by_asset_name - - new_task_names = self.get_intersection_of_tasks( - task_names_by_asset_name - ) - if self._allow_empty_task: - new_task_names.add("") - old_task_names = set(self._items_by_name.keys()) - if new_task_names == old_task_names: - return - - root_item = self.invisibleRootItem() - for task_name in old_task_names: - if task_name not in new_task_names: - item = self._items_by_name.pop(task_name) - root_item.removeRow(item.row()) - - new_items = [] - for task_name in new_task_names: - if task_name in self._items_by_name: - continue - - item = QtGui.QStandardItem(task_name) - item.setData(task_name, TASK_NAME_ROLE) - if task_name: - item.setData(get_default_task_icon(), QtCore.Qt.DecorationRole) - self._items_by_name[task_name] = item - new_items.append(item) - - if new_items: - root_item.appendRows(new_items) - - def headerData(self, section, orientation, role=None): - if role is None: - role = QtCore.Qt.EditRole - # Show nice labels in the header - if section == 0: - if ( - role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole) - and orientation == QtCore.Qt.Horizontal - ): - return "Tasks" - - return super(TasksModel, self).headerData(section, orientation, role) - - -class TasksProxyModel(QtCore.QSortFilterProxyModel): - def lessThan(self, x_index, y_index): - x_order = x_index.data(TASK_ORDER_ROLE) - y_order = y_index.data(TASK_ORDER_ROLE) - if x_order is not None and y_order is not None: - if x_order < y_order: - return True - if x_order > y_order: - return False - - elif x_order is None and y_order is not None: - return True - - elif y_order is None and x_order is not None: - return False - - x_name = x_index.data(QtCore.Qt.DisplayRole) - y_name = y_index.data(QtCore.Qt.DisplayRole) - if x_name == y_name: - return True - - if x_name == tuple(sorted((x_name, y_name)))[0]: - return True - return False - - -class CreateWidgetTasksWidget(QtWidgets.QWidget): - """Widget showing active Tasks - - Deprecated: - This widget will be removed soon. Please do not use it in new code. - """ - - task_changed = QtCore.Signal() - - def __init__(self, controller, parent): - self._controller = controller - - self._enabled = None - - super(CreateWidgetTasksWidget, self).__init__(parent) - - tasks_view = DeselectableTreeView(self) - tasks_view.setIndentation(0) - tasks_view.setSortingEnabled(True) - tasks_view.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) - - header_view = tasks_view.header() - header_view.setSortIndicator(0, QtCore.Qt.AscendingOrder) - - tasks_model = TasksModel(self._controller) - tasks_proxy = TasksProxyModel() - tasks_proxy.setSourceModel(tasks_model) - tasks_view.setModel(tasks_proxy) - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(tasks_view) - - selection_model = tasks_view.selectionModel() - selection_model.selectionChanged.connect(self._on_task_change) - - self._tasks_model = tasks_model - self._tasks_proxy = tasks_proxy - self._tasks_view = tasks_view - - self._last_selected_task_name = None - - def refresh(self): - self._tasks_model.refresh() - - def set_asset_id(self, asset_id): - # Try and preserve the last selected task and reselect it - # after switching assets. If there's no currently selected - # asset keep whatever the "last selected" was prior to it. - current = self.get_selected_task_name() - if current: - self._last_selected_task_name = current - - self._tasks_model.set_asset_id(asset_id) - - if self._last_selected_task_name: - self.select_task_name(self._last_selected_task_name) - - # Force a task changed emit. - self.task_changed.emit() - - def _clear_selection(self): - selection_model = self._tasks_view.selectionModel() - selection_model.clearSelection() - - def select_task_name(self, task_name): - """Select a task by name. - - If the task does not exist in the current model then selection is only - cleared. - - Args: - task_name (str): Name of the task to select. - - """ - task_view_model = self._tasks_view.model() - if not task_view_model: - return - - # Clear selection - selection_model = self._tasks_view.selectionModel() - selection_model.clearSelection() - - # Select the task - mode = ( - QtCore.QItemSelectionModel.Select - | QtCore.QItemSelectionModel.Rows - ) - for row in range(task_view_model.rowCount()): - index = task_view_model.index(row, 0) - name = index.data(TASK_NAME_ROLE) - if name == task_name: - selection_model.select(index, mode) - - # Set the currently active index - self._tasks_view.setCurrentIndex(index) - break - - last_selected_task_name = self.get_selected_task_name() - if last_selected_task_name: - self._last_selected_task_name = last_selected_task_name - - if not self._enabled: - current = self.get_selected_task_name() - if current: - self._last_selected_task_name = current - self._clear_selection() - - def get_selected_task_name(self): - """Return name of task at current index (selected) - - Returns: - str: Name of the current task. - - """ - index = self._tasks_view.currentIndex() - selection_model = self._tasks_view.selectionModel() - if index.isValid() and selection_model.isSelected(index): - return index.data(TASK_NAME_ROLE) - return None - - def get_selected_task_type(self): - index = self._tasks_view.currentIndex() - selection_model = self._tasks_view.selectionModel() - if index.isValid() and selection_model.isSelected(index): - return index.data(TASK_TYPE_ROLE) - return None - - def set_asset_name(self, asset_name): - current = self.get_selected_task_name() - if current: - self._last_selected_task_name = current - - self._tasks_model.set_asset_names([asset_name]) - if self._last_selected_task_name and self._enabled: - self.select_task_name(self._last_selected_task_name) - - # Force a task changed emit. - self.task_changed.emit() - - def set_enabled(self, enabled): - self._enabled = enabled - if not enabled: - last_selected_task_name = self.get_selected_task_name() - if last_selected_task_name: - self._last_selected_task_name = last_selected_task_name - self._clear_selection() - - elif self._last_selected_task_name is not None: - self.select_task_name(self._last_selected_task_name) - - def _on_task_change(self): - self.task_changed.emit() diff --git a/client/ayon_core/tools/publisher/widgets/widgets.py b/client/ayon_core/tools/publisher/widgets/widgets.py index c95f7d8045..4005cf2c84 100644 --- a/client/ayon_core/tools/publisher/widgets/widgets.py +++ b/client/ayon_core/tools/publisher/widgets/widgets.py @@ -26,8 +26,8 @@ from ayon_core.pipeline.create import ( TaskNotSetError, ) from .thumbnail_widget import ThumbnailWidget -from .assets_widget import AssetsDialog -from .tasks_widget import TasksModel +from .folders_dialog import FoldersDialog +from .tasks_model import TasksModel from .icons import ( get_pixmap, get_icon_path @@ -422,29 +422,29 @@ class ClickableLineEdit(QtWidgets.QLineEdit): event.accept() -class AssetsField(BaseClickableFrame): - """Field where asset name of selected instance/s is showed. +class FoldersFields(BaseClickableFrame): + """Field where folder path of selected instance/s is showed. - Click on the field will trigger `AssetsDialog`. + Click on the field will trigger `FoldersDialog`. """ value_changed = QtCore.Signal() def __init__(self, controller, parent): - super(AssetsField, self).__init__(parent) - self.setObjectName("AssetNameInputWidget") + super(FoldersFields, self).__init__(parent) + self.setObjectName("FolderPathInputWidget") # Don't use 'self' for parent! # - this widget has specific styles - dialog = AssetsDialog(controller, parent) + dialog = FoldersDialog(controller, parent) name_input = ClickableLineEdit(self) - name_input.setObjectName("AssetNameInput") + name_input.setObjectName("FolderPathInput") icon_name = "fa.window-maximize" icon = qtawesome.icon(icon_name, color="white") icon_btn = QtWidgets.QPushButton(self) icon_btn.setIcon(icon) - icon_btn.setObjectName("AssetNameInputButton") + icon_btn.setObjectName("FolderPathInputButton") layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) @@ -465,6 +465,7 @@ class AssetsField(BaseClickableFrame): icon_btn.clicked.connect(self._mouse_release_callback) dialog.finished.connect(self._on_dialog_finish) + self._controller = controller self._dialog = dialog self._name_input = name_input self._icon_btn = icon_btn @@ -480,28 +481,28 @@ class AssetsField(BaseClickableFrame): if not result: return - asset_name = self._dialog.get_selected_asset() - if asset_name is None: + folder_path = self._dialog.get_selected_folder_path() + if folder_path is None: return - self._selected_items = [asset_name] + self._selected_items = [folder_path] self._has_value_changed = ( self._origin_value != self._selected_items ) - self.set_text(asset_name) + self.set_text(folder_path) self._set_is_valid(True) self.value_changed.emit() def _mouse_release_callback(self): - self._dialog.set_selected_assets(self._selected_items) + self._dialog.set_selected_folders(self._selected_items) self._dialog.open() def set_multiselection_text(self, text): - """Change text for multiselection of different assets. + """Change text for multiselection of different folders. When there are selected multiple instances at once and they don't have - same asset in context. + same folder in context. """ self._multiselection_text = text @@ -520,63 +521,58 @@ class AssetsField(BaseClickableFrame): set_style_property(self._icon_btn, "state", state) def is_valid(self): - """Is asset valid.""" + """Is folder valid.""" return self._is_valid def has_value_changed(self): - """Value of asset has changed.""" + """Value of folder has changed.""" return self._has_value_changed def get_selected_items(self): - """Selected asset names.""" + """Selected folder paths.""" return list(self._selected_items) def set_text(self, text): """Set text in text field. - Does not change selected items (assets). + Does not change selected items (folders). """ self._name_input.setText(text) self._name_input.end(False) - def set_selected_items(self, asset_names=None): - """Set asset names for selection of instances. + def set_selected_items(self, folder_paths=None): + """Set folder paths for selection of instances. - Passed asset names are validated and if there are 2 or more different - asset names then multiselection text is shown. + Passed folder paths are validated and if there are 2 or more different + folder paths then multiselection text is shown. Args: - asset_names (list, tuple, set, NoneType): List of asset names. + folder_paths (list, tuple, set, NoneType): List of folder paths. + """ - if asset_names is None: - asset_names = [] + if folder_paths is None: + folder_paths = [] self._has_value_changed = False - self._origin_value = list(asset_names) - self._selected_items = list(asset_names) - is_valid = True - if not asset_names: + self._origin_value = list(folder_paths) + self._selected_items = list(folder_paths) + is_valid = self._controller.are_folder_paths_valid(folder_paths) + if not folder_paths: self.set_text("") - elif len(asset_names) == 1: - asset_name = tuple(asset_names)[0] - is_valid = self._dialog.name_is_valid(asset_name) - self.set_text(asset_name) + elif len(folder_paths) == 1: + folder_path = tuple(folder_paths)[0] + self.set_text(folder_path) else: - for asset_name in asset_names: - is_valid = self._dialog.name_is_valid(asset_name) - if not is_valid: - break - multiselection_text = self._multiselection_text if multiselection_text is None: - multiselection_text = "|".join(asset_names) + multiselection_text = "|".join(folder_paths) self.set_text(multiselection_text) self._set_is_valid(is_valid) def reset_to_origin(self): - """Change to asset names set with last `set_selected_items` call.""" + """Change to folder paths set with last `set_selected_items` call.""" self.set_selected_items(self._origin_value) def confirm_value(self): @@ -610,9 +606,9 @@ class TasksCombobox(QtWidgets.QComboBox): """Combobox to show tasks for selected instances. Combobox gives ability to select only from intersection of task names for - asset names in selected instances. + folder paths in selected instances. - If asset names in selected instances does not have same tasks then combobox + If folder paths in selected instances does not have same tasks then combobox will be empty. """ value_changed = QtCore.Signal() @@ -746,23 +742,23 @@ class TasksCombobox(QtWidgets.QComboBox): """ return list(self._selected_items) - def set_asset_names(self, asset_names): - """Set asset names for which should show tasks.""" + def set_folder_paths(self, folder_paths): + """Set folder paths for which should show tasks.""" self._ignore_index_change = True - self._model.set_asset_names(asset_names) + self._model.set_folder_paths(folder_paths) self._proxy_model.set_filter_empty(False) self._proxy_model.sort(0) self._ignore_index_change = False - # It is a bug if not exactly one asset got here - if len(asset_names) != 1: + # It is a bug if not exactly one folder got here + if len(folder_paths) != 1: self.set_selected_item("") self._set_is_valid(False) return - asset_name = tuple(asset_names)[0] + folder_path = tuple(folder_paths)[0] is_valid = False if self._selected_items: @@ -770,7 +766,7 @@ class TasksCombobox(QtWidgets.QComboBox): valid_task_names = [] for task_name in self._selected_items: - _is_valid = self._model.is_task_name_valid(asset_name, task_name) + _is_valid = self._model.is_task_name_valid(folder_path, task_name) if _is_valid: valid_task_names.append(task_name) else: @@ -791,42 +787,42 @@ class TasksCombobox(QtWidgets.QComboBox): self._set_is_valid(is_valid) - def confirm_value(self, asset_names): + def confirm_value(self, folder_paths): new_task_name = self._selected_items[0] self._origin_value = [ - (asset_name, new_task_name) - for asset_name in asset_names + (folder_path, new_task_name) + for folder_path in folder_paths ] self._origin_selection = copy.deepcopy(self._selected_items) self._has_value_changed = False - def set_selected_items(self, asset_task_combinations=None): + def set_selected_items(self, folder_task_combinations=None): """Set items for selected instances. Args: - asset_task_combinations (list): List of tuples. Each item in - the list contain asset name and task name. + folder_task_combinations (list): List of tuples. Each item in + the list contain folder path and task name. """ self._proxy_model.set_filter_empty(False) self._proxy_model.sort(0) - if asset_task_combinations is None: - asset_task_combinations = [] + if folder_task_combinations is None: + folder_task_combinations = [] task_names = set() - task_names_by_asset_name = collections.defaultdict(set) - for asset_name, task_name in asset_task_combinations: + task_names_by_folder_path = collections.defaultdict(set) + for folder_path, task_name in folder_task_combinations: task_names.add(task_name) - task_names_by_asset_name[asset_name].add(task_name) - asset_names = set(task_names_by_asset_name.keys()) + task_names_by_folder_path[folder_path].add(task_name) + folder_paths = set(task_names_by_folder_path.keys()) self._ignore_index_change = True - self._model.set_asset_names(asset_names) + self._model.set_folder_paths(folder_paths) self._has_value_changed = False - self._origin_value = copy.deepcopy(asset_task_combinations) + self._origin_value = copy.deepcopy(folder_task_combinations) self._origin_selection = list(task_names) self._selected_items = list(task_names) @@ -840,9 +836,9 @@ class TasksCombobox(QtWidgets.QComboBox): task_name = tuple(task_names)[0] idx = self.findText(task_name) is_valid = not idx < 0 - if not is_valid and len(asset_names) > 1: - is_valid = self._validate_task_names_by_asset_names( - task_names_by_asset_name + if not is_valid and len(folder_paths) > 1: + is_valid = self._validate_task_names_by_folder_paths( + task_names_by_folder_path ) self.set_selected_item(task_name) @@ -853,9 +849,9 @@ class TasksCombobox(QtWidgets.QComboBox): if not is_valid: break - if not is_valid and len(asset_names) > 1: - is_valid = self._validate_task_names_by_asset_names( - task_names_by_asset_name + if not is_valid and len(folder_paths) > 1: + is_valid = self._validate_task_names_by_folder_paths( + task_names_by_folder_path ) multiselection_text = self._multiselection_text if multiselection_text is None: @@ -868,10 +864,10 @@ class TasksCombobox(QtWidgets.QComboBox): self.value_changed.emit() - def _validate_task_names_by_asset_names(self, task_names_by_asset_name): - for asset_name, task_names in task_names_by_asset_name.items(): + def _validate_task_names_by_folder_paths(self, task_names_by_folder_path): + for folder_path, task_names in task_names_by_folder_path.items(): for task_name in task_names: - if not self._model.is_task_name_valid(asset_name, task_name): + if not self._model.is_task_name_valid(folder_path, task_name): return False return True @@ -1106,17 +1102,17 @@ class GlobalAttrsWidget(QtWidgets.QWidget): self._current_instances = [] variant_input = VariantInputWidget(self) - asset_value_widget = AssetsField(controller, self) + folder_value_widget = FoldersFields(controller, self) task_value_widget = TasksCombobox(controller, self) product_type_value_widget = MultipleItemWidget(self) product_value_widget = MultipleItemWidget(self) variant_input.set_multiselection_text(self.multiselection_text) - asset_value_widget.set_multiselection_text(self.multiselection_text) + folder_value_widget.set_multiselection_text(self.multiselection_text) task_value_widget.set_multiselection_text(self.multiselection_text) variant_input.set_value() - asset_value_widget.set_selected_items() + folder_value_widget.set_selected_items() task_value_widget.set_selected_items() product_type_value_widget.set_value() product_value_widget.set_value() @@ -1137,20 +1133,20 @@ class GlobalAttrsWidget(QtWidgets.QWidget): main_layout.setHorizontalSpacing(INPUTS_LAYOUT_HSPACING) main_layout.setVerticalSpacing(INPUTS_LAYOUT_VSPACING) main_layout.addRow("Variant", variant_input) - main_layout.addRow("Folder", asset_value_widget) + main_layout.addRow("Folder", folder_value_widget) main_layout.addRow("Task", task_value_widget) main_layout.addRow("Product type", product_type_value_widget) main_layout.addRow("Product name", product_value_widget) main_layout.addRow(btns_layout) variant_input.value_changed.connect(self._on_variant_change) - asset_value_widget.value_changed.connect(self._on_asset_change) + folder_value_widget.value_changed.connect(self._on_folder_change) task_value_widget.value_changed.connect(self._on_task_change) submit_btn.clicked.connect(self._on_submit) cancel_btn.clicked.connect(self._on_cancel) self.variant_input = variant_input - self.asset_value_widget = asset_value_widget + self.folder_value_widget = folder_value_widget self.task_value_widget = task_value_widget self.product_type_value_widget = product_type_value_widget self.product_value_widget = product_value_widget @@ -1161,40 +1157,40 @@ class GlobalAttrsWidget(QtWidgets.QWidget): """Commit changes for selected instances.""" variant_value = None - asset_name = None + folder_path = None task_name = None if self.variant_input.has_value_changed(): variant_value = self.variant_input.get_value()[0] - if self.asset_value_widget.has_value_changed(): - asset_name = self.asset_value_widget.get_selected_items()[0] + if self.folder_value_widget.has_value_changed(): + folder_path = self.folder_value_widget.get_selected_items()[0] if self.task_value_widget.has_value_changed(): task_name = self.task_value_widget.get_selected_items()[0] product_names = set() invalid_tasks = False - asset_names = [] + folder_paths = [] for instance in self._current_instances: new_variant_value = instance.get("variant") - new_asset_name = instance.get("folderPath") + new_folder_path = instance.get("folderPath") new_task_name = instance.get("task") if variant_value is not None: new_variant_value = variant_value - if asset_name is not None: - new_asset_name = asset_name + if folder_path is not None: + new_folder_path = folder_path if task_name is not None: new_task_name = task_name - asset_names.append(new_asset_name) + folder_paths.append(new_folder_path) try: new_product_name = self._controller.get_product_name( instance.creator_identifier, new_variant_value, new_task_name, - new_asset_name, + new_folder_path, instance.id, ) @@ -1208,8 +1204,8 @@ class GlobalAttrsWidget(QtWidgets.QWidget): if variant_value is not None: instance["variant"] = variant_value - if asset_name is not None: - instance["folderPath"] = asset_name + if folder_path is not None: + instance["folderPath"] = folder_path instance.set_asset_invalid(False) if task_name is not None: @@ -1229,11 +1225,11 @@ class GlobalAttrsWidget(QtWidgets.QWidget): if variant_value is not None: self.variant_input.confirm_value() - if asset_name is not None: - self.asset_value_widget.confirm_value() + if folder_path is not None: + self.folder_value_widget.confirm_value() if task_name is not None: - self.task_value_widget.confirm_value(asset_names) + self.task_value_widget.confirm_value(folder_paths) self.instance_context_changed.emit() @@ -1241,19 +1237,19 @@ class GlobalAttrsWidget(QtWidgets.QWidget): """Cancel changes and set back to their irigin value.""" self.variant_input.reset_to_origin() - self.asset_value_widget.reset_to_origin() + self.folder_value_widget.reset_to_origin() self.task_value_widget.reset_to_origin() self._set_btns_enabled(False) def _on_value_change(self): any_invalid = ( not self.variant_input.is_valid() - or not self.asset_value_widget.is_valid() + or not self.folder_value_widget.is_valid() or not self.task_value_widget.is_valid() ) any_changed = ( self.variant_input.has_value_changed() - or self.asset_value_widget.has_value_changed() + or self.folder_value_widget.has_value_changed() or self.task_value_widget.has_value_changed() ) self._set_btns_visible(any_changed or any_invalid) @@ -1263,9 +1259,9 @@ class GlobalAttrsWidget(QtWidgets.QWidget): def _on_variant_change(self): self._on_value_change() - def _on_asset_change(self): - asset_names = self.asset_value_widget.get_selected_items() - self.task_value_widget.set_asset_names(asset_names) + def _on_folder_change(self): + folder_paths = self.folder_value_widget.get_selected_items() + self.task_value_widget.set_folder_paths(folder_paths) self._on_value_change() def _on_task_change(self): @@ -1290,7 +1286,7 @@ class GlobalAttrsWidget(QtWidgets.QWidget): self._current_instances = instances - asset_names = set() + folder_paths = set() variants = set() product_types = set() product_names = set() @@ -1299,7 +1295,7 @@ class GlobalAttrsWidget(QtWidgets.QWidget): if len(instances) == 0: editable = False - asset_task_combinations = [] + folder_task_combinations = [] for instance in instances: # NOTE I'm not sure how this can even happen? if instance.creator_identifier is None: @@ -1307,23 +1303,23 @@ class GlobalAttrsWidget(QtWidgets.QWidget): variants.add(instance.get("variant") or self.unknown_value) product_types.add(instance.get("productType") or self.unknown_value) - asset_name = instance.get("folderPath") or self.unknown_value + folder_path = instance.get("folderPath") or self.unknown_value task_name = instance.get("task") or "" - asset_names.add(asset_name) - asset_task_combinations.append((asset_name, task_name)) + folder_paths.add(folder_path) + folder_task_combinations.append((folder_path, task_name)) product_names.add(instance.get("productName") or self.unknown_value) self.variant_input.set_value(variants) - # Set context of asset widget - self.asset_value_widget.set_selected_items(asset_names) + # Set context of folder widget + self.folder_value_widget.set_selected_items(folder_paths) # Set context of task widget - self.task_value_widget.set_selected_items(asset_task_combinations) + self.task_value_widget.set_selected_items(folder_task_combinations) self.product_type_value_widget.set_value(product_types) self.product_value_widget.set_value(product_names) self.variant_input.setEnabled(editable) - self.asset_value_widget.setEnabled(editable) + self.folder_value_widget.setEnabled(editable) self.task_value_widget.setEnabled(editable) diff --git a/client/ayon_core/tools/sceneinventory/switch_dialog/folders_input.py b/client/ayon_core/tools/sceneinventory/switch_dialog/folders_input.py index 2358a82a7f..e46c28474f 100644 --- a/client/ayon_core/tools/sceneinventory/switch_dialog/folders_input.py +++ b/client/ayon_core/tools/sceneinventory/switch_dialog/folders_input.py @@ -196,19 +196,19 @@ class FoldersField(BaseClickableFrame): def __init__(self, controller, parent): super(FoldersField, self).__init__(parent) - self.setObjectName("AssetNameInputWidget") + self.setObjectName("FolderPathInputWidget") # Don't use 'self' for parent! # - this widget has specific styles dialog = FoldersDialog(controller, parent) name_input = ClickableLineEdit(self) - name_input.setObjectName("AssetNameInput") + name_input.setObjectName("FolderPathInput") icon = qtawesome.icon("fa.window-maximize", color="white") icon_btn = QtWidgets.QPushButton(self) icon_btn.setIcon(icon) - icon_btn.setObjectName("AssetNameInputButton") + icon_btn.setObjectName("FolderPathInputButton") layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) diff --git a/client/ayon_core/tools/traypublisher/window.py b/client/ayon_core/tools/traypublisher/window.py index 79386d7ea0..210e77f0fa 100644 --- a/client/ayon_core/tools/traypublisher/window.py +++ b/client/ayon_core/tools/traypublisher/window.py @@ -10,51 +10,47 @@ import platform from qtpy import QtWidgets, QtCore import qtawesome -import appdirs -from ayon_core.lib import JSONSettingRegistry, is_running_from_build +from ayon_core.lib import AYONSettingsRegistry, is_running_from_build from ayon_core.pipeline import install_host from ayon_core.hosts.traypublisher.api import TrayPublisherHost from ayon_core.tools.publisher.control_qt import QtPublisherController from ayon_core.tools.publisher.window import PublisherWindow from ayon_core.tools.utils import PlaceholderLineEdit, get_ayon_qt_app -from ayon_core.tools.utils.constants import PROJECT_NAME_ROLE -from ayon_core.tools.utils.models import ( - ProjectModel, - ProjectSortFilterProxy +from ayon_core.tools.ayon_utils.models import ProjectsModel +from ayon_core.tools.ayon_utils.widgets import ( + ProjectsQtModel, + ProjectSortFilterProxy, + PROJECT_NAME_ROLE, ) +class TrayPublisherRegistry(AYONSettingsRegistry): + def __init__(self): + super(TrayPublisherRegistry, self).__init__("traypublisher") + + class TrayPublisherController(QtPublisherController): + def __init__(self, *args, **kwargs): + super(TrayPublisherController, self).__init__(*args, **kwargs) + self._projects_model = ProjectsModel(self) + @property def host(self): return self._host - def reset_project_data_cache(self): + def reset_hierarchy_cache(self): + self._hierarchy_model.reset() self._asset_docs_cache.reset() - -class TrayPublisherRegistry(JSONSettingRegistry): - """Class handling AYON general settings registry. - - Attributes: - vendor (str): Name used for path construction. - product (str): Additional name used for path construction. - - """ - - def __init__(self): - self.vendor = "pypeclub" - self.product = "openpype" - name = "tray_publisher" - path = appdirs.user_data_dir(self.product, self.vendor) - super(TrayPublisherRegistry, self).__init__(name, path) + def get_project_items(self, sender=None): + return self._projects_model.get_project_items(sender) class StandaloneOverlayWidget(QtWidgets.QFrame): project_selected = QtCore.Signal(str) - def __init__(self, publisher_window): + def __init__(self, controller, publisher_window): super(StandaloneOverlayWidget, self).__init__(publisher_window) self.setObjectName("OverlayFrame") @@ -66,7 +62,7 @@ class StandaloneOverlayWidget(QtWidgets.QFrame): header_label = QtWidgets.QLabel("Choose project", content_widget) header_label.setObjectName("ChooseProjectLabel") # Create project models and view - projects_model = ProjectModel() + projects_model = ProjectsQtModel(controller) projects_proxy = ProjectSortFilterProxy() projects_proxy.setSourceModel(projects_model) projects_proxy.setFilterKeyColumn(0) @@ -137,12 +133,11 @@ class StandaloneOverlayWidget(QtWidgets.QFrame): project_name = None if project_name: - index = None - src_index = self._projects_model.find_project(project_name) - if src_index is not None: - index = self._projects_proxy.mapFromSource(src_index) - - if index is not None: + src_index = self._projects_model.get_index_by_project_name( + project_name + ) + index = self._projects_proxy.mapFromSource(src_index) + if index.isValid(): selection_model = self._projects_view.selectionModel() selection_model.select( index, @@ -201,7 +196,7 @@ class TrayPublishWindow(PublisherWindow): self.setWindowFlags(flags) - overlay_widget = StandaloneOverlayWidget(self) + overlay_widget = StandaloneOverlayWidget(controller, self) btns_widget = self._header_extra_widget @@ -248,7 +243,7 @@ class TrayPublishWindow(PublisherWindow): def _on_project_select(self, project_name): # TODO register project specific plugin paths self._controller.save_changes(False) - self._controller.reset_project_data_cache() + self._controller.reset_hierarchy_cache() self.reset() if not self._controller.instances: diff --git a/client/ayon_core/tools/utils/models.py b/client/ayon_core/tools/utils/models.py index e60d85b4e4..a4b6ad7885 100644 --- a/client/ayon_core/tools/utils/models.py +++ b/client/ayon_core/tools/utils/models.py @@ -243,160 +243,3 @@ class RecursiveSortFilterProxyModel(QtCore.QSortFilterProxyModel): return super(RecursiveSortFilterProxyModel, self).filterAcceptsRow( row, parent_index ) - - -# TODO remove 'ProjectModel' and 'ProjectSortFilterProxy' classes -# - replace their usage with current 'ayon_utils' models -class ProjectModel(QtGui.QStandardItemModel): - def __init__( - self, only_active=True, add_default_project=False, *args, **kwargs - ): - super(ProjectModel, self).__init__(*args, **kwargs) - - self._only_active = only_active - self._add_default_project = add_default_project - - self._default_item = None - self._items_by_name = {} - self._refreshed = False - - def set_default_project_available(self, available=True): - if available is None: - available = not self._add_default_project - - if self._add_default_project == available: - return - - self._add_default_project = available - if not available and self._default_item is not None: - root_item = self.invisibleRootItem() - root_item.removeRow(self._default_item.row()) - self._default_item = None - - def set_only_active(self, only_active=True): - if only_active is None: - only_active = not self._only_active - - if self._only_active == only_active: - return - - self._only_active = only_active - - if self._refreshed: - self.refresh() - - def project_name_is_available(self, project_name): - """Check availability of project name in current items.""" - return project_name in self._items_by_name - - def refresh(self): - # Change '_refreshed' state - self._refreshed = True - new_items = [] - # Add default item to model if should - if self._add_default_project and self._default_item is None: - item = QtGui.QStandardItem(DEFAULT_PROJECT_LABEL) - item.setData(None, PROJECT_NAME_ROLE) - item.setData(True, PROJECT_IS_ACTIVE_ROLE) - new_items.append(item) - self._default_item = item - - project_names = set() - project_docs = get_projects( - inactive=not self._only_active, - fields=["name", "data.active"] - ) - for project_doc in project_docs: - project_name = project_doc["name"] - project_names.add(project_name) - if project_name in self._items_by_name: - item = self._items_by_name[project_name] - else: - item = QtGui.QStandardItem(project_name) - - self._items_by_name[project_name] = item - new_items.append(item) - - is_active = project_doc.get("data", {}).get("active", True) - item.setData(project_name, PROJECT_NAME_ROLE) - item.setData(is_active, PROJECT_IS_ACTIVE_ROLE) - - if not is_active: - font = item.font() - font.setItalic(True) - item.setFont(font) - - root_item = self.invisibleRootItem() - for project_name in tuple(self._items_by_name.keys()): - if project_name not in project_names: - item = self._items_by_name.pop(project_name) - root_item.removeRow(item.row()) - - if new_items: - root_item.appendRows(new_items) - - def find_project(self, project_name): - """ - Get index of 'project_name' value. - - Args: - project_name (str): - Returns: - (QModelIndex) - """ - val = self._items_by_name.get(project_name) - if val: - return self.indexFromItem(val) - - -class ProjectSortFilterProxy(QtCore.QSortFilterProxyModel): - def __init__(self, *args, **kwargs): - super(ProjectSortFilterProxy, self).__init__(*args, **kwargs) - self._filter_enabled = True - # Disable case sensitivity - self.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive) - - def lessThan(self, left_index, right_index): - if left_index.data(PROJECT_NAME_ROLE) is None: - return True - - if right_index.data(PROJECT_NAME_ROLE) is None: - return False - - left_is_active = left_index.data(PROJECT_IS_ACTIVE_ROLE) - right_is_active = right_index.data(PROJECT_IS_ACTIVE_ROLE) - if right_is_active == left_is_active: - return super(ProjectSortFilterProxy, self).lessThan( - left_index, right_index - ) - - if left_is_active: - return True - return False - - def filterAcceptsRow(self, source_row, source_parent): - index = self.sourceModel().index(source_row, 0, source_parent) - string_pattern = self.filterRegularExpression().pattern() - if self._filter_enabled: - result = self._custom_index_filter(index) - if result is not None: - project_name = index.data(PROJECT_NAME_ROLE) - if project_name is None: - return result - return string_pattern.lower() in project_name.lower() - - return super(ProjectSortFilterProxy, self).filterAcceptsRow( - source_row, source_parent - ) - - def _custom_index_filter(self, index): - is_active = bool(index.data(PROJECT_IS_ACTIVE_ROLE)) - - return is_active - - def is_filter_enabled(self): - return self._filter_enabled - - def set_filter_enabled(self, value): - self._filter_enabled = value - self.invalidateFilter() diff --git a/server_addon/max/server/settings/publishers.py b/server_addon/max/server/settings/publishers.py index 5e28c1b467..6ea6a887d1 100644 --- a/server_addon/max/server/settings/publishers.py +++ b/server_addon/max/server/settings/publishers.py @@ -86,6 +86,10 @@ class PublishersModel(BaseSettingsModel): default_factory=ValidateLoadedPluginModel, title="Validate Loaded Plugin" ) + ValidateMeshHasUVs: BasicValidateModel = SettingsField( + default_factory=BasicValidateModel, + title="Validate Mesh Has UVs" + ) ExtractModelObj: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Extract OBJ", @@ -134,6 +138,11 @@ DEFAULT_PUBLISH_SETTINGS = { "optional": True, "family_plugins_mapping": [] }, + "ValidateMeshHasUVs": { + "enabled": True, + "optional": True, + "active": False + }, "ExtractModelObj": { "enabled": True, "optional": True, diff --git a/server_addon/max/server/version.py b/server_addon/max/server/version.py index 1276d0254f..0a8da88258 100644 --- a/server_addon/max/server/version.py +++ b/server_addon/max/server/version.py @@ -1 +1 @@ -__version__ = "0.1.5" +__version__ = "0.1.6"