From efc1cc257258da3e024d2db2609f1dbc83288de9 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 16 Jan 2024 18:00:34 +0800 Subject: [PATCH 001/150] refactor the validate deadline publish and rename to validate render passes --- .../publish/validate_deadline_publish.py | 43 ------ .../plugins/publish/validate_renderpasses.py | 137 ++++++++++++++++++ .../defaults/project_settings/max.json | 5 + .../schemas/schema_max_publish.json | 25 ++++ .../max/server/settings/publishers.py | 9 ++ server_addon/max/server/version.py | 2 +- 6 files changed, 177 insertions(+), 44 deletions(-) delete mode 100644 openpype/hosts/max/plugins/publish/validate_deadline_publish.py create mode 100644 openpype/hosts/max/plugins/publish/validate_renderpasses.py diff --git a/openpype/hosts/max/plugins/publish/validate_deadline_publish.py b/openpype/hosts/max/plugins/publish/validate_deadline_publish.py deleted file mode 100644 index b2f0e863f4..0000000000 --- a/openpype/hosts/max/plugins/publish/validate_deadline_publish.py +++ /dev/null @@ -1,43 +0,0 @@ -import os -import pyblish.api -from pymxs import runtime as rt -from openpype.pipeline.publish import ( - RepairAction, - ValidateContentsOrder, - PublishValidationError, - OptionalPyblishPluginMixin -) -from openpype.hosts.max.api.lib_rendersettings import RenderSettings - - -class ValidateDeadlinePublish(pyblish.api.InstancePlugin, - OptionalPyblishPluginMixin): - """Validates Render File Directory is - not the same in every submission - """ - - order = ValidateContentsOrder - families = ["maxrender"] - hosts = ["max"] - label = "Render Output for Deadline" - optional = True - actions = [RepairAction] - - def process(self, instance): - if not self.is_active(instance.data): - return - file = rt.maxFileName - filename, ext = os.path.splitext(file) - if filename not in rt.rendOutputFilename: - raise PublishValidationError( - "Render output folder " - "doesn't match the max scene name! " - "Use Repair action to " - "fix the folder file path.." - ) - - @classmethod - def repair(cls, instance): - container = instance.data.get("instance_node") - RenderSettings().render_output(container) - cls.log.debug("Reset the render output folder...") diff --git a/openpype/hosts/max/plugins/publish/validate_renderpasses.py b/openpype/hosts/max/plugins/publish/validate_renderpasses.py new file mode 100644 index 0000000000..9e65f305a2 --- /dev/null +++ b/openpype/hosts/max/plugins/publish/validate_renderpasses.py @@ -0,0 +1,137 @@ +import os +import pyblish.api +from pymxs import runtime as rt +from openpype.pipeline.publish import ( + RepairAction, + ValidateContentsOrder, + PublishValidationError, + OptionalPyblishPluginMixin +) +from openpype.hosts.max.api.lib import get_current_renderer +from openpype.hosts.max.api.lib_rendersettings import RenderSettings + + +class ValidateRenderPasses(OptionalPyblishPluginMixin, + pyblish.api.InstancePlugin): + """Validates Render Passes before Deadline Submission + """ + + order = ValidateContentsOrder + families = ["maxrender"] + hosts = ["max"] + label = "Validate Render Passes" + optional = True + actions = [RepairAction] + + def process(self, instance): + if not self.is_active(instance.data): + return + invalid = self.get_invalid(instance) + if invalid: + bullet_point_invalid_statement = "\n".join( + f"- {err_type}: {filepath}" for err_type, filepath + in invalid + ) + report = ( + "Invalid render passes found.\n\n" + f"{bullet_point_invalid_statement}\n\n" + "You can use repair action to fix the invalid filepath." + ) + raise PublishValidationError( + report, title="Invalid Render Passes") + + @classmethod + def get_invalid(cls, instance): + """Function to get invalid beauty render outputs and + render elements + + Args: + instance (pyblish.api.Instance): instance + filename (str): filename of the Max scene + + Returns: + list: list of invalid filename which doesn't match + with the project name + """ + invalid = [] + file = rt.maxFileName + filename, ext = os.path.splitext(file) + if filename not in rt.rendOutputFilename: + cls.log.error( + "Render output folder " + "doesn't match the max scene name! " + ) + invalid_folder_name = os.path.dirname( + rt.rendOutputFilename).replace( + "\\", "/").split("/")[-1] + invalid.append(("Invalid Render Output Folder", + invalid_folder_name)) + beauty_fname = os.path.basename(rt.rendOutputFilename) + ext = os.path.splitext(beauty_fname)[-1].lstrip(".") + invalid_image_format = cls.get_invalid_image_format( + cls, instance, ext) + invalid.extend(invalid_image_format) + renderer_class = get_current_renderer() + renderer = str(renderer_class).split(":")[0] + if renderer in [ + "ART_Renderer", + "Redshift_Renderer", + "V_Ray_6_Hotfix_3", + "V_Ray_GPU_6_Hotfix_3", + "Default_Scanline_Renderer", + "Quicksilver_Hardware_Renderer", + ]: + render_elem = rt.maxOps.GetCurRenderElementMgr() + render_elem_num = render_elem.NumRenderElements() + for i in range(render_elem_num): + renderlayer_name = render_elem.GetRenderElement(i) + renderpass = str(renderlayer_name).split(":")[-1] + rend_file = render_elem.GetRenderElementFilename(i) + if not rend_file: + cls.log.error(f"No filepath for {renderpass}") + invalid.append((f"Invalid {renderpass}", + "No filepath")) + rend_fname, ext = os.path.splitext( + os.path.basename(rend_file)) + if not rend_fname.lstrip(".").endswith(renderpass): + err_msg = ( + f"Filename for {renderpass} should be " + f"ended with {renderpass}" + ) + cls.log.error(err_msg) + invalid.append((f"Invalid {renderpass}", + os.path.basename(rend_file))) + invalid_image_format = cls.get_invalid_image_format( + cls, instance, ext) + invalid.extend(invalid_image_format) + elif renderer == "Arnold": + cls.log.debug( + "Temporarily not support to check on Arnold render.") + + return invalid + + def get_invalid_image_format(self, instance, ext): + """Function to check if the image format of the render outputs + aligns with that in the setting. + + Args: + instance (pyblish.api.Instance): instance + ext (str): image extension + + Returns: + list: list of files with invalid image format + """ + invalid = [] + settings = instance.context.data["project_settings"].get("max") + image_format = settings["RenderSettings"]["image_format"] + if ext.lstrip(".") != image_format: + msg = "Invalid image format for render outputs" + self.log.error(msg) + invalid.append((msg, ext.lstrip("."))) + return invalid + + @classmethod + def repair(cls, instance): + container = instance.data.get("instance_node") + RenderSettings().render_output(container) + cls.log.debug("Reset the render output folder...") diff --git a/openpype/settings/defaults/project_settings/max.json b/openpype/settings/defaults/project_settings/max.json index d1610610dc..293515f1a2 100644 --- a/openpype/settings/defaults/project_settings/max.json +++ b/openpype/settings/defaults/project_settings/max.json @@ -61,6 +61,11 @@ "optional": true, "family_plugins_mapping": [] }, + "ValidateRenderPasses": { + "enabled": true, + "optional": true, + "active": true + }, "ExtractModelObj": { "enabled": true, "optional": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json index b4d85bda98..4dd3d519e8 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json @@ -88,6 +88,31 @@ } ] } + }, + { + "type": "dict", + "collapsible": true, + "checkbox_key": "enabled", + "key": "ValidateRenderPasses", + "label": "Validate Render Passes", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "boolean", + "key": "active", + "label": "Active" + } + ] } ] }, diff --git a/server_addon/max/server/settings/publishers.py b/server_addon/max/server/settings/publishers.py index d40d85a99b..a10e72b36a 100644 --- a/server_addon/max/server/settings/publishers.py +++ b/server_addon/max/server/settings/publishers.py @@ -68,6 +68,10 @@ class PublishersModel(BaseSettingsModel): default_factory=ValidateLoadedPluginModel, title="Validate Loaded Plugin" ) + ValidateRenderPasses: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Render Passes" + ) ExtractModelObj: BasicValidateModel = Field( default_factory=BasicValidateModel, title="Extract OBJ", @@ -106,6 +110,11 @@ DEFAULT_PUBLISH_SETTINGS = { "optional": True, "family_plugins_mapping": [] }, + "ValidateRenderPasses": { + "enabled": True, + "optional": True, + "active": True + }, "ExtractModelObj": { "enabled": True, "optional": True, diff --git a/server_addon/max/server/version.py b/server_addon/max/server/version.py index bbab0242f6..1276d0254f 100644 --- a/server_addon/max/server/version.py +++ b/server_addon/max/server/version.py @@ -1 +1 @@ -__version__ = "0.1.4" +__version__ = "0.1.5" From f0b8f254a113b51e96bfbc7f70ed1d440684412c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 16 Jan 2024 18:33:37 +0800 Subject: [PATCH 002/150] add docstrings & fix the bug on the setting of validate render pass in legacy OP --- .../plugins/publish/validate_renderpasses.py | 21 ++++++++- .../schemas/schema_max_publish.json | 46 +++++++++---------- 2 files changed, 43 insertions(+), 24 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_renderpasses.py b/openpype/hosts/max/plugins/publish/validate_renderpasses.py index 9e65f305a2..f1bba81ecc 100644 --- a/openpype/hosts/max/plugins/publish/validate_renderpasses.py +++ b/openpype/hosts/max/plugins/publish/validate_renderpasses.py @@ -43,7 +43,26 @@ class ValidateRenderPasses(OptionalPyblishPluginMixin, @classmethod def get_invalid(cls, instance): """Function to get invalid beauty render outputs and - render elements + render elements. + + 1. Checking Render Output Folder matches the name of + the current Max Scene + e.g. The name of the current Max scene: + John_Doe.max + The expected render output directory: + {root[work]}/{project[name]}/{hierarchy} + /{asset}/work/{task[name]}/render/3dsmax/John_Doe/ + + 2. Checking the image extension(s) of the render output(s) + does not match with image format in OP/AYON setting. + e.g. The current image format in the setting: png + The expected render outputs: Joe_Doe.png + + 3. Checking the filename of render element is not ended + with the name of render element from the 3dsMax Render + Element Manager. + e.g. The name of render element: RsCryptomatte + The expected filename: {InstanceName}_RsCryptomatte.png Args: instance (pyblish.api.Instance): instance diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json index 4dd3d519e8..d0da4029f5 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json @@ -88,31 +88,31 @@ } ] } + } + ] + }, + { + "type": "dict", + "collapsible": true, + "checkbox_key": "enabled", + "key": "ValidateRenderPasses", + "label": "Validate Render Passes", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" }, { - "type": "dict", - "collapsible": true, - "checkbox_key": "enabled", - "key": "ValidateRenderPasses", - "label": "Validate Render Passes", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "boolean", - "key": "optional", - "label": "Optional" - }, - { - "type": "boolean", - "key": "active", - "label": "Active" - } - ] + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "boolean", + "key": "active", + "label": "Active" } ] }, From 2590648f93e8f99e82ff0f6fcdd1af8abe134eb6 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 16 Jan 2024 19:27:01 +0800 Subject: [PATCH 003/150] big roy's comment - docstring tweaks and some log message tweaks --- .../plugins/publish/validate_renderpasses.py | 55 ++++++++++--------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_renderpasses.py b/openpype/hosts/max/plugins/publish/validate_renderpasses.py index f1bba81ecc..8472b56336 100644 --- a/openpype/hosts/max/plugins/publish/validate_renderpasses.py +++ b/openpype/hosts/max/plugins/publish/validate_renderpasses.py @@ -7,7 +7,6 @@ from openpype.pipeline.publish import ( PublishValidationError, OptionalPyblishPluginMixin ) -from openpype.hosts.max.api.lib import get_current_renderer from openpype.hosts.max.api.lib_rendersettings import RenderSettings @@ -45,23 +44,22 @@ class ValidateRenderPasses(OptionalPyblishPluginMixin, """Function to get invalid beauty render outputs and render elements. - 1. Checking Render Output Folder matches the name of - the current Max Scene - e.g. The name of the current Max scene: - John_Doe.max - The expected render output directory: - {root[work]}/{project[name]}/{hierarchy} - /{asset}/work/{task[name]}/render/3dsmax/John_Doe/ + 1. Check Render Output Folder matches the name of + the current Max Scene, e.g. + The name of the current Max scene: + John_Doe.max + The expected render output directory: + {root[work]}/{project[name]}/{hierarchy}/{asset}/ + work/{task[name]}/render/3dsmax/John_Doe/ - 2. Checking the image extension(s) of the render output(s) - does not match with image format in OP/AYON setting. - e.g. The current image format in the setting: png - The expected render outputs: Joe_Doe.png + 2. Check image extension(s) of the render output(s) + matches the image format in OP/AYON setting, e.g. + The current image format in settings: png + The expected render outputs: John_Doe.png - 3. Checking the filename of render element is not ended - with the name of render element from the 3dsMax Render - Element Manager. - e.g. The name of render element: RsCryptomatte + 3. Check filename of render element ends with the name of + render element from the 3dsMax Render Element Manager. + e.g. The name of render element: RsCryptomatte The expected filename: {InstanceName}_RsCryptomatte.png Args: @@ -90,8 +88,7 @@ class ValidateRenderPasses(OptionalPyblishPluginMixin, invalid_image_format = cls.get_invalid_image_format( cls, instance, ext) invalid.extend(invalid_image_format) - renderer_class = get_current_renderer() - renderer = str(renderer_class).split(":")[0] + renderer = instance.data["renderer"] if renderer in [ "ART_Renderer", "Redshift_Renderer", @@ -107,17 +104,17 @@ class ValidateRenderPasses(OptionalPyblishPluginMixin, renderpass = str(renderlayer_name).split(":")[-1] rend_file = render_elem.GetRenderElementFilename(i) if not rend_file: - cls.log.error(f"No filepath for {renderpass}") + cls.log.error( + f"No filepath for render element {renderpass}") invalid.append((f"Invalid {renderpass}", "No filepath")) rend_fname, ext = os.path.splitext( os.path.basename(rend_file)) if not rend_fname.lstrip(".").endswith(renderpass): - err_msg = ( - f"Filename for {renderpass} should be " - f"ended with {renderpass}" + cls.log.error( + f"Filename for {renderpass} should " + f"end with {renderpass}" ) - cls.log.error(err_msg) invalid.append((f"Invalid {renderpass}", os.path.basename(rend_file))) invalid_image_format = cls.get_invalid_image_format( @@ -125,7 +122,8 @@ class ValidateRenderPasses(OptionalPyblishPluginMixin, invalid.extend(invalid_image_format) elif renderer == "Arnold": cls.log.debug( - "Temporarily not support to check on Arnold render.") + "Renderpass validation not supported Arnold yet," + " validation skipped...") return invalid @@ -143,10 +141,13 @@ class ValidateRenderPasses(OptionalPyblishPluginMixin, invalid = [] settings = instance.context.data["project_settings"].get("max") image_format = settings["RenderSettings"]["image_format"] - if ext.lstrip(".") != image_format: - msg = "Invalid image format for render outputs" + ext = ext.lstrip(".") + if ext != image_format: + msg = ( + f"Invalid image format {ext} for render outputs" + f"Should be : {image_format}") self.log.error(msg) - invalid.append((msg, ext.lstrip("."))) + invalid.append((msg, ext)) return invalid @classmethod From 38c6683756b4dbfd3dd9e276cbcdcc9070a91296 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 16 Jan 2024 20:13:12 +0800 Subject: [PATCH 004/150] codes tweaks on log message if it errors out --- .../max/plugins/publish/validate_renderpasses.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_renderpasses.py b/openpype/hosts/max/plugins/publish/validate_renderpasses.py index 8472b56336..2ad8f5601a 100644 --- a/openpype/hosts/max/plugins/publish/validate_renderpasses.py +++ b/openpype/hosts/max/plugins/publish/validate_renderpasses.py @@ -76,7 +76,7 @@ class ValidateRenderPasses(OptionalPyblishPluginMixin, if filename not in rt.rendOutputFilename: cls.log.error( "Render output folder " - "doesn't match the max scene name! " + f"doesn't match the max scene name {filename} " ) invalid_folder_name = os.path.dirname( rt.rendOutputFilename).replace( @@ -85,8 +85,7 @@ class ValidateRenderPasses(OptionalPyblishPluginMixin, invalid_folder_name)) beauty_fname = os.path.basename(rt.rendOutputFilename) ext = os.path.splitext(beauty_fname)[-1].lstrip(".") - invalid_image_format = cls.get_invalid_image_format( - cls, instance, ext) + invalid_image_format = cls.get_invalid_image_format(instance, ext) invalid.extend(invalid_image_format) renderer = instance.data["renderer"] if renderer in [ @@ -117,8 +116,7 @@ class ValidateRenderPasses(OptionalPyblishPluginMixin, ) invalid.append((f"Invalid {renderpass}", os.path.basename(rend_file))) - invalid_image_format = cls.get_invalid_image_format( - cls, instance, ext) + invalid_image_format = cls.get_invalid_image_format(instance, ext) invalid.extend(invalid_image_format) elif renderer == "Arnold": cls.log.debug( @@ -127,7 +125,8 @@ class ValidateRenderPasses(OptionalPyblishPluginMixin, return invalid - def get_invalid_image_format(self, instance, ext): + @classmethod + def get_invalid_image_format(cls, instance, ext): """Function to check if the image format of the render outputs aligns with that in the setting. @@ -146,12 +145,13 @@ class ValidateRenderPasses(OptionalPyblishPluginMixin, msg = ( f"Invalid image format {ext} for render outputs" f"Should be : {image_format}") - self.log.error(msg) + cls.log.error(msg) invalid.append((msg, ext)) return invalid @classmethod def repair(cls, instance): container = instance.data.get("instance_node") + # TODO: need to rename the function of render_output RenderSettings().render_output(container) - cls.log.debug("Reset the render output folder...") + cls.log.debug("Finished repairing the render output folder and filenames.") From 204504b6406c14a3cec118756ba7d530f6c3366c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 16 Jan 2024 20:15:05 +0800 Subject: [PATCH 005/150] hound shut --- openpype/hosts/max/plugins/publish/validate_renderpasses.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_renderpasses.py b/openpype/hosts/max/plugins/publish/validate_renderpasses.py index 2ad8f5601a..9b3064c393 100644 --- a/openpype/hosts/max/plugins/publish/validate_renderpasses.py +++ b/openpype/hosts/max/plugins/publish/validate_renderpasses.py @@ -116,7 +116,8 @@ class ValidateRenderPasses(OptionalPyblishPluginMixin, ) invalid.append((f"Invalid {renderpass}", os.path.basename(rend_file))) - invalid_image_format = cls.get_invalid_image_format(instance, ext) + invalid_image_format = cls.get_invalid_image_format( + instance, ext) invalid.extend(invalid_image_format) elif renderer == "Arnold": cls.log.debug( @@ -154,4 +155,5 @@ class ValidateRenderPasses(OptionalPyblishPluginMixin, container = instance.data.get("instance_node") # TODO: need to rename the function of render_output RenderSettings().render_output(container) - cls.log.debug("Finished repairing the render output folder and filenames.") + cls.log.debug("Finished repairing the render output " + "folder and filenames.") From bb6155738398f2e71c27f4012b72601d7ab3d911 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 17 Jan 2024 20:13:28 +0800 Subject: [PATCH 006/150] Debug message code tweaks --- openpype/hosts/max/plugins/publish/validate_renderpasses.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_renderpasses.py b/openpype/hosts/max/plugins/publish/validate_renderpasses.py index 9b3064c393..7b85763c74 100644 --- a/openpype/hosts/max/plugins/publish/validate_renderpasses.py +++ b/openpype/hosts/max/plugins/publish/validate_renderpasses.py @@ -144,8 +144,8 @@ class ValidateRenderPasses(OptionalPyblishPluginMixin, ext = ext.lstrip(".") if ext != image_format: msg = ( - f"Invalid image format {ext} for render outputs" - f"Should be : {image_format}") + f"Invalid image format {ext} for render outputs.\n" + f"Should be: {image_format}") cls.log.error(msg) invalid.append((msg, ext)) return invalid From 9a8700977df33ccd239a1c6d4f091636d76ca649 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 26 Jan 2024 18:26:41 +0800 Subject: [PATCH 007/150] make sure also validate the instance name inside the filename and make sure renderpass validated right --- .../plugins/publish/validate_renderpasses.py | 45 +++++++++++++++---- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_renderpasses.py b/openpype/hosts/max/plugins/publish/validate_renderpasses.py index 7b85763c74..b3f5c0325b 100644 --- a/openpype/hosts/max/plugins/publish/validate_renderpasses.py +++ b/openpype/hosts/max/plugins/publish/validate_renderpasses.py @@ -84,8 +84,12 @@ class ValidateRenderPasses(OptionalPyblishPluginMixin, invalid.append(("Invalid Render Output Folder", invalid_folder_name)) beauty_fname = os.path.basename(rt.rendOutputFilename) - ext = os.path.splitext(beauty_fname)[-1].lstrip(".") - invalid_image_format = cls.get_invalid_image_format(instance, ext) + beauty_name, ext = os.path.splitext(beauty_fname) + invalid_filenames = cls.get_invalid_filenames( + instance, beauty_name) + invalid.extend(invalid_filenames) + invalid_image_format = cls.get_invalid_image_format( + instance, ext.lstrip(".")) invalid.extend(invalid_image_format) renderer = instance.data["renderer"] if renderer in [ @@ -109,13 +113,9 @@ class ValidateRenderPasses(OptionalPyblishPluginMixin, "No filepath")) rend_fname, ext = os.path.splitext( os.path.basename(rend_file)) - if not rend_fname.lstrip(".").endswith(renderpass): - cls.log.error( - f"Filename for {renderpass} should " - f"end with {renderpass}" - ) - invalid.append((f"Invalid {renderpass}", - os.path.basename(rend_file))) + invalid_filenames = cls.get_invalid_filenames( + instance, rend_fname, renderpass=renderpass) + invalid.extend(invalid_filenames) invalid_image_format = cls.get_invalid_image_format( instance, ext) invalid.extend(invalid_image_format) @@ -126,6 +126,33 @@ class ValidateRenderPasses(OptionalPyblishPluginMixin, return invalid + @classmethod + def get_invalid_filenames(cls, instance, file_name, renderpass=None): + """Function to get invalid filenames from render outputs. + + Args: + instance (pyblish.api.Instance): instance + file_name (str): name of the file + renderpass (str, optional): name of the renderpass. Defaults to None. + + Returns: + list: invalid filenames + """ + invalid = [] + if instance.name not in file_name: + cls.log.error("The renderpass should have instance name inside.") + invalid.append((f"Invalid instance name", + file_name)) + if renderpass is not None: + if not file_name.rstrip(".").endswith(renderpass): + cls.log.error( + f"Filename for {renderpass} should " + f"end with {renderpass}" + ) + invalid.append((f"Invalid {renderpass}", + os.path.basename(file_name))) + return invalid + @classmethod def get_invalid_image_format(cls, instance, ext): """Function to check if the image format of the render outputs From 2b6f47035b1e9b21dc15c975c9ceb8584855ef57 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 26 Jan 2024 18:29:13 +0800 Subject: [PATCH 008/150] hound shut --- openpype/hosts/max/plugins/publish/validate_renderpasses.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/validate_renderpasses.py b/openpype/hosts/max/plugins/publish/validate_renderpasses.py index b3f5c0325b..e636b4e8fe 100644 --- a/openpype/hosts/max/plugins/publish/validate_renderpasses.py +++ b/openpype/hosts/max/plugins/publish/validate_renderpasses.py @@ -133,7 +133,8 @@ class ValidateRenderPasses(OptionalPyblishPluginMixin, Args: instance (pyblish.api.Instance): instance file_name (str): name of the file - renderpass (str, optional): name of the renderpass. Defaults to None. + renderpass (str, optional): name of the renderpass. + Defaults to None. Returns: list: invalid filenames From 6ab5c5218dd10930efe2bd92d19a83cb1614ed21 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 9 Feb 2024 11:23:30 +0200 Subject: [PATCH 009/150] add houdini deadline settings --- .../server/settings/publish_plugins.py | 66 +++++++++++++++++++ server_addon/deadline/server/version.py | 2 +- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/server_addon/deadline/server/settings/publish_plugins.py b/server_addon/deadline/server/settings/publish_plugins.py index 8abe59674b..3dcd8028ef 100644 --- a/server_addon/deadline/server/settings/publish_plugins.py +++ b/server_addon/deadline/server/settings/publish_plugins.py @@ -211,6 +211,49 @@ class HarmonySubmitDeadlineModel(BaseSettingsModel): department: str = SettingsField(title="Department") +class HoudiniSubmitCacheDeadlineModel(BaseSettingsModel): + priority: int = SettingsField(title="Priority") + chunk_size: int = SettingsField(title="Chunk Size") + group: str = SettingsField(title="Group") + + +class HoudiniSubmitExportDeadlineModel(BaseSettingsModel): + priority: int = SettingsField(title="Priority") + chunk_size: int = SettingsField(title="Chunk Size") + group: str = SettingsField(title="Group") + + +class HoudiniSubmitRenderDeadlineModel(BaseSettingsModel): + priority: int = SettingsField(title="Priority") + chunk_size: int = SettingsField(title="Chunk Size") + group: str = SettingsField(title="Group") + + +class HoudiniSubmitDeadlineModel(BaseSettingsModel): + """Houdini deadline submitter settings.""" + enabled: bool = SettingsField(title="Enabled") + optional: bool = SettingsField(title="Optional") + active: bool = SettingsField(title="Active") + HoudiniSubmitCacheDeadline: HoudiniSubmitCacheDeadlineModel = ( + SettingsField( + default_factory=HoudiniSubmitCacheDeadlineModel, + title="Submit Cache" + ) + ) + HoudiniSubmitExportDeadline: HoudiniSubmitExportDeadlineModel = ( + SettingsField( + default_factory=HoudiniSubmitExportDeadlineModel, + title="Submit Export Job" + ) + ) + HoudiniSubmitRenderDeadline: HoudiniSubmitRenderDeadlineModel = ( + SettingsField( + default_factory=HoudiniSubmitRenderDeadlineModel, + title="Submit Render" + ) + ) + + class AfterEffectsSubmitDeadlineModel(BaseSettingsModel): """After Effects deadline submitter settings.""" @@ -312,6 +355,9 @@ class PublishPluginsModel(BaseSettingsModel): MaxSubmitDeadline: MaxSubmitDeadlineModel = SettingsField( default_factory=MaxSubmitDeadlineModel, title="Max Submit to deadline") + HoudiniSubmitDeadline: HoudiniSubmitDeadlineModel = SettingsField( + default_factory=HoudiniSubmitDeadlineModel, + title="Houdini Submit to deadline") FusionSubmitDeadline: FusionSubmitDeadlineModel = SettingsField( default_factory=FusionSubmitDeadlineModel, title="Fusion submit to Deadline") @@ -385,6 +431,26 @@ DEFAULT_DEADLINE_PLUGINS_SETTINGS = { "chunk_size": 10, "group": "none" }, + "HoudiniSubmitDeadline":{ + "enabled": True, + "optional": False, + "active": True, + "HoudiniSubmitCacheDeadline":{ + "priority": 50, + "chunk_size":999999, + "group": "" + }, + "HoudiniSubmitExportDeadline":{ + "priority": 50, + "chunk_size":10, + "group": "" + }, + "HoudiniSubmitRenderDeadline":{ + "priority": 50, + "chunk_size":1, + "group": "" + } + }, "FusionSubmitDeadline": { "enabled": True, "optional": False, diff --git a/server_addon/deadline/server/version.py b/server_addon/deadline/server/version.py index 9cb17e7976..c11f861afb 100644 --- a/server_addon/deadline/server/version.py +++ b/server_addon/deadline/server/version.py @@ -1 +1 @@ -__version__ = "0.1.8" +__version__ = "0.1.9" From 96e12e2d487ffb07631fc24e7c06ba7545cdd402 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 9 Feb 2024 01:07:32 +0200 Subject: [PATCH 010/150] make collect pools work with some houdini families --- .../deadline/plugins/publish/collect_pools.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py b/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py index 6c35012173..7fe1dc6cd8 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py @@ -31,14 +31,22 @@ class CollectDeadlinePools(pyblish.api.InstancePlugin, "harmony" "nuke", "maya", - "max"] + "max", + "houdini"] families = ["render", "rendering", "render.farm", "renderFarm", "renderlayer", - "maxrender"] + "maxrender", + "usdrender", + "redshift_rop", + "arnold_rop", + "mantra_rop", + "karma_rop", + "vray_rop", + "publish.hou"] primary_pool = None secondary_pool = None From ae889c597ea21159e4524c0440c015a7a4ed2630 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 9 Feb 2024 01:16:52 +0200 Subject: [PATCH 011/150] Apply settings to Ayon Houdini submitters --- .../publish/submit_houdini_cache_deadline.py | 11 +++- .../publish/submit_houdini_render_deadline.py | 57 ++++++++++++------- 2 files changed, 48 insertions(+), 20 deletions(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py index eed930e372..df1f81d9a9 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py @@ -46,10 +46,19 @@ class HoudiniCacheSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline targets = ["local"] priority = 50 + ChunkSize = 999999 + group = None jobInfo = {} pluginInfo = {} group = None + @classmethod + def apply_settings(cls, project_settings, system_settings): + settings = project_settings["deadline"]["publish"]["HoudiniSubmitDeadline"]["HoudiniSubmitCacheDeadline"] # noqa + cls.priority = settings.get("priority", cls.priority) + cls.ChunkSize = settings.get("chunk_size", cls.ChunkSize) + cls.group = settings.get("group", cls.group) + def get_job_info(self): job_info = DeadlineJobInfo(Plugin="Houdini") @@ -89,7 +98,7 @@ class HoudiniCacheSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline attr_values = self.get_attr_values_from_data(instance.data) - job_info.ChunkSize = instance.data["chunkSize"] + job_info.ChunkSize = instance.data.get("chunkSize", self.ChunkSize) job_info.Comment = context.data.get("comment") job_info.Priority = attr_values.get("priority", self.priority) job_info.Group = attr_values.get("group", self.group) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py index 9988248957..5cb347bb97 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -11,6 +11,7 @@ from openpype_modules.deadline import abstract_submit_deadline from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo from ayon_core.lib import ( BoolDef, + TextDef, NumberDef ) @@ -76,16 +77,33 @@ class HoudiniSubmitDeadline( use_published = True # presets - priority = 50 - chunk_size = 1 export_priority = 50 export_chunk_size = 10 - group = "" export_group = "" + priority = 50 + chunk_size = 1 + group = "" + @classmethod + def apply_settings(cls, project_settings, system_settings): + export_settings = project_settings["deadline"]["publish"]["HoudiniSubmitDeadline"]["HoudiniSubmitExportDeadline"] # noqa + cls.export_priority = export_settings.get("priority", cls.export_priority) + cls.export_chunk_size = export_settings.get("chunk_size", cls.export_chunk_size) + cls.export_group = export_settings.get("group", cls.export_group) + + render_settings = project_settings["deadline"]["publish"]["HoudiniSubmitDeadline"]["HoudiniSubmitRenderDeadline"] # noqa + cls.priority = render_settings.get("priority", cls.priority) + cls.chunk_size = render_settings.get("chunk_size", cls.chunk_size) + cls.group = render_settings.get("group", cls.group) + @classmethod def get_attribute_defs(cls): return [ + BoolDef( + "suspend_publish", + default=False, + label="Suspend publish" + ), NumberDef( "priority", label="Priority", @@ -100,10 +118,14 @@ class HoudiniSubmitDeadline( minimum=1, maximum=1000 ), + TextDef("group", + default=cls.group, + label="Group Name" + ), NumberDef( "export_priority", label="Export Priority", - default=cls.priority, + default=cls.export_priority, decimals=0 ), NumberDef( @@ -114,11 +136,9 @@ class HoudiniSubmitDeadline( minimum=1, maximum=1000 ), - BoolDef( - "suspend_publish", - default=False, - label="Suspend publish" - ) + TextDef("export_group", + default=cls.export_group, + label="Export Group Name"), ] def get_job_info(self, dependency_job_ids=None): @@ -158,15 +178,6 @@ class HoudiniSubmitDeadline( job_info.UserName = context.data.get( "deadlineUser", getpass.getuser()) - if split_render_job and is_export_job: - job_info.Priority = attribute_values.get( - "export_priority", self.export_priority - ) - else: - job_info.Priority = attribute_values.get( - "priority", self.priority - ) - if is_in_tests(): job_info.BatchName += datetime.now().strftime("%d%m%Y%H%M%S") @@ -187,15 +198,23 @@ class HoudiniSubmitDeadline( job_info.Pool = instance.data.get("primaryPool") job_info.SecondaryPool = instance.data.get("secondaryPool") - job_info.Group = self.group + if split_render_job and is_export_job: + job_info.Priority = attribute_values.get( + "export_priority", self.export_priority + ) job_info.ChunkSize = attribute_values.get( "export_chunk", self.export_chunk_size ) + job_info.Group = self.export_group else: + job_info.Priority = attribute_values.get( + "priority", self.priority + ) job_info.ChunkSize = attribute_values.get( "chunk", self.chunk_size ) + job_info.Group = self.group job_info.Comment = context.data.get("comment") From 80e32464104395eb4eeff5d4c7f8e59ad5086dec Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 9 Feb 2024 01:19:20 +0200 Subject: [PATCH 012/150] remove redundant line --- .../deadline/plugins/publish/submit_houdini_cache_deadline.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py index df1f81d9a9..fda470bde3 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py @@ -50,7 +50,6 @@ class HoudiniCacheSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline group = None jobInfo = {} pluginInfo = {} - group = None @classmethod def apply_settings(cls, project_settings, system_settings): From 08413a80bbba6fe31d737cb9d0c4c7e4f8a4ff9e Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 9 Feb 2024 20:16:38 +0200 Subject: [PATCH 013/150] Kuba's comment - remove unnecessary logic, stick to code style --- .../publish/submit_houdini_cache_deadline.py | 10 +- .../publish/submit_houdini_render_deadline.py | 14 +-- .../server/settings/publish_plugins.py | 91 ++++++++----------- 3 files changed, 41 insertions(+), 74 deletions(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py index fda470bde3..7603fbaaf3 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py @@ -46,17 +46,11 @@ class HoudiniCacheSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline targets = ["local"] priority = 50 - ChunkSize = 999999 + chunk_size = 999999 group = None jobInfo = {} pluginInfo = {} - @classmethod - def apply_settings(cls, project_settings, system_settings): - settings = project_settings["deadline"]["publish"]["HoudiniSubmitDeadline"]["HoudiniSubmitCacheDeadline"] # noqa - cls.priority = settings.get("priority", cls.priority) - cls.ChunkSize = settings.get("chunk_size", cls.ChunkSize) - cls.group = settings.get("group", cls.group) def get_job_info(self): job_info = DeadlineJobInfo(Plugin="Houdini") @@ -97,7 +91,7 @@ class HoudiniCacheSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline attr_values = self.get_attr_values_from_data(instance.data) - job_info.ChunkSize = instance.data.get("chunkSize", self.ChunkSize) + job_info.ChunkSize = instance.data.get("chunk_size", self.chunk_size) job_info.Comment = context.data.get("comment") job_info.Priority = attr_values.get("priority", self.priority) job_info.Group = attr_values.get("group", self.group) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py index 5cb347bb97..63dc2712fe 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -83,19 +83,7 @@ class HoudiniSubmitDeadline( priority = 50 chunk_size = 1 group = "" - - @classmethod - def apply_settings(cls, project_settings, system_settings): - export_settings = project_settings["deadline"]["publish"]["HoudiniSubmitDeadline"]["HoudiniSubmitExportDeadline"] # noqa - cls.export_priority = export_settings.get("priority", cls.export_priority) - cls.export_chunk_size = export_settings.get("chunk_size", cls.export_chunk_size) - cls.export_group = export_settings.get("group", cls.export_group) - - render_settings = project_settings["deadline"]["publish"]["HoudiniSubmitDeadline"]["HoudiniSubmitRenderDeadline"] # noqa - cls.priority = render_settings.get("priority", cls.priority) - cls.chunk_size = render_settings.get("chunk_size", cls.chunk_size) - cls.group = render_settings.get("group", cls.group) - + @classmethod def get_attribute_defs(cls): return [ diff --git a/server_addon/deadline/server/settings/publish_plugins.py b/server_addon/deadline/server/settings/publish_plugins.py index 3dcd8028ef..fde076965b 100644 --- a/server_addon/deadline/server/settings/publish_plugins.py +++ b/server_addon/deadline/server/settings/publish_plugins.py @@ -211,47 +211,30 @@ class HarmonySubmitDeadlineModel(BaseSettingsModel): department: str = SettingsField(title="Department") -class HoudiniSubmitCacheDeadlineModel(BaseSettingsModel): - priority: int = SettingsField(title="Priority") - chunk_size: int = SettingsField(title="Chunk Size") - group: str = SettingsField(title="Group") - - -class HoudiniSubmitExportDeadlineModel(BaseSettingsModel): - priority: int = SettingsField(title="Priority") - chunk_size: int = SettingsField(title="Chunk Size") - group: str = SettingsField(title="Group") - - -class HoudiniSubmitRenderDeadlineModel(BaseSettingsModel): - priority: int = SettingsField(title="Priority") - chunk_size: int = SettingsField(title="Chunk Size") - group: str = SettingsField(title="Group") - - class HoudiniSubmitDeadlineModel(BaseSettingsModel): - """Houdini deadline submitter settings.""" + """Houdini deadline render submitter settings.""" enabled: bool = SettingsField(title="Enabled") optional: bool = SettingsField(title="Optional") active: bool = SettingsField(title="Active") - HoudiniSubmitCacheDeadline: HoudiniSubmitCacheDeadlineModel = ( - SettingsField( - default_factory=HoudiniSubmitCacheDeadlineModel, - title="Submit Cache" - ) - ) - HoudiniSubmitExportDeadline: HoudiniSubmitExportDeadlineModel = ( - SettingsField( - default_factory=HoudiniSubmitExportDeadlineModel, - title="Submit Export Job" - ) - ) - HoudiniSubmitRenderDeadline: HoudiniSubmitRenderDeadlineModel = ( - SettingsField( - default_factory=HoudiniSubmitRenderDeadlineModel, - title="Submit Render" - ) - ) + + priority: int = SettingsField(title="Priority") + chunk_size: int = SettingsField(title="Chunk Size") + group: str = SettingsField(title="Group") + + export_priority: int = SettingsField(title="Export Priority") + export_chunk_size: int = SettingsField(title="Export Chunk Size") + export_group: str = SettingsField(title="Export Group") + + +class HoudiniCacheSubmitDeadlineModel(BaseSettingsModel): + """Houdini deadline cache submitter settings.""" + enabled: bool = SettingsField(title="Enabled") + optional: bool = SettingsField(title="Optional") + active: bool = SettingsField(title="Active") + + priority: int = SettingsField(title="Priority") + chunk_size: int = SettingsField(title="Chunk Size") + group: str = SettingsField(title="Group") class AfterEffectsSubmitDeadlineModel(BaseSettingsModel): @@ -357,7 +340,10 @@ class PublishPluginsModel(BaseSettingsModel): title="Max Submit to deadline") HoudiniSubmitDeadline: HoudiniSubmitDeadlineModel = SettingsField( default_factory=HoudiniSubmitDeadlineModel, - title="Houdini Submit to deadline") + title="Houdini Submit render to deadline") + HoudiniCacheSubmitDeadline: HoudiniCacheSubmitDeadlineModel = SettingsField( + default_factory=HoudiniCacheSubmitDeadlineModel, + title="Houdini Submit cache to deadline") FusionSubmitDeadline: FusionSubmitDeadlineModel = SettingsField( default_factory=FusionSubmitDeadlineModel, title="Fusion submit to Deadline") @@ -435,21 +421,20 @@ DEFAULT_DEADLINE_PLUGINS_SETTINGS = { "enabled": True, "optional": False, "active": True, - "HoudiniSubmitCacheDeadline":{ - "priority": 50, - "chunk_size":999999, - "group": "" - }, - "HoudiniSubmitExportDeadline":{ - "priority": 50, - "chunk_size":10, - "group": "" - }, - "HoudiniSubmitRenderDeadline":{ - "priority": 50, - "chunk_size":1, - "group": "" - } + "priority": 50, + "chunk_size":1, + "group": "", + "export_priority": 50, + "export_chunk_size":10, + "export_group": "" + }, + "HoudiniCacheSubmitDeadline": { + "enabled": True, + "optional": False, + "active": True, + "priority": 50, + "chunk_size":999999, + "group": "" }, "FusionSubmitDeadline": { "enabled": True, From b2099c83b715ab49aecac2fdfae885aa5168bdd3 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 12 Feb 2024 18:19:12 +0200 Subject: [PATCH 014/150] bump deadline addon version --- server_addon/deadline/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/deadline/server/version.py b/server_addon/deadline/server/version.py index c11f861afb..569b1212f7 100644 --- a/server_addon/deadline/server/version.py +++ b/server_addon/deadline/server/version.py @@ -1 +1 @@ -__version__ = "0.1.9" +__version__ = "0.1.10" From b3a39ddcc38e63787c143cfc01d702e35f4d0562 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 13 Feb 2024 10:55:17 +0100 Subject: [PATCH 015/150] feat: Copy ACES input transform from timeline clip to new media item This commit adds functionality to copy the ACES input transform from a timeline clip to a new media item. It retrieves the IDT (Input Device Transform) property from the timeline clip and sets it on the new media item. Also, it updates the ClipLoader class in plugin.py to read trimming information from the timeline item instead of using "Start" and "End" properties. The start time is calculated as the left offset of the timeline item, and the end time is calculated by adding the duration of the timeline item to its start time. These changes improve compatibility and ensure accurate copying of clips in Ayon Core Resolve API. --- client/ayon_core/hosts/resolve/api/lib.py | 5 +++++ client/ayon_core/hosts/resolve/api/plugin.py | 10 ++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/hosts/resolve/api/lib.py b/client/ayon_core/hosts/resolve/api/lib.py index 2c648bb4cc..ab4f30ac64 100644 --- a/client/ayon_core/hosts/resolve/api/lib.py +++ b/client/ayon_core/hosts/resolve/api/lib.py @@ -713,6 +713,11 @@ def swap_clips(from_clip, to_clip, to_in_frame, to_out_frame): bool: True if successfully replaced """ + # copy ACES input transform from timeline clip to new media item + mediapool_item_from_timeline = from_clip.GetMediaPoolItem() + _idt = mediapool_item_from_timeline.GetClipProperty('IDT') + to_clip.SetClipProperty('IDT', _idt) + _clip_prop = to_clip.GetClipProperty to_clip_name = _clip_prop("File Name") # add clip item as take to timeline diff --git a/client/ayon_core/hosts/resolve/api/plugin.py b/client/ayon_core/hosts/resolve/api/plugin.py index ccb20f712f..73a7409f68 100644 --- a/client/ayon_core/hosts/resolve/api/plugin.py +++ b/client/ayon_core/hosts/resolve/api/plugin.py @@ -477,14 +477,16 @@ class ClipLoader: ) _clip_property = media_pool_item.GetClipProperty - source_in = int(_clip_property("Start")) - source_out = int(_clip_property("End")) + # Read trimming from timeline item + timeline_item_in = timeline_item.GetLeftOffset() + timeline_item_len = timeline_item.GetDuration() + timeline_item_out = timeline_item_in + timeline_item_len lib.swap_clips( timeline_item, media_pool_item, - source_in, - source_out + timeline_item_in, + timeline_item_out ) print("Loading clips: `{}`".format(self.data["clip_name"])) From d0b8ea08fbaa496421a04e78d331d34b13fe3f37 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 19 Feb 2024 16:38:08 +0100 Subject: [PATCH 016/150] Add assertion for creating media pool item if not found Asserts creation of media pool item when not found for specified files. This ensures proper handling and error messaging in such cases. --- client/ayon_core/hosts/resolve/api/plugin.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/ayon_core/hosts/resolve/api/plugin.py b/client/ayon_core/hosts/resolve/api/plugin.py index ccb20f712f..bcdc30d20f 100644 --- a/client/ayon_core/hosts/resolve/api/plugin.py +++ b/client/ayon_core/hosts/resolve/api/plugin.py @@ -405,6 +405,11 @@ class ClipLoader: files, self.active_bin ) + + assert media_pool_item, AssertionError( + "Cannot create media pool item for files: `{}`".format(files) + ) + _clip_property = media_pool_item.GetClipProperty source_in = int(_clip_property("Start")) source_out = int(_clip_property("End")) From 3addd98805782356677c23303d62f992c28a4e1b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 27 Feb 2024 13:59:16 +0100 Subject: [PATCH 017/150] Add LoadError handling in ClipLoader class ClipLoader class in the plugin file now handles LoadError exceptions for creating media pool items when processing files. This change ensures proper error handling during media item creation. --- client/ayon_core/hosts/resolve/api/plugin.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/resolve/api/plugin.py b/client/ayon_core/hosts/resolve/api/plugin.py index bcdc30d20f..e95c9da82d 100644 --- a/client/ayon_core/hosts/resolve/api/plugin.py +++ b/client/ayon_core/hosts/resolve/api/plugin.py @@ -11,6 +11,7 @@ from ayon_core.pipeline import ( LoaderPlugin, Anatomy ) +from ayon_core.pipeline.load import LoadError from . import lib from .menu import load_stylesheet @@ -406,7 +407,7 @@ class ClipLoader: self.active_bin ) - assert media_pool_item, AssertionError( + assert media_pool_item, LoadError( "Cannot create media pool item for files: `{}`".format(files) ) @@ -480,6 +481,11 @@ class ClipLoader: files, self.active_bin ) + + assert media_pool_item, LoadError( + "Cannot create media pool item for files: `{}`".format(files) + ) + _clip_property = media_pool_item.GetClipProperty source_in = int(_clip_property("Start")) From 254958f29bf7891c9fd41e23171f7dfbc6c4abe6 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 27 Feb 2024 15:04:46 +0100 Subject: [PATCH 018/150] Add check for MediaPoolItem existence before processing. Improve data handling in timeline items retrieval function. - Added a check to skip processing if MediaPoolItem doesn't exist. - Enhanced data handling within the timeline items retrieval function. - this is for case some adjustment clips or fusion clips are on timeline --- client/ayon_core/hosts/resolve/api/lib.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/ayon_core/hosts/resolve/api/lib.py b/client/ayon_core/hosts/resolve/api/lib.py index 5eb88afdcb..2d15c93f87 100644 --- a/client/ayon_core/hosts/resolve/api/lib.py +++ b/client/ayon_core/hosts/resolve/api/lib.py @@ -409,6 +409,9 @@ def get_current_timeline_items( } # get track item object and its color for clip_index, ti in enumerate(_clips[track_index]): + if not ti.GetMediaPoolItem(): + continue + data = _data.copy() data["clip"] = { "item": ti, From b23500187d5d70f92843fd659df90fdaebc331f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 29 Feb 2024 14:07:00 +0100 Subject: [PATCH 019/150] :recycle: remove context asserts and support for multiple paths --- client/ayon_core/cli.py | 13 +++--- client/ayon_core/cli_commands.py | 29 +++++++------ .../plugins/publish/collect_rendered_files.py | 43 ++++++++----------- 3 files changed, 40 insertions(+), 45 deletions(-) diff --git a/client/ayon_core/cli.py b/client/ayon_core/cli.py index 88b574da76..0b59589ebc 100644 --- a/client/ayon_core/cli.py +++ b/client/ayon_core/cli.py @@ -102,19 +102,18 @@ def extractenvironments(output_json_path, project, asset, task, app, envgroup): @main_cli.command() -@click.argument("paths", nargs=-1) -@click.option("-t", "--targets", help="Targets module", default=None, +@click.argument("path", nargs=1) +@click.option("-t", "--targets", help="Targets", default=None, multiple=True) @click.option("-g", "--gui", is_flag=True, help="Show Publish UI", default=False) -def publish(paths, targets, gui): +def publish(path, targets, gui): """Start CLI publishing. - Publish collects json from paths provided as an argument. - More than one path is allowed. + Publish collects json from path provided as an argument. +S """ - - Commands.publish(list(paths), targets, gui) + Commands.publish(path, targets, gui) @main_cli.command(context_settings={"ignore_unknown_options": True}) diff --git a/client/ayon_core/cli_commands.py b/client/ayon_core/cli_commands.py index a24710aef2..dc11187990 100644 --- a/client/ayon_core/cli_commands.py +++ b/client/ayon_core/cli_commands.py @@ -3,6 +3,7 @@ import os import sys import json +import warnings class Commands: @@ -41,21 +42,21 @@ class Commands: return click_func @staticmethod - def publish(paths, targets=None, gui=False): + def publish(path: str, targets: list=None, gui:bool=False) -> None: """Start headless publishing. - Publish use json from passed paths argument. + Publish use json from passed path argument. Args: - paths (list): Paths to jsons. - targets (string): What module should be targeted - (to choose validator for example) + path (str): Path to JSON. + targets (list of str): List of pyblish targets. gui (bool): Show publish UI. Raises: RuntimeError: When there is no path to process. - """ + RuntimeError: When executed with list of JSON paths. + """ from ayon_core.lib import Logger from ayon_core.lib.applications import ( get_app_environments_for_context, @@ -73,6 +74,11 @@ class Commands: import pyblish.api import pyblish.util + if not isinstance(path, str): + warnings.warn( + "Passing list of paths is deprecated.", + DeprecationWarning) + # Fix older jobs for src_key, dst_key in ( ("AVALON_PROJECT", "AYON_PROJECT_NAME"), @@ -95,11 +101,8 @@ class Commands: publish_paths = manager.collect_plugin_paths()["publish"] - for path in publish_paths: - pyblish.api.register_plugin_path(path) - - if not any(paths): - raise RuntimeError("No publish paths specified") + for plugin_path in publish_paths: + pyblish.api.register_plugin_path(plugin_path) app_full_name = os.getenv("AYON_APP_NAME") if app_full_name: @@ -111,7 +114,7 @@ class Commands: app_full_name, launch_type=LaunchTypes.farm_publish, ) - os.environ.update(env) + os.environ |= env pyblish.api.register_host("shell") @@ -122,7 +125,7 @@ class Commands: else: pyblish.api.register_target("farm") - os.environ["AYON_PUBLISH_DATA"] = os.pathsep.join(paths) + os.environ["AYON_PUBLISH_DATA"] = os.pathsep.join(path) os.environ["HEADLESS_PUBLISH"] = 'true' # to use in app lib log.info("Running publish ...") diff --git a/client/ayon_core/plugins/publish/collect_rendered_files.py b/client/ayon_core/plugins/publish/collect_rendered_files.py index ca88a7aa82..152771da6f 100644 --- a/client/ayon_core/plugins/publish/collect_rendered_files.py +++ b/client/ayon_core/plugins/publish/collect_rendered_files.py @@ -36,18 +36,18 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): def _load_json(self, path): path = path.strip('\"') - assert os.path.isfile(path), ( - "Path to json file doesn't exist. \"{}\"".format(path) - ) + + if not os.path.isfile(path): + raise FileNotFoundError( + f"Path to json file doesn't exist. \"{path}\"") + data = None with open(path, "r") as json_file: try: data = json.load(json_file) except Exception as exc: self.log.error( - "Error loading json: " - "{} - Exception: {}".format(path, exc) - ) + "Error loading json: %s - Exception: %s", path, exc) return data def _fill_staging_dir(self, data_object, anatomy): @@ -73,30 +73,23 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): data_err = "invalid json file - missing data" required = ["user", "comment", "job", "instances", "version"] - assert all(elem in data.keys() for elem in required), data_err + + if any(elem not in data.keys() for elem in required): + raise ValueError(data_err) + if "folderPath" not in data and "asset" not in data: - raise AssertionError(data_err) + raise ValueError(data_err) if "folderPath" not in data: data["folderPath"] = data.pop("asset") - # set context by first json file - ctx = self._context.data - - ctx["folderPath"] = ctx.get("folderPath") or data.get("folderPath") - ctx["intent"] = ctx.get("intent") or data.get("intent") - ctx["comment"] = ctx.get("comment") or data.get("comment") - ctx["user"] = ctx.get("user") or data.get("user") - ctx["version"] = ctx.get("version") or data.get("version") - - # basic sanity check to see if we are working in same context - # if some other json file has different context, bail out. - ctx_err = "inconsistent contexts in json files - %s" - assert ctx.get("folderPath") == data.get("folderPath"), ctx_err % "folderPath" - assert ctx.get("intent") == data.get("intent"), ctx_err % "intent" - assert ctx.get("comment") == data.get("comment"), ctx_err % "comment" - assert ctx.get("user") == data.get("user"), ctx_err % "user" - assert ctx.get("version") == data.get("version"), ctx_err % "version" + # ftrack credentials are passed as environment variables by Deadline + # to publish job, but Muster doesn't pass them. + if data.get("ftrack") and not os.environ.get("FTRACK_API_USER"): + ftrack = data.get("ftrack") + os.environ["FTRACK_API_USER"] = ftrack["FTRACK_API_USER"] + os.environ["FTRACK_API_KEY"] = ftrack["FTRACK_API_KEY"] + os.environ["FTRACK_SERVER"] = ftrack["FTRACK_SERVER"] # now we can just add instances from json file and we are done any_staging_dir_persistent = False From 7955ab5e88e36d199fbace8b0a490b7c3474a871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 1 Mar 2024 11:20:41 +0100 Subject: [PATCH 020/150] :bug: path as string --- client/ayon_core/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/cli.py b/client/ayon_core/cli.py index 0b59589ebc..52eadccdd4 100644 --- a/client/ayon_core/cli.py +++ b/client/ayon_core/cli.py @@ -102,7 +102,7 @@ def extractenvironments(output_json_path, project, asset, task, app, envgroup): @main_cli.command() -@click.argument("path", nargs=1) +@click.argument("path", required=True) @click.option("-t", "--targets", help="Targets", default=None, multiple=True) @click.option("-g", "--gui", is_flag=True, From 14385cf12aef8aff6f3d2d1ae1dd9a7868ac7591 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 1 Mar 2024 11:26:34 +0100 Subject: [PATCH 021/150] :bug: raise exception instead of deprecation warning --- client/ayon_core/cli_commands.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/client/ayon_core/cli_commands.py b/client/ayon_core/cli_commands.py index dc11187990..08933a5501 100644 --- a/client/ayon_core/cli_commands.py +++ b/client/ayon_core/cli_commands.py @@ -75,9 +75,7 @@ class Commands: import pyblish.util if not isinstance(path, str): - warnings.warn( - "Passing list of paths is deprecated.", - DeprecationWarning) + raise RuntimeError("Path to JSON must be a string.") # Fix older jobs for src_key, dst_key in ( From 25573cebd5d27e8c915bdb5102b093b3345e4e02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 1 Mar 2024 11:49:04 +0100 Subject: [PATCH 022/150] :recycle: small refactors --- client/ayon_core/cli_commands.py | 4 ++-- client/ayon_core/plugins/publish/collect_rendered_files.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/cli_commands.py b/client/ayon_core/cli_commands.py index 08933a5501..f50ad61622 100644 --- a/client/ayon_core/cli_commands.py +++ b/client/ayon_core/cli_commands.py @@ -112,7 +112,7 @@ class Commands: app_full_name, launch_type=LaunchTypes.farm_publish, ) - os.environ |= env + os.environ.update(env) pyblish.api.register_host("shell") @@ -123,7 +123,7 @@ class Commands: else: pyblish.api.register_target("farm") - os.environ["AYON_PUBLISH_DATA"] = os.pathsep.join(path) + os.environ["AYON_PUBLISH_DATA"] = path os.environ["HEADLESS_PUBLISH"] = 'true' # to use in app lib log.info("Running publish ...") diff --git a/client/ayon_core/plugins/publish/collect_rendered_files.py b/client/ayon_core/plugins/publish/collect_rendered_files.py index 152771da6f..8a60e7619d 100644 --- a/client/ayon_core/plugins/publish/collect_rendered_files.py +++ b/client/ayon_core/plugins/publish/collect_rendered_files.py @@ -74,7 +74,7 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): required = ["user", "comment", "job", "instances", "version"] - if any(elem not in data.keys() for elem in required): + if any(elem not in data for elem in required): raise ValueError(data_err) if "folderPath" not in data and "asset" not in data: From 1204c5f2c736cf0b16403569bfcf8d1850369cdc Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 4 Mar 2024 21:13:45 +0800 Subject: [PATCH 023/150] tweaks on the debug message --- .../hosts/max/plugins/publish/validate_renderpasses.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_renderpasses.py b/client/ayon_core/hosts/max/plugins/publish/validate_renderpasses.py index 52c4c14367..1e4990ae3a 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_renderpasses.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_renderpasses.py @@ -75,8 +75,8 @@ class ValidateRenderPasses(OptionalPyblishPluginMixin, filename, ext = os.path.splitext(file) if filename not in rt.rendOutputFilename: cls.log.error( - "Render output folder " - f"doesn't match the max scene name {filename} " + "Render output folder must include" + f"the max scene name {filename} " ) invalid_folder_name = os.path.dirname( rt.rendOutputFilename).replace( @@ -121,9 +121,8 @@ class ValidateRenderPasses(OptionalPyblishPluginMixin, invalid.extend(invalid_image_format) elif renderer == "Arnold": cls.log.debug( - "Renderpass validation not supported Arnold yet," + "Renderpass validation does not support Arnold yet," " validation skipped...") - return invalid @classmethod From e01de841c0490dfd96702256c1cdcfeb2c1dda65 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 4 Mar 2024 21:25:00 +0800 Subject: [PATCH 024/150] missing space --- .../hosts/max/plugins/publish/validate_renderpasses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_renderpasses.py b/client/ayon_core/hosts/max/plugins/publish/validate_renderpasses.py index 1e4990ae3a..4e2298045c 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_renderpasses.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_renderpasses.py @@ -76,7 +76,7 @@ class ValidateRenderPasses(OptionalPyblishPluginMixin, if filename not in rt.rendOutputFilename: cls.log.error( "Render output folder must include" - f"the max scene name {filename} " + f" the max scene name {filename} " ) invalid_folder_name = os.path.dirname( rt.rendOutputFilename).replace( From a26f17c1cb96a9618aae92a2a6eb0f62a909496a Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 11 Mar 2024 12:15:10 +0200 Subject: [PATCH 025/150] fix code style --- .../publish/submit_houdini_render_deadline.py | 15 +++++++++------ .../deadline/server/settings/publish_plugins.py | 8 ++++---- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py index 2f359da80b..6952604293 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -109,9 +109,10 @@ class HoudiniSubmitDeadline( minimum=1, maximum=1000 ), - TextDef("group", - default=cls.group, - label="Group Name" + TextDef( + "group", + default=cls.group, + label="Group Name" ), NumberDef( "export_priority", @@ -127,9 +128,11 @@ class HoudiniSubmitDeadline( minimum=1, maximum=1000 ), - TextDef("export_group", - default=cls.export_group, - label="Export Group Name"), + TextDef( + "export_group", + default=cls.export_group, + label="Export Group Name" + ), ] def get_job_info(self, dependency_job_ids=None): diff --git a/server_addon/deadline/server/settings/publish_plugins.py b/server_addon/deadline/server/settings/publish_plugins.py index 746fd54d57..9f69143e37 100644 --- a/server_addon/deadline/server/settings/publish_plugins.py +++ b/server_addon/deadline/server/settings/publish_plugins.py @@ -453,18 +453,18 @@ DEFAULT_DEADLINE_PLUGINS_SETTINGS = { "optional": False, "active": True, "priority": 50, - "chunk_size":999999, + "chunk_size": 999999, "group": "" }, - "HoudiniSubmitDeadline":{ + "HoudiniSubmitDeadline": { "enabled": True, "optional": False, "active": True, "priority": 50, - "chunk_size":1, + "chunk_size": 1, "group": "", "export_priority": 50, - "export_chunk_size":10, + "export_chunk_size": 10, "export_group": "" }, "MaxSubmitDeadline": { From 4565db8841e6ad3295d037055edd192632c7aec9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 12 Mar 2024 10:46:15 +0100 Subject: [PATCH 026/150] ignoring clips without linked mediapoolitems --- client/ayon_core/hosts/resolve/api/lib.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/hosts/resolve/api/lib.py b/client/ayon_core/hosts/resolve/api/lib.py index 2d15c93f87..731da6fc6f 100644 --- a/client/ayon_core/hosts/resolve/api/lib.py +++ b/client/ayon_core/hosts/resolve/api/lib.py @@ -330,19 +330,25 @@ def get_timeline_item(media_pool_item: object, Returns: object: resolve.TimelineItem """ - _clip_property = media_pool_item.GetClipProperty - clip_name = _clip_property("File Name") + clip_name = media_pool_item.GetClipProperty("File Name") output_timeline_item = None timeline = timeline or get_current_timeline() with maintain_current_timeline(timeline): # search the timeline for the added clip - for _ti_data in get_current_timeline_items(): - _ti_clip = _ti_data["clip"]["item"] - _ti_clip_property = _ti_clip.GetMediaPoolItem().GetClipProperty - if clip_name in _ti_clip_property("File Name"): - output_timeline_item = _ti_clip + for ti_data in get_current_timeline_items(): + ti_clip_item = ti_data["clip"]["item"] + ti_media_pool_item = ti_clip_item.GetMediaPoolItem() + + # Skip items that do not have a media pool item, like for example + # an "Adjustment Clip" or a "Fusion Composition" from the effects + # toolbox + if not ti_media_pool_item: + continue + + if clip_name in ti_media_pool_item.GetClipProperty("File Name"): + output_timeline_item = ti_clip_item return output_timeline_item From 466a3c977cf6be9150a738f50476b72d10eb5903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Tue, 12 Mar 2024 10:57:38 +0100 Subject: [PATCH 027/150] reversing changes since this situation will not happen --- client/ayon_core/hosts/resolve/api/plugin.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/client/ayon_core/hosts/resolve/api/plugin.py b/client/ayon_core/hosts/resolve/api/plugin.py index e95c9da82d..ccb20f712f 100644 --- a/client/ayon_core/hosts/resolve/api/plugin.py +++ b/client/ayon_core/hosts/resolve/api/plugin.py @@ -11,7 +11,6 @@ from ayon_core.pipeline import ( LoaderPlugin, Anatomy ) -from ayon_core.pipeline.load import LoadError from . import lib from .menu import load_stylesheet @@ -406,11 +405,6 @@ class ClipLoader: files, self.active_bin ) - - assert media_pool_item, LoadError( - "Cannot create media pool item for files: `{}`".format(files) - ) - _clip_property = media_pool_item.GetClipProperty source_in = int(_clip_property("Start")) source_out = int(_clip_property("End")) @@ -481,11 +475,6 @@ class ClipLoader: files, self.active_bin ) - - assert media_pool_item, LoadError( - "Cannot create media pool item for files: `{}`".format(files) - ) - _clip_property = media_pool_item.GetClipProperty source_in = int(_clip_property("Start")) From 4cf6e1a443412314cdd1e7f69a050082a56540af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Tue, 12 Mar 2024 10:58:45 +0100 Subject: [PATCH 028/150] Update client/ayon_core/hosts/resolve/api/lib.py --- client/ayon_core/hosts/resolve/api/lib.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/client/ayon_core/hosts/resolve/api/lib.py b/client/ayon_core/hosts/resolve/api/lib.py index 731da6fc6f..dd97303383 100644 --- a/client/ayon_core/hosts/resolve/api/lib.py +++ b/client/ayon_core/hosts/resolve/api/lib.py @@ -415,9 +415,6 @@ def get_current_timeline_items( } # get track item object and its color for clip_index, ti in enumerate(_clips[track_index]): - if not ti.GetMediaPoolItem(): - continue - data = _data.copy() data["clip"] = { "item": ti, From 3227afae7d7e1f30314a0ad1d39f6c08b3d5b5d2 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 12 Mar 2024 16:10:54 +0100 Subject: [PATCH 029/150] Fix when no RR addon in bundle --- client/ayon_core/hosts/maya/plugins/publish/collect_render.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/collect_render.py b/client/ayon_core/hosts/maya/plugins/publish/collect_render.py index 19e0c133c4..3294e1d609 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/collect_render.py +++ b/client/ayon_core/hosts/maya/plugins/publish/collect_render.py @@ -290,8 +290,8 @@ class CollectMayaRender(pyblish.api.InstancePlugin): "colorspaceView": colorspace_data["view"], } - rr_settings = context.data["project_settings"]["royalrender"] - if rr_settings["enabled"]: + rr_settings = context.data["project_settings"].get("royalrender", {}) + if rr_settings: data["rrPathName"] = instance.data.get("rrPathName") self.log.debug(data["rrPathName"]) From 8cff941a0562e98a33925f73cb1494e3e2a61c5f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 13 Mar 2024 10:31:17 +0100 Subject: [PATCH 030/150] Changed to use module manager Settings are not necessary, used only to check if there is a RR addon enabled. This seems cleanere. --- client/ayon_core/hosts/maya/plugins/publish/collect_render.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/collect_render.py b/client/ayon_core/hosts/maya/plugins/publish/collect_render.py index 3294e1d609..13eb8fd49e 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/collect_render.py +++ b/client/ayon_core/hosts/maya/plugins/publish/collect_render.py @@ -290,8 +290,8 @@ class CollectMayaRender(pyblish.api.InstancePlugin): "colorspaceView": colorspace_data["view"], } - rr_settings = context.data["project_settings"].get("royalrender", {}) - if rr_settings: + manager = context.data["ayonAddonsManager"] + if manager.get_enabled_addon("royalrender") is not None: data["rrPathName"] = instance.data.get("rrPathName") self.log.debug(data["rrPathName"]) From b908ddcedb7e0c29d9d3ca3df35d5940d8e56d82 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Mar 2024 18:10:22 +0100 Subject: [PATCH 031/150] split anatomy.py into multiple python files --- client/ayon_core/pipeline/anatomy.py | 1550 ----------------- client/ayon_core/pipeline/anatomy/__init__.py | 17 + client/ayon_core/pipeline/anatomy/anatomy.py | 502 ++++++ .../ayon_core/pipeline/anatomy/exceptions.py | 39 + client/ayon_core/pipeline/anatomy/roots.py | 534 ++++++ .../ayon_core/pipeline/anatomy/templates.py | 523 ++++++ 6 files changed, 1615 insertions(+), 1550 deletions(-) delete mode 100644 client/ayon_core/pipeline/anatomy.py create mode 100644 client/ayon_core/pipeline/anatomy/__init__.py create mode 100644 client/ayon_core/pipeline/anatomy/anatomy.py create mode 100644 client/ayon_core/pipeline/anatomy/exceptions.py create mode 100644 client/ayon_core/pipeline/anatomy/roots.py create mode 100644 client/ayon_core/pipeline/anatomy/templates.py diff --git a/client/ayon_core/pipeline/anatomy.py b/client/ayon_core/pipeline/anatomy.py deleted file mode 100644 index d6e09bad39..0000000000 --- a/client/ayon_core/pipeline/anatomy.py +++ /dev/null @@ -1,1550 +0,0 @@ -import os -import re -import copy -import platform -import collections -import numbers -import time - -import six -import ayon_api - -from ayon_core.lib import Logger, get_local_site_id -from ayon_core.lib.path_templates import ( - TemplateUnsolved, - TemplateResult, - StringTemplate, - TemplatesDict, - FormatObject, -) -from ayon_core.addon import AddonsManager - -log = Logger.get_logger(__name__) - - -class ProjectNotSet(Exception): - """Exception raised when is created Anatomy without project name.""" - - -class RootCombinationError(Exception): - """This exception is raised when templates has combined root types.""" - - def __init__(self, roots): - joined_roots = ", ".join( - ["\"{}\"".format(_root) for _root in roots] - ) - # TODO better error message - msg = ( - "Combination of root with and" - " without root name in AnatomyTemplates. {}" - ).format(joined_roots) - - super(RootCombinationError, self).__init__(msg) - - -class BaseAnatomy(object): - """Anatomy module helps to keep project settings. - - Wraps key project specifications, AnatomyTemplates and Roots. - """ - root_key_regex = re.compile(r"{(root?[^}]+)}") - root_name_regex = re.compile(r"root\[([^]]+)\]") - - def __init__(self, project_entity, root_overrides=None): - project_name = project_entity["name"] - self.project_name = project_name - self.project_code = project_entity["code"] - - self._data = self._prepare_anatomy_data( - project_entity, root_overrides - ) - self._templates_obj = AnatomyTemplates(self) - self._roots_obj = Roots(self) - - # Anatomy used as dictionary - # - implemented only getters returning copy - def __getitem__(self, key): - return copy.deepcopy(self._data[key]) - - def get(self, key, default=None): - return copy.deepcopy(self._data).get(key, default) - - def keys(self): - return copy.deepcopy(self._data).keys() - - def values(self): - return copy.deepcopy(self._data).values() - - def items(self): - return copy.deepcopy(self._data).items() - - def _prepare_anatomy_data(self, project_entity, root_overrides): - """Prepare anatomy data for further processing. - - Method added to replace `{task}` with `{task[name]}` in templates. - """ - - anatomy_data = self._project_entity_to_anatomy_data(project_entity) - - self._apply_local_settings_on_anatomy_data( - anatomy_data, - root_overrides - ) - - return anatomy_data - - @property - def templates(self): - """Wrap property `templates` of Anatomy's AnatomyTemplates instance.""" - return self._templates_obj.templates - - @property - def templates_obj(self): - """Return `AnatomyTemplates` object of current Anatomy instance.""" - return self._templates_obj - - def format(self, *args, **kwargs): - """Wrap `format` method of Anatomy's `templates_obj`.""" - return self._templates_obj.format(*args, **kwargs) - - def format_all(self, *args, **kwargs): - """Wrap `format_all` method of Anatomy's `templates_obj`.""" - return self._templates_obj.format_all(*args, **kwargs) - - @property - def roots(self): - """Wrap `roots` property of Anatomy's `roots_obj`.""" - return self._roots_obj.roots - - @property - def roots_obj(self): - """Return `Roots` object of current Anatomy instance.""" - return self._roots_obj - - def root_environments(self): - """Return AYON_PROJECT_ROOT_* environments for current project.""" - return self._roots_obj.root_environments() - - def root_environmets_fill_data(self, template=None): - """Environment variable values in dictionary for rootless path. - - Args: - template (str): Template for environment variable key fill. - By default is set to `"${}"`. - """ - return self.roots_obj.root_environmets_fill_data(template) - - def find_root_template_from_path(self, *args, **kwargs): - """Wrapper for Roots `find_root_template_from_path`.""" - return self.roots_obj.find_root_template_from_path(*args, **kwargs) - - def path_remapper(self, *args, **kwargs): - """Wrapper for Roots `path_remapper`.""" - return self.roots_obj.path_remapper(*args, **kwargs) - - def all_root_paths(self): - """Wrapper for Roots `all_root_paths`.""" - return self.roots_obj.all_root_paths() - - def set_root_environments(self): - """Set AYON_PROJECT_ROOT_* environments for current project.""" - self._roots_obj.set_root_environments() - - def root_names(self): - """Return root names for current project.""" - return self.root_names_from_templates(self.templates) - - def _root_keys_from_templates(self, data): - """Extract root key from templates in data. - - Args: - data (dict): Data that may contain templates as string. - - Return: - set: Set of all root names from templates as strings. - - Output example: `{"root[work]", "root[publish]"}` - """ - - output = set() - if isinstance(data, dict): - for value in data.values(): - for root in self._root_keys_from_templates(value): - output.add(root) - - elif isinstance(data, str): - for group in re.findall(self.root_key_regex, data): - output.add(group) - - return output - - def root_value_for_template(self, template): - """Returns value of root key from template.""" - root_templates = [] - for group in re.findall(self.root_key_regex, template): - root_templates.append("{" + group + "}") - - if not root_templates: - return None - - return root_templates[0].format(**{"root": self.roots}) - - def root_names_from_templates(self, templates): - """Extract root names form anatomy templates. - - Returns None if values in templates contain only "{root}". - Empty list is returned if there is no "root" in templates. - Else returns all root names from templates in list. - - RootCombinationError is raised when templates contain both root types, - basic "{root}" and with root name specification "{root[work]}". - - Args: - templates (dict): Anatomy templates where roots are not filled. - - Return: - list/None: List of all root names from templates as strings when - multiroot setup is used, otherwise None is returned. - """ - roots = list(self._root_keys_from_templates(templates)) - # Return empty list if no roots found in templates - if not roots: - return roots - - # Raise exception when root keys have roots with and without root name. - # Invalid output example: ["root", "root[project]", "root[render]"] - if len(roots) > 1 and "root" in roots: - raise RootCombinationError(roots) - - # Return None if "root" without root name in templates - if len(roots) == 1 and roots[0] == "root": - return None - - names = set() - for root in roots: - for group in re.findall(self.root_name_regex, root): - names.add(group) - return list(names) - - def fill_root(self, template_path): - """Fill template path where is only "root" key unfilled. - - Args: - template_path (str): Path with "root" key in. - Example path: "{root}/projects/MyProject/Shot01/Lighting/..." - - Return: - str: formatted path - """ - # NOTE does not care if there are different keys than "root" - return template_path.format(**{"root": self.roots}) - - @classmethod - def fill_root_with_path(cls, rootless_path, root_path): - """Fill path without filled "root" key with passed path. - - This is helper to fill root with different directory path than anatomy - has defined no matter if is single or multiroot. - - Output path is same as input path if `rootless_path` does not contain - unfilled root key. - - Args: - rootless_path (str): Path without filled "root" key. Example: - "{root[work]}/MyProject/..." - root_path (str): What should replace root key in `rootless_path`. - - Returns: - str: Path with filled root. - """ - output = str(rootless_path) - for group in re.findall(cls.root_key_regex, rootless_path): - replacement = "{" + group + "}" - output = output.replace(replacement, root_path) - - return output - - def replace_root_with_env_key(self, filepath, template=None): - """Replace root of path with environment key. - - # Example: - ## Project with roots: - ``` - { - "nas": { - "windows": P:/projects", - ... - } - ... - } - ``` - - ## Entered filepath - "P:/projects/project/folder/task/animation_v001.ma" - - ## Entered template - "<{}>" - - ## Output - "/project/folder/task/animation_v001.ma" - - Args: - filepath (str): Full file path where root should be replaced. - template (str): Optional template for environment key. Must - have one index format key. - Default value if not entered: "${}" - - Returns: - str: Path where root is replaced with environment root key. - - Raise: - ValueError: When project's roots were not found in entered path. - """ - success, rootless_path = self.find_root_template_from_path(filepath) - if not success: - raise ValueError( - "{}: Project's roots were not found in path: {}".format( - self.project_name, filepath - ) - ) - - data = self.root_environmets_fill_data(template) - return rootless_path.format(**data) - - def _project_entity_to_anatomy_data(self, project_entity): - """Convert project document to anatomy data. - - Probably should fill missing keys and values. - """ - - output = copy.deepcopy(project_entity["config"]) - # TODO remove AYON convertion - task_types = copy.deepcopy(project_entity["taskTypes"]) - new_task_types = {} - for task_type in task_types: - name = task_type["name"] - new_task_types[name] = task_type - output["tasks"] = new_task_types - output["attributes"] = copy.deepcopy(project_entity["attrib"]) - - return output - - def _apply_local_settings_on_anatomy_data( - self, anatomy_data, root_overrides - ): - """Apply local settings on anatomy data. - - ATM local settings can modify project roots. Project name is required - as local settings have data stored data by project's name. - - Local settings override root values in this order: - 1.) Check if local settings contain overrides for default project and - apply it's values on roots if there are any. - 2.) If passed `project_name` is not None then check project specific - overrides in local settings for the project and apply it's value on - roots if there are any. - - NOTE: Root values of default project from local settings are always - applied if are set. - - Args: - anatomy_data (dict): Data for anatomy. - root_overrides (dict): Data of local settings. - """ - - # Skip processing if roots for current active site are not available in - # local settings - if not root_overrides: - return - - current_platform = platform.system().lower() - - root_data = anatomy_data["roots"] - for root_name, path in root_overrides.items(): - if root_name not in root_data: - continue - anatomy_data["roots"][root_name][current_platform] = ( - path - ) - - -class CacheItem: - """Helper to cache data. - - Helper does not handle refresh of data and does not mark data as outdated. - Who uses the object should check of outdated state on his own will. - """ - - default_lifetime = 10 - - def __init__(self, lifetime=None): - self._data = None - self._cached = None - self._lifetime = lifetime or self.default_lifetime - - @property - def data(self): - """Cached data/object. - - Returns: - Any: Whatever was cached. - """ - - return self._data - - @property - def is_outdated(self): - """Item has outdated cache. - - Lifetime of cache item expired or was not yet set. - - Returns: - bool: Item is outdated. - """ - - if self._cached is None: - return True - return (time.time() - self._cached) > self._lifetime - - def update_data(self, data): - """Update cache of data. - - Args: - data (Any): Data to cache. - """ - - self._data = data - self._cached = time.time() - - -class Anatomy(BaseAnatomy): - _sync_server_addon_cache = CacheItem() - _project_cache = collections.defaultdict(CacheItem) - _default_site_id_cache = collections.defaultdict(CacheItem) - _root_overrides_cache = collections.defaultdict( - lambda: collections.defaultdict(CacheItem) - ) - - def __init__( - self, project_name=None, site_name=None, project_entity=None - ): - if not project_name: - project_name = os.environ.get("AYON_PROJECT_NAME") - - if not project_name: - raise ProjectNotSet(( - "Implementation bug: Project name is not set. Anatomy requires" - " to load data for specific project." - )) - - if not project_entity: - project_entity = self.get_project_entity_from_cache(project_name) - root_overrides = self._get_site_root_overrides( - project_name, site_name - ) - - super(Anatomy, self).__init__(project_entity, root_overrides) - - @classmethod - def get_project_entity_from_cache(cls, project_name): - project_cache = cls._project_cache[project_name] - if project_cache.is_outdated: - project_cache.update_data(ayon_api.get_project(project_name)) - return copy.deepcopy(project_cache.data) - - @classmethod - def get_sync_server_addon(cls): - if cls._sync_server_addon_cache.is_outdated: - manager = AddonsManager() - cls._sync_server_addon_cache.update_data( - manager.get_enabled_addon("sync_server") - ) - return cls._sync_server_addon_cache.data - - @classmethod - def _get_studio_roots_overrides(cls, project_name): - """This would return 'studio' site override by local settings. - - Notes: - This logic handles local overrides of studio site which may be - available even when sync server is not enabled. - Handling of 'studio' and 'local' site was separated as preparation - for AYON development where that will be received from - separated sources. - - Args: - project_name (str): Name of project. - - Returns: - Union[Dict[str, str], None]): Local root overrides. - """ - if not project_name: - return - return ayon_api.get_project_roots_for_site( - project_name, get_local_site_id() - ) - - @classmethod - def _get_site_root_overrides(cls, project_name, site_name): - """Get root overrides for site. - - Args: - project_name (str): Project name for which root overrides should be - received. - site_name (Union[str, None]): Name of site for which root overrides - should be returned. - """ - - # First check if sync server is available and enabled - sync_server = cls.get_sync_server_addon() - if sync_server is None or not sync_server.enabled: - # QUESTION is ok to force 'studio' when site sync is not enabled? - site_name = "studio" - - elif not site_name: - # Use sync server to receive active site name - project_cache = cls._default_site_id_cache[project_name] - if project_cache.is_outdated: - project_cache.update_data( - sync_server.get_active_site_type(project_name) - ) - site_name = project_cache.data - - site_cache = cls._root_overrides_cache[project_name][site_name] - if site_cache.is_outdated: - if site_name == "studio": - # Handle studio root overrides without sync server - # - studio root overrides can be done even without sync server - roots_overrides = cls._get_studio_roots_overrides( - project_name - ) - else: - # Ask sync server to get roots overrides - roots_overrides = sync_server.get_site_root_overrides( - project_name, site_name - ) - site_cache.update_data(roots_overrides) - return site_cache.data - - -class AnatomyTemplateUnsolved(TemplateUnsolved): - """Exception for unsolved template when strict is set to True.""" - - msg = "Anatomy template \"{0}\" is unsolved.{1}{2}" - - -class AnatomyTemplateResult(TemplateResult): - rootless = None - - def __new__(cls, result, rootless_path): - new_obj = super(AnatomyTemplateResult, cls).__new__( - cls, - str(result), - result.template, - result.solved, - result.used_values, - result.missing_keys, - result.invalid_types - ) - new_obj.rootless = rootless_path - return new_obj - - def validate(self): - if not self.solved: - raise AnatomyTemplateUnsolved( - self.template, - self.missing_keys, - self.invalid_types - ) - - def copy(self): - tmp = TemplateResult( - str(self), - self.template, - self.solved, - self.used_values, - self.missing_keys, - self.invalid_types - ) - return self.__class__(tmp, self.rootless) - - def normalized(self): - """Convert to normalized path.""" - - tmp = TemplateResult( - os.path.normpath(self), - self.template, - self.solved, - self.used_values, - self.missing_keys, - self.invalid_types - ) - return self.__class__(tmp, self.rootless) - - -class AnatomyStringTemplate(StringTemplate): - """String template which has access to anatomy.""" - - def __init__(self, anatomy_templates, template): - self.anatomy_templates = anatomy_templates - super(AnatomyStringTemplate, self).__init__(template) - - def format(self, data): - """Format template and add 'root' key to data if not available. - - Args: - data (dict[str, Any]): Formatting data for template. - - Returns: - AnatomyTemplateResult: Formatting result. - """ - - anatomy_templates = self.anatomy_templates - if not data.get("root"): - data = copy.deepcopy(data) - data["root"] = anatomy_templates.anatomy.roots - result = StringTemplate.format(self, data) - rootless_path = anatomy_templates.rootless_path_from_result(result) - return AnatomyTemplateResult(result, rootless_path) - - -class AnatomyTemplates(TemplatesDict): - inner_key_pattern = re.compile(r"(\{@.*?[^{}0]*\})") - inner_key_name_pattern = re.compile(r"\{@(.*?[^{}0]*)\}") - - def __init__(self, anatomy): - super(AnatomyTemplates, self).__init__() - self.anatomy = anatomy - self.loaded_project = None - - def reset(self): - self._raw_templates = None - self._templates = None - self._objected_templates = None - - @property - def project_name(self): - return self.anatomy.project_name - - @property - def roots(self): - return self.anatomy.roots - - @property - def templates(self): - self._validate_discovery() - return self._templates - - @property - def objected_templates(self): - self._validate_discovery() - return self._objected_templates - - def _validate_discovery(self): - if self.project_name != self.loaded_project: - self.reset() - - if self._templates is None: - self._discover() - self.loaded_project = self.project_name - - def _format_value(self, value, data): - if isinstance(value, RootItem): - return self._solve_dict(value, data) - return super(AnatomyTemplates, self)._format_value(value, data) - - @staticmethod - def _ayon_template_conversion(templates): - def _convert_template_item(template_item): - # Change 'directory' to 'folder' - if "directory" in template_item: - template_item["folder"] = template_item["directory"] - - if ( - "path" not in template_item - and "file" in template_item - and "folder" in template_item - ): - template_item["path"] = "/".join( - (template_item["folder"], template_item["file"]) - ) - - def _get_default_template_name(templates): - default_template = None - for name, template in templates.items(): - if name == "default": - return "default" - - if default_template is None: - default_template = name - - return default_template - - def _fill_template_category(templates, cat_templates, cat_key): - default_template_name = _get_default_template_name(cat_templates) - for template_name, cat_template in cat_templates.items(): - _convert_template_item(cat_template) - if template_name == default_template_name: - templates[cat_key] = cat_template - else: - new_name = "{}_{}".format(cat_key, template_name) - templates["others"][new_name] = cat_template - - others_templates = templates.pop("others", None) or {} - new_others_templates = {} - templates["others"] = new_others_templates - for name, template in others_templates.items(): - _convert_template_item(template) - new_others_templates[name] = template - - for key in ( - "work", - "publish", - "hero", - ): - cat_templates = templates.pop(key) - _fill_template_category(templates, cat_templates, key) - - delivery_templates = templates.pop("delivery", None) or {} - new_delivery_templates = {} - for name, delivery_template in delivery_templates.items(): - new_delivery_templates[name] = "/".join( - (delivery_template["directory"], delivery_template["file"]) - ) - templates["delivery"] = new_delivery_templates - - def set_templates(self, templates): - if not templates: - self.reset() - return - - templates = copy.deepcopy(templates) - # TODO remove AYON convertion - self._ayon_template_conversion(templates) - - self._raw_templates = copy.deepcopy(templates) - v_queue = collections.deque() - v_queue.append(templates) - while v_queue: - item = v_queue.popleft() - if not isinstance(item, dict): - continue - - for key in tuple(item.keys()): - value = item[key] - if isinstance(value, dict): - v_queue.append(value) - - elif ( - isinstance(value, six.string_types) - and "{task}" in value - ): - item[key] = value.replace("{task}", "{task[name]}") - - solved_templates = self.solve_template_inner_links(templates) - self._templates = solved_templates - self._objected_templates = self.create_objected_templates( - solved_templates - ) - - def _create_template_object(self, template): - return AnatomyStringTemplate(self, template) - - def default_templates(self): - """Return default templates data with solved inner keys.""" - return self.solve_template_inner_links( - self.anatomy["templates"] - ) - - def _discover(self): - """ Loads anatomy templates from yaml. - Default templates are loaded if project is not set or project does - not have set it's own. - TODO: create templates if not exist. - - Returns: - TemplatesResultDict: Contain templates data for current project of - default templates. - """ - - if self.project_name is None: - # QUESTION create project specific if not found? - raise AssertionError(( - "Project \"{0}\" does not have his own templates." - " Trying to use default." - ).format(self.project_name)) - - self.set_templates(self.anatomy["templates"]) - - @classmethod - def replace_inner_keys(cls, matches, value, key_values, key): - """Replacement of inner keys in template values.""" - for match in matches: - anatomy_sub_keys = ( - cls.inner_key_name_pattern.findall(match) - ) - if key in anatomy_sub_keys: - raise ValueError(( - "Unsolvable recursion in inner keys, " - "key: \"{}\" is in his own value." - " Can't determine source, please check Anatomy templates." - ).format(key)) - - for anatomy_sub_key in anatomy_sub_keys: - replace_value = key_values.get(anatomy_sub_key) - if replace_value is None: - raise KeyError(( - "Anatomy templates can't be filled." - " Anatomy key `{0}` has" - " invalid inner key `{1}`." - ).format(key, anatomy_sub_key)) - - if not ( - isinstance(replace_value, numbers.Number) - or isinstance(replace_value, six.string_types) - ): - raise ValueError(( - "Anatomy templates can't be filled." - " Anatomy key `{0}` has" - " invalid inner key `{1}`" - " with value `{2}`." - ).format(key, anatomy_sub_key, str(replace_value))) - - value = value.replace(match, str(replace_value)) - - return value - - @classmethod - def prepare_inner_keys(cls, key_values): - """Check values of inner keys. - - Check if inner key exist in template group and has valid value. - It is also required to avoid infinite loop with unsolvable recursion - when first inner key's value refers to second inner key's value where - first is used. - """ - keys_to_solve = set(key_values.keys()) - while True: - found = False - for key in tuple(keys_to_solve): - value = key_values[key] - - if isinstance(value, six.string_types): - matches = cls.inner_key_pattern.findall(value) - if not matches: - keys_to_solve.remove(key) - continue - - found = True - key_values[key] = cls.replace_inner_keys( - matches, value, key_values, key - ) - continue - - elif not isinstance(value, dict): - keys_to_solve.remove(key) - continue - - subdict_found = False - for _key, _value in tuple(value.items()): - matches = cls.inner_key_pattern.findall(_value) - if not matches: - continue - - subdict_found = True - found = True - key_values[key][_key] = cls.replace_inner_keys( - matches, _value, key_values, - "{}.{}".format(key, _key) - ) - - if not subdict_found: - keys_to_solve.remove(key) - - if not found: - break - - return key_values - - @classmethod - def solve_template_inner_links(cls, templates): - """Solve templates inner keys identified by "{@*}". - - Process is split into 2 parts. - First is collecting all global keys (keys in top hierarchy where value - is not dictionary). All global keys are set for all group keys (keys - in top hierarchy where value is dictionary). Value of a key is not - overridden in group if already contain value for the key. - - In second part all keys with "at" symbol in value are replaced with - value of the key afterward "at" symbol from the group. - - Args: - templates (dict): Raw templates data. - - Example: - templates:: - key_1: "value_1", - key_2: "{@key_1}/{filling_key}" - - group_1: - key_3: "value_3/{@key_2}" - - group_2: - key_2": "value_2" - key_4": "value_4/{@key_2}" - - output:: - key_1: "value_1" - key_2: "value_1/{filling_key}" - - group_1: { - key_1: "value_1" - key_2: "value_1/{filling_key}" - key_3: "value_3/value_1/{filling_key}" - - group_2: { - key_1: "value_1" - key_2: "value_2" - key_4: "value_3/value_2" - """ - default_key_values = templates.pop("common", {}) - for key, value in tuple(templates.items()): - if isinstance(value, dict): - continue - default_key_values[key] = templates.pop(key) - - # Pop "others" key before before expected keys are processed - other_templates = templates.pop("others") or {} - - keys_by_subkey = {} - for sub_key, sub_value in templates.items(): - key_values = {} - key_values.update(default_key_values) - key_values.update(sub_value) - keys_by_subkey[sub_key] = cls.prepare_inner_keys(key_values) - - for sub_key, sub_value in other_templates.items(): - if sub_key in keys_by_subkey: - log.warning(( - "Key \"{}\" is duplicated in others. Skipping." - ).format(sub_key)) - continue - - key_values = {} - key_values.update(default_key_values) - key_values.update(sub_value) - keys_by_subkey[sub_key] = cls.prepare_inner_keys(key_values) - - default_keys_by_subkeys = cls.prepare_inner_keys(default_key_values) - - for key, value in default_keys_by_subkeys.items(): - keys_by_subkey[key] = value - - return keys_by_subkey - - @classmethod - def _dict_to_subkeys_list(cls, subdict, pre_keys=None): - if pre_keys is None: - pre_keys = [] - output = [] - for key in subdict: - value = subdict[key] - result = list(pre_keys) - result.append(key) - if isinstance(value, dict): - for item in cls._dict_to_subkeys_list(value, result): - output.append(item) - else: - output.append(result) - return output - - def _keys_to_dicts(self, key_list, value): - if not key_list: - return None - if len(key_list) == 1: - return {key_list[0]: value} - return {key_list[0]: self._keys_to_dicts(key_list[1:], value)} - - @classmethod - def rootless_path_from_result(cls, result): - """Calculate rootless path from formatting result. - - Args: - result (TemplateResult): Result of StringTemplate formatting. - - Returns: - str: Rootless path if result contains one of anatomy roots. - """ - - used_values = result.used_values - missing_keys = result.missing_keys - template = result.template - invalid_types = result.invalid_types - if ( - "root" not in used_values - or "root" in missing_keys - or "{root" not in template - ): - return - - for invalid_type in invalid_types: - if "root" in invalid_type: - return - - root_keys = cls._dict_to_subkeys_list({"root": used_values["root"]}) - if not root_keys: - return - - output = str(result) - for used_root_keys in root_keys: - if not used_root_keys: - continue - - used_value = used_values - root_key = None - for key in used_root_keys: - used_value = used_value[key] - if root_key is None: - root_key = key - else: - root_key += "[{}]".format(key) - - root_key = "{" + root_key + "}" - output = output.replace(str(used_value), root_key) - - return output - - def format(self, data, strict=True): - copy_data = copy.deepcopy(data) - roots = self.roots - if roots: - copy_data["root"] = roots - result = super(AnatomyTemplates, self).format(copy_data) - result.strict = strict - return result - - def format_all(self, in_data, only_keys=True): - """ Solves templates based on entered data. - - Args: - data (dict): Containing keys to be filled into template. - - Returns: - TemplatesResultDict: Output `TemplateResult` have `strict` - attribute set to False so accessing unfilled keys in templates - won't raise any exceptions. - """ - return self.format(in_data, strict=False) - - -class RootItem(FormatObject): - """Represents one item or roots. - - Holds raw data of root item specification. Raw data contain value - for each platform, but current platform value is used when object - is used for formatting of template. - - Args: - root_raw_data (dict): Dictionary containing root values by platform - names. ["windows", "linux" and "darwin"] - name (str, optional): Root name which is representing. Used with - multi root setup otherwise None value is expected. - parent_keys (list, optional): All dictionary parent keys. Values of - `parent_keys` are used for get full key which RootItem is - representing. Used for replacing root value in path with - formattable key. e.g. parent_keys == ["work"] -> {root[work]} - parent (object, optional): It is expected to be `Roots` object. - Value of `parent` won't affect code logic much. - """ - - def __init__( - self, root_raw_data, name=None, parent_keys=None, parent=None - ): - lowered_platform_keys = {} - for key, value in root_raw_data.items(): - lowered_platform_keys[key.lower()] = value - self.raw_data = lowered_platform_keys - self.cleaned_data = self._clean_roots(lowered_platform_keys) - self.name = name - self.parent_keys = parent_keys or [] - self.parent = parent - - self.available_platforms = list(lowered_platform_keys.keys()) - self.value = lowered_platform_keys.get(platform.system().lower()) - self.clean_value = self.clean_root(self.value) - - def __format__(self, *args, **kwargs): - return self.value.__format__(*args, **kwargs) - - def __str__(self): - return str(self.value) - - def __repr__(self): - return self.__str__() - - def __getitem__(self, key): - if isinstance(key, numbers.Number): - return self.value[key] - - additional_info = "" - if self.parent and self.parent.project_name: - additional_info += " for project \"{}\"".format( - self.parent.project_name - ) - - raise AssertionError( - "Root key \"{}\" is missing{}.".format( - key, additional_info - ) - ) - - def full_key(self): - """Full key value for dictionary formatting in template. - - Returns: - str: Return full replacement key for formatting. This helps when - multiple roots are set. In that case e.g. `"root[work]"` is - returned. - """ - if not self.name: - return "root" - - joined_parent_keys = "".join( - ["[{}]".format(key) for key in self.parent_keys] - ) - return "root{}".format(joined_parent_keys) - - def clean_path(self, path): - """Just replace backslashes with forward slashes.""" - return str(path).replace("\\", "/") - - def clean_root(self, root): - """Makes sure root value does not end with slash.""" - if root: - root = self.clean_path(root) - while root.endswith("/"): - root = root[:-1] - return root - - def _clean_roots(self, raw_data): - """Clean all values of raw root item values.""" - cleaned = {} - for key, value in raw_data.items(): - cleaned[key] = self.clean_root(value) - return cleaned - - def path_remapper(self, path, dst_platform=None, src_platform=None): - """Remap path for specific platform. - - Args: - path (str): Source path which need to be remapped. - dst_platform (str, optional): Specify destination platform - for which remapping should happen. - src_platform (str, optional): Specify source platform. This is - recommended to not use and keep unset until you really want - to use specific platform. - roots (dict/RootItem/None, optional): It is possible to remap - path with different roots then instance where method was - called has. - - Returns: - str/None: When path does not contain known root then - None is returned else returns remapped path with "{root}" - or "{root[]}". - """ - cleaned_path = self.clean_path(path) - if dst_platform: - dst_root_clean = self.cleaned_data.get(dst_platform) - if not dst_root_clean: - key_part = "" - full_key = self.full_key() - if full_key != "root": - key_part += "\"{}\" ".format(full_key) - - log.warning( - "Root {}miss platform \"{}\" definition.".format( - key_part, dst_platform - ) - ) - return None - - if cleaned_path.startswith(dst_root_clean): - return cleaned_path - - if src_platform: - src_root_clean = self.cleaned_data.get(src_platform) - if src_root_clean is None: - log.warning( - "Root \"{}\" miss platform \"{}\" definition.".format( - self.full_key(), src_platform - ) - ) - return None - - if not cleaned_path.startswith(src_root_clean): - return None - - subpath = cleaned_path[len(src_root_clean):] - if dst_platform: - # `dst_root_clean` is used from upper condition - return dst_root_clean + subpath - return self.clean_value + subpath - - result, template = self.find_root_template_from_path(path) - if not result: - return None - - def parent_dict(keys, value): - if not keys: - return value - - key = keys.pop(0) - return {key: parent_dict(keys, value)} - - if dst_platform: - format_value = parent_dict(list(self.parent_keys), dst_root_clean) - else: - format_value = parent_dict(list(self.parent_keys), self.value) - - return template.format(**{"root": format_value}) - - def find_root_template_from_path(self, path): - """Replaces known root value with formattable key in path. - - All platform values are checked for this replacement. - - Args: - path (str): Path where root value should be found. - - Returns: - tuple: Tuple contain 2 values: `success` (bool) and `path` (str). - When success it True then path should contain replaced root - value with formattable key. - - Example: - When input path is:: - "C:/windows/path/root/projects/my_project/file.ext" - - And raw data of item looks like:: - { - "windows": "C:/windows/path/root", - "linux": "/mount/root" - } - - Output will be:: - (True, "{root}/projects/my_project/file.ext") - - If any of raw data value wouldn't match path's root output is:: - (False, "C:/windows/path/root/projects/my_project/file.ext") - """ - result = False - output = str(path) - - mod_path = self.clean_path(path) - for root_os, root_path in self.cleaned_data.items(): - # Skip empty paths - if not root_path: - continue - - _mod_path = mod_path # reset to original cleaned value - if root_os == "windows": - root_path = root_path.lower() - _mod_path = _mod_path.lower() - - if _mod_path.startswith(root_path): - result = True - replacement = "{" + self.full_key() + "}" - output = replacement + mod_path[len(root_path):] - break - - return (result, output) - - -class Roots: - """Object which should be used for formatting "root" key in templates. - - Args: - anatomy Anatomy: Anatomy object created for a specific project. - """ - - env_prefix = "AYON_PROJECT_ROOT" - roots_filename = "roots.json" - - def __init__(self, anatomy): - self.anatomy = anatomy - self.loaded_project = None - self._roots = None - - def __format__(self, *args, **kwargs): - return self.roots.__format__(*args, **kwargs) - - def __getitem__(self, key): - return self.roots[key] - - def reset(self): - """Reset current roots value.""" - self._roots = None - - def path_remapper( - self, path, dst_platform=None, src_platform=None, roots=None - ): - """Remap path for specific platform. - - Args: - path (str): Source path which need to be remapped. - dst_platform (str, optional): Specify destination platform - for which remapping should happen. - src_platform (str, optional): Specify source platform. This is - recommended to not use and keep unset until you really want - to use specific platform. - roots (dict/RootItem/None, optional): It is possible to remap - path with different roots then instance where method was - called has. - - Returns: - str/None: When path does not contain known root then - None is returned else returns remapped path with "{root}" - or "{root[]}". - """ - if roots is None: - roots = self.roots - - if roots is None: - raise ValueError("Roots are not set. Can't find path.") - - if "{root" in path: - path = path.format(**{"root": roots}) - # If `dst_platform` is not specified then return else continue. - if not dst_platform: - return path - - if isinstance(roots, RootItem): - return roots.path_remapper(path, dst_platform, src_platform) - - for _root in roots.values(): - result = self.path_remapper( - path, dst_platform, src_platform, _root - ) - if result is not None: - return result - - def find_root_template_from_path(self, path, roots=None): - """Find root value in entered path and replace it with formatting key. - - Args: - path (str): Source path where root will be searched. - roots (Roots/dict, optional): It is possible to use different - roots than instance where method was triggered has. - - Returns: - tuple: Output contains tuple with bool representing success as - first value and path with or without replaced root with - formatting key as second value. - - Raises: - ValueError: When roots are not entered and can't be loaded. - """ - if roots is None: - log.debug( - "Looking for matching root in path \"{}\".".format(path) - ) - roots = self.roots - - if roots is None: - raise ValueError("Roots are not set. Can't find path.") - - if isinstance(roots, RootItem): - return roots.find_root_template_from_path(path) - - for root_name, _root in roots.items(): - success, result = self.find_root_template_from_path(path, _root) - if success: - log.info("Found match in root \"{}\".".format(root_name)) - return success, result - - log.warning("No matching root was found in current setting.") - return (False, path) - - def set_root_environments(self): - """Set root environments for current project.""" - for key, value in self.root_environments().items(): - os.environ[key] = value - - def root_environments(self): - """Use root keys to create unique keys for environment variables. - - Concatenates prefix "AYON_PROJECT_ROOT_" with root keys to create - unique keys. - - Returns: - dict: Result is `{(str): (str)}` dicitonary where key represents - unique key concatenated by keys and value is root value of - current platform root. - - Example: - With raw root values:: - "work": { - "windows": "P:/projects/work", - "linux": "/mnt/share/projects/work", - "darwin": "/darwin/path/work" - }, - "publish": { - "windows": "P:/projects/publish", - "linux": "/mnt/share/projects/publish", - "darwin": "/darwin/path/publish" - } - - Result on windows platform:: - { - "AYON_PROJECT_ROOT_WORK": "P:/projects/work", - "AYON_PROJECT_ROOT_PUBLISH": "P:/projects/publish" - } - - """ - return self._root_environments() - - def all_root_paths(self, roots=None): - """Return all paths for all roots of all platforms.""" - if roots is None: - roots = self.roots - - output = [] - if isinstance(roots, RootItem): - for value in roots.raw_data.values(): - output.append(value) - return output - - for _roots in roots.values(): - output.extend(self.all_root_paths(_roots)) - return output - - def _root_environments(self, keys=None, roots=None): - if not keys: - keys = [] - if roots is None: - roots = self.roots - - if isinstance(roots, RootItem): - key_items = [self.env_prefix] - for _key in keys: - key_items.append(_key.upper()) - - key = "_".join(key_items) - # Make sure key and value does not contain unicode - # - can happen in Python 2 hosts - return {str(key): str(roots.value)} - - output = {} - for _key, _value in roots.items(): - _keys = list(keys) - _keys.append(_key) - output.update(self._root_environments(_keys, _value)) - return output - - def root_environmets_fill_data(self, template=None): - """Environment variable values in dictionary for rootless path. - - Args: - template (str): Template for environment variable key fill. - By default is set to `"${}"`. - """ - if template is None: - template = "${}" - return self._root_environmets_fill_data(template) - - def _root_environmets_fill_data(self, template, keys=None, roots=None): - if keys is None and roots is None: - return { - "root": self._root_environmets_fill_data( - template, [], self.roots - ) - } - - if isinstance(roots, RootItem): - key_items = [Roots.env_prefix] - for _key in keys: - key_items.append(_key.upper()) - key = "_".join(key_items) - return template.format(key) - - output = {} - for key, value in roots.items(): - _keys = list(keys) - _keys.append(key) - output[key] = self._root_environmets_fill_data( - template, _keys, value - ) - return output - - @property - def project_name(self): - """Return project name which will be used for loading root values.""" - return self.anatomy.project_name - - @property - def roots(self): - """Property for filling "root" key in templates. - - This property returns roots for current project or default root values. - Warning: - Default roots value may cause issues when project use different - roots settings. That may happen when project use multiroot - templates but default roots miss their keys. - """ - if self.project_name != self.loaded_project: - self._roots = None - - if self._roots is None: - self._roots = self._discover() - self.loaded_project = self.project_name - return self._roots - - def _discover(self): - """ Loads current project's roots or default. - - Default roots are loaded if project override's does not contain roots. - - Returns: - `RootItem` or `dict` with multiple `RootItem`s when multiroot - setting is used. - """ - - return self._parse_dict(self.anatomy["roots"], parent=self) - - @staticmethod - def _parse_dict(data, key=None, parent_keys=None, parent=None): - """Parse roots raw data into RootItem or dictionary with RootItems. - - Converting raw roots data to `RootItem` helps to handle platform keys. - This method is recursive to be able handle multiroot setup and - is static to be able to load default roots without creating new object. - - Args: - data (dict): Should contain raw roots data to be parsed. - key (str, optional): Current root key. Set by recursion. - parent_keys (list): Parent dictionary keys. Set by recursion. - parent (Roots, optional): Parent object set in `RootItem` - helps to keep RootItem instance updated with `Roots` object. - - Returns: - `RootItem` or `dict` with multiple `RootItem`s when multiroot - setting is used. - """ - if not parent_keys: - parent_keys = [] - is_last = False - for value in data.values(): - if isinstance(value, six.string_types): - is_last = True - break - - if is_last: - return RootItem(data, key, parent_keys, parent=parent) - - output = {} - for _key, value in data.items(): - _parent_keys = list(parent_keys) - _parent_keys.append(_key) - output[_key] = Roots._parse_dict(value, _key, _parent_keys, parent) - return output diff --git a/client/ayon_core/pipeline/anatomy/__init__.py b/client/ayon_core/pipeline/anatomy/__init__.py new file mode 100644 index 0000000000..336d09ccaa --- /dev/null +++ b/client/ayon_core/pipeline/anatomy/__init__.py @@ -0,0 +1,17 @@ +from .exceptions import ( + ProjectNotSet, + RootCombinationError, + TemplateMissingKey, + AnatomyTemplateUnsolved, +) +from .anatomy import Anatomy + + +__all__ = ( + "ProjectNotSet", + "RootCombinationError", + "TemplateMissingKey", + "AnatomyTemplateUnsolved", + + "Anatomy", +) diff --git a/client/ayon_core/pipeline/anatomy/anatomy.py b/client/ayon_core/pipeline/anatomy/anatomy.py new file mode 100644 index 0000000000..5eaf918663 --- /dev/null +++ b/client/ayon_core/pipeline/anatomy/anatomy.py @@ -0,0 +1,502 @@ +import os +import re +import copy +import platform +import collections +import time + +import ayon_api + +from ayon_core.lib import Logger, get_local_site_id +from ayon_core.addon import AddonsManager + +from .exceptions import RootCombinationError, ProjectNotSet +from .roots import Roots +from .templates import AnatomyTemplates + +log = Logger.get_logger(__name__) + + +class BaseAnatomy(object): + """Anatomy module helps to keep project settings. + + Wraps key project specifications, AnatomyTemplates and Roots. + """ + root_key_regex = re.compile(r"{(root?[^}]+)}") + root_name_regex = re.compile(r"root\[([^]]+)\]") + + def __init__(self, project_entity, root_overrides=None): + project_name = project_entity["name"] + self.project_name = project_name + self.project_code = project_entity["code"] + + self._data = self._prepare_anatomy_data( + project_entity, root_overrides + ) + self._templates_obj = AnatomyTemplates(self) + self._roots_obj = Roots(self) + + # Anatomy used as dictionary + # - implemented only getters returning copy + def __getitem__(self, key): + return copy.deepcopy(self._data[key]) + + def get(self, key, default=None): + return copy.deepcopy(self._data).get(key, default) + + def keys(self): + return copy.deepcopy(self._data).keys() + + def values(self): + return copy.deepcopy(self._data).values() + + def items(self): + return copy.deepcopy(self._data).items() + + def _prepare_anatomy_data(self, project_entity, root_overrides): + """Prepare anatomy data for further processing. + + Method added to replace `{task}` with `{task[name]}` in templates. + """ + + anatomy_data = self._project_entity_to_anatomy_data(project_entity) + + self._apply_local_settings_on_anatomy_data( + anatomy_data, + root_overrides + ) + + return anatomy_data + + @property + def templates(self): + """Wrap property `templates` of Anatomy's AnatomyTemplates instance.""" + return self._templates_obj.templates + + @property + def templates_obj(self): + """Return `AnatomyTemplates` object of current Anatomy instance.""" + return self._templates_obj + + def format(self, *args, **kwargs): + """Wrap `format` method of Anatomy's `templates_obj`.""" + return self._templates_obj.format(*args, **kwargs) + + def format_all(self, *args, **kwargs): + """Wrap `format_all` method of Anatomy's `templates_obj`.""" + return self._templates_obj.format_all(*args, **kwargs) + + @property + def roots(self): + """Wrap `roots` property of Anatomy's `roots_obj`.""" + return self._roots_obj.roots + + @property + def roots_obj(self): + """Return `Roots` object of current Anatomy instance.""" + return self._roots_obj + + def root_environments(self): + """Return AYON_PROJECT_ROOT_* environments for current project.""" + return self._roots_obj.root_environments() + + def root_environmets_fill_data(self, template=None): + """Environment variable values in dictionary for rootless path. + + Args: + template (str): Template for environment variable key fill. + By default is set to `"${}"`. + """ + return self.roots_obj.root_environmets_fill_data(template) + + def find_root_template_from_path(self, *args, **kwargs): + """Wrapper for Roots `find_root_template_from_path`.""" + return self.roots_obj.find_root_template_from_path(*args, **kwargs) + + def path_remapper(self, *args, **kwargs): + """Wrapper for Roots `path_remapper`.""" + return self.roots_obj.path_remapper(*args, **kwargs) + + def all_root_paths(self): + """Wrapper for Roots `all_root_paths`.""" + return self.roots_obj.all_root_paths() + + def set_root_environments(self): + """Set AYON_PROJECT_ROOT_* environments for current project.""" + self._roots_obj.set_root_environments() + + def root_names(self): + """Return root names for current project.""" + return self.root_names_from_templates(self.templates) + + def _root_keys_from_templates(self, data): + """Extract root key from templates in data. + + Args: + data (dict): Data that may contain templates as string. + + Return: + set: Set of all root names from templates as strings. + + Output example: `{"root[work]", "root[publish]"}` + """ + + output = set() + if isinstance(data, dict): + for value in data.values(): + for root in self._root_keys_from_templates(value): + output.add(root) + + elif isinstance(data, str): + for group in re.findall(self.root_key_regex, data): + output.add(group) + + return output + + def root_value_for_template(self, template): + """Returns value of root key from template.""" + root_templates = [] + for group in re.findall(self.root_key_regex, template): + root_templates.append("{" + group + "}") + + if not root_templates: + return None + + return root_templates[0].format(**{"root": self.roots}) + + def root_names_from_templates(self, templates): + """Extract root names form anatomy templates. + + Returns None if values in templates contain only "{root}". + Empty list is returned if there is no "root" in templates. + Else returns all root names from templates in list. + + RootCombinationError is raised when templates contain both root types, + basic "{root}" and with root name specification "{root[work]}". + + Args: + templates (dict): Anatomy templates where roots are not filled. + + Return: + list/None: List of all root names from templates as strings when + multiroot setup is used, otherwise None is returned. + """ + roots = list(self._root_keys_from_templates(templates)) + # Return empty list if no roots found in templates + if not roots: + return roots + + # Raise exception when root keys have roots with and without root name. + # Invalid output example: ["root", "root[project]", "root[render]"] + if len(roots) > 1 and "root" in roots: + raise RootCombinationError(roots) + + # Return None if "root" without root name in templates + if len(roots) == 1 and roots[0] == "root": + return None + + names = set() + for root in roots: + for group in re.findall(self.root_name_regex, root): + names.add(group) + return list(names) + + def fill_root(self, template_path): + """Fill template path where is only "root" key unfilled. + + Args: + template_path (str): Path with "root" key in. + Example path: "{root}/projects/MyProject/Shot01/Lighting/..." + + Return: + str: formatted path + """ + # NOTE does not care if there are different keys than "root" + return template_path.format(**{"root": self.roots}) + + @classmethod + def fill_root_with_path(cls, rootless_path, root_path): + """Fill path without filled "root" key with passed path. + + This is helper to fill root with different directory path than anatomy + has defined no matter if is single or multiroot. + + Output path is same as input path if `rootless_path` does not contain + unfilled root key. + + Args: + rootless_path (str): Path without filled "root" key. Example: + "{root[work]}/MyProject/..." + root_path (str): What should replace root key in `rootless_path`. + + Returns: + str: Path with filled root. + """ + output = str(rootless_path) + for group in re.findall(cls.root_key_regex, rootless_path): + replacement = "{" + group + "}" + output = output.replace(replacement, root_path) + + return output + + def replace_root_with_env_key(self, filepath, template=None): + """Replace root of path with environment key. + + # Example: + ## Project with roots: + ``` + { + "nas": { + "windows": P:/projects", + ... + } + ... + } + ``` + + ## Entered filepath + "P:/projects/project/folder/task/animation_v001.ma" + + ## Entered template + "<{}>" + + ## Output + "/project/folder/task/animation_v001.ma" + + Args: + filepath (str): Full file path where root should be replaced. + template (str): Optional template for environment key. Must + have one index format key. + Default value if not entered: "${}" + + Returns: + str: Path where root is replaced with environment root key. + + Raise: + ValueError: When project's roots were not found in entered path. + """ + success, rootless_path = self.find_root_template_from_path(filepath) + if not success: + raise ValueError( + "{}: Project's roots were not found in path: {}".format( + self.project_name, filepath + ) + ) + + data = self.root_environmets_fill_data(template) + return rootless_path.format(**data) + + def _project_entity_to_anatomy_data(self, project_entity): + """Convert project document to anatomy data. + + Probably should fill missing keys and values. + """ + + output = copy.deepcopy(project_entity["config"]) + # TODO remove AYON convertion + task_types = copy.deepcopy(project_entity["taskTypes"]) + new_task_types = {} + for task_type in task_types: + name = task_type["name"] + new_task_types[name] = task_type + output["tasks"] = new_task_types + output["attributes"] = copy.deepcopy(project_entity["attrib"]) + + return output + + def _apply_local_settings_on_anatomy_data( + self, anatomy_data, root_overrides + ): + """Apply local settings on anatomy data. + + ATM local settings can modify project roots. Project name is required + as local settings have data stored data by project's name. + + Local settings override root values in this order: + 1.) Check if local settings contain overrides for default project and + apply it's values on roots if there are any. + 2.) If passed `project_name` is not None then check project specific + overrides in local settings for the project and apply it's value on + roots if there are any. + + NOTE: Root values of default project from local settings are always + applied if are set. + + Args: + anatomy_data (dict): Data for anatomy. + root_overrides (dict): Data of local settings. + """ + + # Skip processing if roots for current active site are not available in + # local settings + if not root_overrides: + return + + current_platform = platform.system().lower() + + root_data = anatomy_data["roots"] + for root_name, path in root_overrides.items(): + if root_name not in root_data: + continue + anatomy_data["roots"][root_name][current_platform] = ( + path + ) + + +class CacheItem: + """Helper to cache data. + + Helper does not handle refresh of data and does not mark data as outdated. + Who uses the object should check of outdated state on his own will. + """ + + default_lifetime = 10 + + def __init__(self, lifetime=None): + self._data = None + self._cached = None + self._lifetime = lifetime or self.default_lifetime + + @property + def data(self): + """Cached data/object. + + Returns: + Any: Whatever was cached. + """ + + return self._data + + @property + def is_outdated(self): + """Item has outdated cache. + + Lifetime of cache item expired or was not yet set. + + Returns: + bool: Item is outdated. + """ + + if self._cached is None: + return True + return (time.time() - self._cached) > self._lifetime + + def update_data(self, data): + """Update cache of data. + + Args: + data (Any): Data to cache. + """ + + self._data = data + self._cached = time.time() + + +class Anatomy(BaseAnatomy): + _sync_server_addon_cache = CacheItem() + _project_cache = collections.defaultdict(CacheItem) + _default_site_id_cache = collections.defaultdict(CacheItem) + _root_overrides_cache = collections.defaultdict( + lambda: collections.defaultdict(CacheItem) + ) + + def __init__( + self, project_name=None, site_name=None, project_entity=None + ): + if not project_name: + project_name = os.environ.get("AYON_PROJECT_NAME") + + if not project_name: + raise ProjectNotSet(( + "Implementation bug: Project name is not set. Anatomy requires" + " to load data for specific project." + )) + + if not project_entity: + project_entity = self.get_project_entity_from_cache(project_name) + root_overrides = self._get_site_root_overrides( + project_name, site_name + ) + + super(Anatomy, self).__init__(project_entity, root_overrides) + + @classmethod + def get_project_entity_from_cache(cls, project_name): + project_cache = cls._project_cache[project_name] + if project_cache.is_outdated: + project_cache.update_data(ayon_api.get_project(project_name)) + return copy.deepcopy(project_cache.data) + + @classmethod + def get_sync_server_addon(cls): + if cls._sync_server_addon_cache.is_outdated: + manager = AddonsManager() + cls._sync_server_addon_cache.update_data( + manager.get_enabled_addon("sync_server") + ) + return cls._sync_server_addon_cache.data + + @classmethod + def _get_studio_roots_overrides(cls, project_name): + """This would return 'studio' site override by local settings. + + Notes: + This logic handles local overrides of studio site which may be + available even when sync server is not enabled. + Handling of 'studio' and 'local' site was separated as preparation + for AYON development where that will be received from + separated sources. + + Args: + project_name (str): Name of project. + + Returns: + Union[Dict[str, str], None]): Local root overrides. + """ + if not project_name: + return + return ayon_api.get_project_roots_for_site( + project_name, get_local_site_id() + ) + + @classmethod + def _get_site_root_overrides(cls, project_name, site_name): + """Get root overrides for site. + + Args: + project_name (str): Project name for which root overrides should be + received. + site_name (Union[str, None]): Name of site for which root overrides + should be returned. + """ + + # First check if sync server is available and enabled + sync_server = cls.get_sync_server_addon() + if sync_server is None or not sync_server.enabled: + # QUESTION is ok to force 'studio' when site sync is not enabled? + site_name = "studio" + + elif not site_name: + # Use sync server to receive active site name + project_cache = cls._default_site_id_cache[project_name] + if project_cache.is_outdated: + project_cache.update_data( + sync_server.get_active_site_type(project_name) + ) + site_name = project_cache.data + + site_cache = cls._root_overrides_cache[project_name][site_name] + if site_cache.is_outdated: + if site_name == "studio": + # Handle studio root overrides without sync server + # - studio root overrides can be done even without sync server + roots_overrides = cls._get_studio_roots_overrides( + project_name + ) + else: + # Ask sync server to get roots overrides + roots_overrides = sync_server.get_site_root_overrides( + project_name, site_name + ) + site_cache.update_data(roots_overrides) + return site_cache.data diff --git a/client/ayon_core/pipeline/anatomy/exceptions.py b/client/ayon_core/pipeline/anatomy/exceptions.py new file mode 100644 index 0000000000..39f116baf0 --- /dev/null +++ b/client/ayon_core/pipeline/anatomy/exceptions.py @@ -0,0 +1,39 @@ +from ayon_core.lib.path_templates import TemplateUnsolved + + +class ProjectNotSet(Exception): + """Exception raised when is created Anatomy without project name.""" + + +class RootCombinationError(Exception): + """This exception is raised when templates has combined root types.""" + + def __init__(self, roots): + joined_roots = ", ".join( + ["\"{}\"".format(_root) for _root in roots] + ) + # TODO better error message + msg = ( + "Combination of root with and" + " without root name in AnatomyTemplates. {}" + ).format(joined_roots) + + super(RootCombinationError, self).__init__(msg) + + +class TemplateMissingKey(Exception): + """Exception for cases when key does not exist in template.""" + + msg = "Template key '{}' was not found." + + def __init__(self, parents): + parent_join = "".join(["[\"{0}\"]".format(key) for key in parents]) + super(TemplateMissingKey, self).__init__( + self.msg.format(parent_join) + ) + + +class AnatomyTemplateUnsolved(TemplateUnsolved): + """Exception for unsolved template when strict is set to True.""" + + msg = "Anatomy template \"{0}\" is unsolved.{1}{2}" diff --git a/client/ayon_core/pipeline/anatomy/roots.py b/client/ayon_core/pipeline/anatomy/roots.py new file mode 100644 index 0000000000..9290a2ca38 --- /dev/null +++ b/client/ayon_core/pipeline/anatomy/roots.py @@ -0,0 +1,534 @@ +import os +import numbers +import platform + +import six + +from ayon_core.lib import Logger +from ayon_core.lib.path_templates import FormatObject + +class RootItem(FormatObject): + """Represents one item or roots. + + Holds raw data of root item specification. Raw data contain value + for each platform, but current platform value is used when object + is used for formatting of template. + + Args: + root_raw_data (dict): Dictionary containing root values by platform + names. ["windows", "linux" and "darwin"] + name (str, optional): Root name which is representing. Used with + multi root setup otherwise None value is expected. + parent_keys (list, optional): All dictionary parent keys. Values of + `parent_keys` are used for get full key which RootItem is + representing. Used for replacing root value in path with + formattable key. e.g. parent_keys == ["work"] -> {root[work]} + parent (object, optional): It is expected to be `Roots` object. + Value of `parent` won't affect code logic much. + """ + + def __init__( + self, root_raw_data, name=None, parent_keys=None, parent=None + ): + super(RootItem, self).__init__() + self._log = None + lowered_platform_keys = {} + for key, value in root_raw_data.items(): + lowered_platform_keys[key.lower()] = value + self.raw_data = lowered_platform_keys + self.cleaned_data = self._clean_roots(lowered_platform_keys) + self.name = name + self.parent_keys = parent_keys or [] + self.parent = parent + + self.available_platforms = list(lowered_platform_keys.keys()) + self.value = lowered_platform_keys.get(platform.system().lower()) + self.clean_value = self.clean_root(self.value) + + def __format__(self, *args, **kwargs): + return self.value.__format__(*args, **kwargs) + + def __str__(self): + return str(self.value) + + def __repr__(self): + return self.__str__() + + def __getitem__(self, key): + if isinstance(key, numbers.Number): + return self.value[key] + + additional_info = "" + if self.parent and self.parent.project_name: + additional_info += " for project \"{}\"".format( + self.parent.project_name + ) + + raise AssertionError( + "Root key \"{}\" is missing{}.".format( + key, additional_info + ) + ) + + @property + def log(self): + if self._log is None: + self._log = Logger.get_logger(self.__class__.__name__) + return self._log + + def full_key(self): + """Full key value for dictionary formatting in template. + + Returns: + str: Return full replacement key for formatting. This helps when + multiple roots are set. In that case e.g. `"root[work]"` is + returned. + """ + if not self.name: + return "root" + + joined_parent_keys = "".join( + ["[{}]".format(key) for key in self.parent_keys] + ) + return "root{}".format(joined_parent_keys) + + def clean_path(self, path): + """Just replace backslashes with forward slashes.""" + return str(path).replace("\\", "/") + + def clean_root(self, root): + """Makes sure root value does not end with slash.""" + if root: + root = self.clean_path(root) + while root.endswith("/"): + root = root[:-1] + return root + + def _clean_roots(self, raw_data): + """Clean all values of raw root item values.""" + cleaned = {} + for key, value in raw_data.items(): + cleaned[key] = self.clean_root(value) + return cleaned + + def path_remapper(self, path, dst_platform=None, src_platform=None): + """Remap path for specific platform. + + Args: + path (str): Source path which need to be remapped. + dst_platform (str, optional): Specify destination platform + for which remapping should happen. + src_platform (str, optional): Specify source platform. This is + recommended to not use and keep unset until you really want + to use specific platform. + roots (dict/RootItem/None, optional): It is possible to remap + path with different roots then instance where method was + called has. + + Returns: + str/None: When path does not contain known root then + None is returned else returns remapped path with "{root}" + or "{root[]}". + """ + cleaned_path = self.clean_path(path) + if dst_platform: + dst_root_clean = self.cleaned_data.get(dst_platform) + if not dst_root_clean: + key_part = "" + full_key = self.full_key() + if full_key != "root": + key_part += "\"{}\" ".format(full_key) + + self.log.warning( + "Root {}miss platform \"{}\" definition.".format( + key_part, dst_platform + ) + ) + return None + + if cleaned_path.startswith(dst_root_clean): + return cleaned_path + + if src_platform: + src_root_clean = self.cleaned_data.get(src_platform) + if src_root_clean is None: + self.log.warning( + "Root \"{}\" miss platform \"{}\" definition.".format( + self.full_key(), src_platform + ) + ) + return None + + if not cleaned_path.startswith(src_root_clean): + return None + + subpath = cleaned_path[len(src_root_clean):] + if dst_platform: + # `dst_root_clean` is used from upper condition + return dst_root_clean + subpath + return self.clean_value + subpath + + result, template = self.find_root_template_from_path(path) + if not result: + return None + + def parent_dict(keys, value): + if not keys: + return value + + key = keys.pop(0) + return {key: parent_dict(keys, value)} + + if dst_platform: + format_value = parent_dict(list(self.parent_keys), dst_root_clean) + else: + format_value = parent_dict(list(self.parent_keys), self.value) + + return template.format(**{"root": format_value}) + + def find_root_template_from_path(self, path): + """Replaces known root value with formattable key in path. + + All platform values are checked for this replacement. + + Args: + path (str): Path where root value should be found. + + Returns: + tuple: Tuple contain 2 values: `success` (bool) and `path` (str). + When success it True then path should contain replaced root + value with formattable key. + + Example: + When input path is:: + "C:/windows/path/root/projects/my_project/file.ext" + + And raw data of item looks like:: + { + "windows": "C:/windows/path/root", + "linux": "/mount/root" + } + + Output will be:: + (True, "{root}/projects/my_project/file.ext") + + If any of raw data value wouldn't match path's root output is:: + (False, "C:/windows/path/root/projects/my_project/file.ext") + """ + result = False + output = str(path) + + mod_path = self.clean_path(path) + for root_os, root_path in self.cleaned_data.items(): + # Skip empty paths + if not root_path: + continue + + _mod_path = mod_path # reset to original cleaned value + if root_os == "windows": + root_path = root_path.lower() + _mod_path = _mod_path.lower() + + if _mod_path.startswith(root_path): + result = True + replacement = "{" + self.full_key() + "}" + output = replacement + mod_path[len(root_path):] + break + + return (result, output) + + +class Roots: + """Object which should be used for formatting "root" key in templates. + + Args: + anatomy Anatomy: Anatomy object created for a specific project. + """ + + env_prefix = "AYON_PROJECT_ROOT" + roots_filename = "roots.json" + + def __init__(self, anatomy): + self._log = None + self.anatomy = anatomy + self.loaded_project = None + self._roots = None + + def __format__(self, *args, **kwargs): + return self.roots.__format__(*args, **kwargs) + + def __getitem__(self, key): + return self.roots[key] + + @property + def log(self): + if self._log is None: + self._log = Logger.get_logger(self.__class__.__name__) + return self._log + + def reset(self): + """Reset current roots value.""" + self._roots = None + + def path_remapper( + self, path, dst_platform=None, src_platform=None, roots=None + ): + """Remap path for specific platform. + + Args: + path (str): Source path which need to be remapped. + dst_platform (str, optional): Specify destination platform + for which remapping should happen. + src_platform (str, optional): Specify source platform. This is + recommended to not use and keep unset until you really want + to use specific platform. + roots (dict/RootItem/None, optional): It is possible to remap + path with different roots then instance where method was + called has. + + Returns: + str/None: When path does not contain known root then + None is returned else returns remapped path with "{root}" + or "{root[]}". + """ + if roots is None: + roots = self.roots + + if roots is None: + raise ValueError("Roots are not set. Can't find path.") + + if "{root" in path: + path = path.format(**{"root": roots}) + # If `dst_platform` is not specified then return else continue. + if not dst_platform: + return path + + if isinstance(roots, RootItem): + return roots.path_remapper(path, dst_platform, src_platform) + + for _root in roots.values(): + result = self.path_remapper( + path, dst_platform, src_platform, _root + ) + if result is not None: + return result + + def find_root_template_from_path(self, path, roots=None): + """Find root value in entered path and replace it with formatting key. + + Args: + path (str): Source path where root will be searched. + roots (Roots/dict, optional): It is possible to use different + roots than instance where method was triggered has. + + Returns: + tuple: Output contains tuple with bool representing success as + first value and path with or without replaced root with + formatting key as second value. + + Raises: + ValueError: When roots are not entered and can't be loaded. + """ + if roots is None: + self.log.debug( + "Looking for matching root in path \"{}\".".format(path) + ) + roots = self.roots + + if roots is None: + raise ValueError("Roots are not set. Can't find path.") + + if isinstance(roots, RootItem): + return roots.find_root_template_from_path(path) + + for root_name, _root in roots.items(): + success, result = self.find_root_template_from_path(path, _root) + if success: + self.log.info("Found match in root \"{}\".".format(root_name)) + return success, result + + self.log.warning("No matching root was found in current setting.") + return (False, path) + + def set_root_environments(self): + """Set root environments for current project.""" + for key, value in self.root_environments().items(): + os.environ[key] = value + + def root_environments(self): + """Use root keys to create unique keys for environment variables. + + Concatenates prefix "AYON_PROJECT_ROOT_" with root keys to create + unique keys. + + Returns: + dict: Result is `{(str): (str)}` dicitonary where key represents + unique key concatenated by keys and value is root value of + current platform root. + + Example: + With raw root values:: + "work": { + "windows": "P:/projects/work", + "linux": "/mnt/share/projects/work", + "darwin": "/darwin/path/work" + }, + "publish": { + "windows": "P:/projects/publish", + "linux": "/mnt/share/projects/publish", + "darwin": "/darwin/path/publish" + } + + Result on windows platform:: + { + "AYON_PROJECT_ROOT_WORK": "P:/projects/work", + "AYON_PROJECT_ROOT_PUBLISH": "P:/projects/publish" + } + + """ + return self._root_environments() + + def all_root_paths(self, roots=None): + """Return all paths for all roots of all platforms.""" + if roots is None: + roots = self.roots + + output = [] + if isinstance(roots, RootItem): + for value in roots.raw_data.values(): + output.append(value) + return output + + for _roots in roots.values(): + output.extend(self.all_root_paths(_roots)) + return output + + def _root_environments(self, keys=None, roots=None): + if not keys: + keys = [] + if roots is None: + roots = self.roots + + if isinstance(roots, RootItem): + key_items = [self.env_prefix] + for _key in keys: + key_items.append(_key.upper()) + + key = "_".join(key_items) + # Make sure key and value does not contain unicode + # - can happen in Python 2 hosts + return {str(key): str(roots.value)} + + output = {} + for _key, _value in roots.items(): + _keys = list(keys) + _keys.append(_key) + output.update(self._root_environments(_keys, _value)) + return output + + def root_environmets_fill_data(self, template=None): + """Environment variable values in dictionary for rootless path. + + Args: + template (str): Template for environment variable key fill. + By default is set to `"${}"`. + """ + if template is None: + template = "${}" + return self._root_environmets_fill_data(template) + + def _root_environmets_fill_data(self, template, keys=None, roots=None): + if keys is None and roots is None: + return { + "root": self._root_environmets_fill_data( + template, [], self.roots + ) + } + + if isinstance(roots, RootItem): + key_items = [Roots.env_prefix] + for _key in keys: + key_items.append(_key.upper()) + key = "_".join(key_items) + return template.format(key) + + output = {} + for key, value in roots.items(): + _keys = list(keys) + _keys.append(key) + output[key] = self._root_environmets_fill_data( + template, _keys, value + ) + return output + + @property + def project_name(self): + """Return project name which will be used for loading root values.""" + return self.anatomy.project_name + + @property + def roots(self): + """Property for filling "root" key in templates. + + This property returns roots for current project or default root values. + Warning: + Default roots value may cause issues when project use different + roots settings. That may happen when project use multiroot + templates but default roots miss their keys. + """ + if self.project_name != self.loaded_project: + self._roots = None + + if self._roots is None: + self._roots = self._discover() + self.loaded_project = self.project_name + return self._roots + + def _discover(self): + """ Loads current project's roots or default. + + Default roots are loaded if project override's does not contain roots. + + Returns: + `RootItem` or `dict` with multiple `RootItem`s when multiroot + setting is used. + """ + + return self._parse_dict(self.anatomy["roots"], parent=self) + + @staticmethod + def _parse_dict(data, key=None, parent_keys=None, parent=None): + """Parse roots raw data into RootItem or dictionary with RootItems. + + Converting raw roots data to `RootItem` helps to handle platform keys. + This method is recursive to be able handle multiroot setup and + is static to be able to load default roots without creating new object. + + Args: + data (dict): Should contain raw roots data to be parsed. + key (str, optional): Current root key. Set by recursion. + parent_keys (list): Parent dictionary keys. Set by recursion. + parent (Roots, optional): Parent object set in `RootItem` + helps to keep RootItem instance updated with `Roots` object. + + Returns: + `RootItem` or `dict` with multiple `RootItem`s when multiroot + setting is used. + """ + if not parent_keys: + parent_keys = [] + is_last = False + for value in data.values(): + if isinstance(value, six.string_types): + is_last = True + break + + if is_last: + return RootItem(data, key, parent_keys, parent=parent) + + output = {} + for _key, value in data.items(): + _parent_keys = list(parent_keys) + _parent_keys.append(_key) + output[_key] = Roots._parse_dict(value, _key, _parent_keys, parent) + return output diff --git a/client/ayon_core/pipeline/anatomy/templates.py b/client/ayon_core/pipeline/anatomy/templates.py new file mode 100644 index 0000000000..1eee5b0945 --- /dev/null +++ b/client/ayon_core/pipeline/anatomy/templates.py @@ -0,0 +1,523 @@ +import os +import copy +import re +import collections +import numbers + +import six + +from ayon_core.lib import Logger +from ayon_core.lib.path_templates import ( + TemplateResult, + StringTemplate, + TemplatesDict, +) + +from .exceptions import AnatomyTemplateUnsolved +from .roots import RootItem + + +class AnatomyTemplateResult(TemplateResult): + rootless = None + + def __new__(cls, result, rootless_path): + new_obj = super(AnatomyTemplateResult, cls).__new__( + cls, + str(result), + result.template, + result.solved, + result.used_values, + result.missing_keys, + result.invalid_types + ) + new_obj.rootless = rootless_path + return new_obj + + def validate(self): + if not self.solved: + raise AnatomyTemplateUnsolved( + self.template, + self.missing_keys, + self.invalid_types + ) + + def copy(self): + tmp = TemplateResult( + str(self), + self.template, + self.solved, + self.used_values, + self.missing_keys, + self.invalid_types + ) + return self.__class__(tmp, self.rootless) + + def normalized(self): + """Convert to normalized path.""" + + tmp = TemplateResult( + os.path.normpath(self), + self.template, + self.solved, + self.used_values, + self.missing_keys, + self.invalid_types + ) + return self.__class__(tmp, self.rootless) + + +class AnatomyStringTemplate(StringTemplate): + """String template which has access to anatomy.""" + + def __init__(self, anatomy_templates, template): + self.anatomy_templates = anatomy_templates + super(AnatomyStringTemplate, self).__init__(template) + + def format(self, data): + """Format template and add 'root' key to data if not available. + + Args: + data (dict[str, Any]): Formatting data for template. + + Returns: + AnatomyTemplateResult: Formatting result. + """ + + anatomy_templates = self.anatomy_templates + if not data.get("root"): + data = copy.deepcopy(data) + data["root"] = anatomy_templates.anatomy.roots + result = StringTemplate.format(self, data) + rootless_path = anatomy_templates.rootless_path_from_result(result) + return AnatomyTemplateResult(result, rootless_path) + + +class AnatomyTemplates(TemplatesDict): + inner_key_pattern = re.compile(r"(\{@.*?[^{}0]*\})") + inner_key_name_pattern = re.compile(r"\{@(.*?[^{}0]*)\}") + + def __init__(self, anatomy): + self._log = Logger.get_logger(self.__class__.__name__) + super(AnatomyTemplates, self).__init__() + self.anatomy = anatomy + self.loaded_project = None + + def reset(self): + self._raw_templates = None + self._templates = None + self._objected_templates = None + + @property + def project_name(self): + return self.anatomy.project_name + + @property + def roots(self): + return self.anatomy.roots + + @property + def templates(self): + self._validate_discovery() + return self._templates + + @property + def objected_templates(self): + self._validate_discovery() + return self._objected_templates + + def _validate_discovery(self): + if self.project_name != self.loaded_project: + self.reset() + + if self._templates is None: + self._discover() + self.loaded_project = self.project_name + + def _format_value(self, value, data): + if isinstance(value, RootItem): + return self._solve_dict(value, data) + return super(AnatomyTemplates, self)._format_value(value, data) + + @staticmethod + def _ayon_template_conversion(templates): + def _convert_template_item(template_item): + # Change 'directory' to 'folder' + if "directory" in template_item: + template_item["folder"] = template_item["directory"] + + if ( + "path" not in template_item + and "file" in template_item + and "folder" in template_item + ): + template_item["path"] = "/".join( + (template_item["folder"], template_item["file"]) + ) + + def _get_default_template_name(templates): + default_template = None + for name, template in templates.items(): + if name == "default": + return "default" + + if default_template is None: + default_template = name + + return default_template + + def _fill_template_category(templates, cat_templates, cat_key): + default_template_name = _get_default_template_name(cat_templates) + for template_name, cat_template in cat_templates.items(): + _convert_template_item(cat_template) + if template_name == default_template_name: + templates[cat_key] = cat_template + else: + new_name = "{}_{}".format(cat_key, template_name) + templates["others"][new_name] = cat_template + + others_templates = templates.pop("others", None) or {} + new_others_templates = {} + templates["others"] = new_others_templates + for name, template in others_templates.items(): + _convert_template_item(template) + new_others_templates[name] = template + + for key in ( + "work", + "publish", + "hero", + ): + cat_templates = templates.pop(key) + _fill_template_category(templates, cat_templates, key) + + delivery_templates = templates.pop("delivery", None) or {} + new_delivery_templates = {} + for name, delivery_template in delivery_templates.items(): + new_delivery_templates[name] = "/".join( + (delivery_template["directory"], delivery_template["file"]) + ) + templates["delivery"] = new_delivery_templates + + def set_templates(self, templates): + if not templates: + self.reset() + return + + templates = copy.deepcopy(templates) + # TODO remove AYON convertion + self._ayon_template_conversion(templates) + + self._raw_templates = copy.deepcopy(templates) + v_queue = collections.deque() + v_queue.append(templates) + while v_queue: + item = v_queue.popleft() + if not isinstance(item, dict): + continue + + for key in tuple(item.keys()): + value = item[key] + if isinstance(value, dict): + v_queue.append(value) + + elif ( + isinstance(value, six.string_types) + and "{task}" in value + ): + item[key] = value.replace("{task}", "{task[name]}") + + solved_templates = self.solve_template_inner_links(templates) + self._templates = solved_templates + self._objected_templates = self.create_objected_templates( + solved_templates + ) + + def _create_template_object(self, template): + return AnatomyStringTemplate(self, template) + + def default_templates(self): + """Return default templates data with solved inner keys.""" + return self.solve_template_inner_links( + self.anatomy["templates"] + ) + + def _discover(self): + """ Loads anatomy templates from yaml. + Default templates are loaded if project is not set or project does + not have set it's own. + TODO: create templates if not exist. + + Returns: + TemplatesResultDict: Contain templates data for current project of + default templates. + """ + + if self.project_name is None: + # QUESTION create project specific if not found? + raise AssertionError(( + "Project \"{0}\" does not have his own templates." + " Trying to use default." + ).format(self.project_name)) + + self.set_templates(self.anatomy["templates"]) + + @classmethod + def replace_inner_keys(cls, matches, value, key_values, key): + """Replacement of inner keys in template values.""" + for match in matches: + anatomy_sub_keys = ( + cls.inner_key_name_pattern.findall(match) + ) + if key in anatomy_sub_keys: + raise ValueError(( + "Unsolvable recursion in inner keys, " + "key: \"{}\" is in his own value." + " Can't determine source, please check Anatomy templates." + ).format(key)) + + for anatomy_sub_key in anatomy_sub_keys: + replace_value = key_values.get(anatomy_sub_key) + if replace_value is None: + raise KeyError(( + "Anatomy templates can't be filled." + " Anatomy key `{0}` has" + " invalid inner key `{1}`." + ).format(key, anatomy_sub_key)) + + if not ( + isinstance(replace_value, numbers.Number) + or isinstance(replace_value, six.string_types) + ): + raise ValueError(( + "Anatomy templates can't be filled." + " Anatomy key `{0}` has" + " invalid inner key `{1}`" + " with value `{2}`." + ).format(key, anatomy_sub_key, str(replace_value))) + + value = value.replace(match, str(replace_value)) + + return value + + @classmethod + def prepare_inner_keys(cls, key_values): + """Check values of inner keys. + + Check if inner key exist in template group and has valid value. + It is also required to avoid infinite loop with unsolvable recursion + when first inner key's value refers to second inner key's value where + first is used. + """ + keys_to_solve = set(key_values.keys()) + while True: + found = False + for key in tuple(keys_to_solve): + value = key_values[key] + + if isinstance(value, six.string_types): + matches = cls.inner_key_pattern.findall(value) + if not matches: + keys_to_solve.remove(key) + continue + + found = True + key_values[key] = cls.replace_inner_keys( + matches, value, key_values, key + ) + continue + + elif not isinstance(value, dict): + keys_to_solve.remove(key) + continue + + subdict_found = False + for _key, _value in tuple(value.items()): + matches = cls.inner_key_pattern.findall(_value) + if not matches: + continue + + subdict_found = True + found = True + key_values[key][_key] = cls.replace_inner_keys( + matches, _value, key_values, + "{}.{}".format(key, _key) + ) + + if not subdict_found: + keys_to_solve.remove(key) + + if not found: + break + + return key_values + + @classmethod + def solve_template_inner_links(cls, templates): + """Solve templates inner keys identified by "{@*}". + + Process is split into 2 parts. + First is collecting all global keys (keys in top hierarchy where value + is not dictionary). All global keys are set for all group keys (keys + in top hierarchy where value is dictionary). Value of a key is not + overridden in group if already contain value for the key. + + In second part all keys with "at" symbol in value are replaced with + value of the key afterward "at" symbol from the group. + + Args: + templates (dict): Raw templates data. + + Example: + templates:: + key_1: "value_1", + key_2: "{@key_1}/{filling_key}" + + group_1: + key_3: "value_3/{@key_2}" + + group_2: + key_2": "value_2" + key_4": "value_4/{@key_2}" + + output:: + key_1: "value_1" + key_2: "value_1/{filling_key}" + + group_1: { + key_1: "value_1" + key_2: "value_1/{filling_key}" + key_3: "value_3/value_1/{filling_key}" + + group_2: { + key_1: "value_1" + key_2: "value_2" + key_4: "value_3/value_2" + """ + default_key_values = templates.pop("common", {}) + for key, value in tuple(templates.items()): + if isinstance(value, dict): + continue + default_key_values[key] = templates.pop(key) + + # Pop "others" key before before expected keys are processed + other_templates = templates.pop("others") or {} + + keys_by_subkey = {} + for sub_key, sub_value in templates.items(): + key_values = {} + key_values.update(default_key_values) + key_values.update(sub_value) + keys_by_subkey[sub_key] = cls.prepare_inner_keys(key_values) + + for sub_key, sub_value in other_templates.items(): + if sub_key in keys_by_subkey: + self.log.warning(( + "Key \"{}\" is duplicated in others. Skipping." + ).format(sub_key)) + continue + + key_values = {} + key_values.update(default_key_values) + key_values.update(sub_value) + keys_by_subkey[sub_key] = cls.prepare_inner_keys(key_values) + + default_keys_by_subkeys = cls.prepare_inner_keys(default_key_values) + + for key, value in default_keys_by_subkeys.items(): + keys_by_subkey[key] = value + + return keys_by_subkey + + @classmethod + def _dict_to_subkeys_list(cls, subdict, pre_keys=None): + if pre_keys is None: + pre_keys = [] + output = [] + for key in subdict: + value = subdict[key] + result = list(pre_keys) + result.append(key) + if isinstance(value, dict): + for item in cls._dict_to_subkeys_list(value, result): + output.append(item) + else: + output.append(result) + return output + + def _keys_to_dicts(self, key_list, value): + if not key_list: + return None + if len(key_list) == 1: + return {key_list[0]: value} + return {key_list[0]: self._keys_to_dicts(key_list[1:], value)} + + @classmethod + def rootless_path_from_result(cls, result): + """Calculate rootless path from formatting result. + + Args: + result (TemplateResult): Result of StringTemplate formatting. + + Returns: + str: Rootless path if result contains one of anatomy roots. + """ + + used_values = result.used_values + missing_keys = result.missing_keys + template = result.template + invalid_types = result.invalid_types + if ( + "root" not in used_values + or "root" in missing_keys + or "{root" not in template + ): + return + + for invalid_type in invalid_types: + if "root" in invalid_type: + return + + root_keys = cls._dict_to_subkeys_list({"root": used_values["root"]}) + if not root_keys: + return + + output = str(result) + for used_root_keys in root_keys: + if not used_root_keys: + continue + + used_value = used_values + root_key = None + for key in used_root_keys: + used_value = used_value[key] + if root_key is None: + root_key = key + else: + root_key += "[{}]".format(key) + + root_key = "{" + root_key + "}" + output = output.replace(str(used_value), root_key) + + return output + + def format(self, data, strict=True): + copy_data = copy.deepcopy(data) + roots = self.roots + if roots: + copy_data["root"] = roots + result = super(AnatomyTemplates, self).format(copy_data) + result.strict = strict + return result + + def format_all(self, in_data, only_keys=True): + """ Solves templates based on entered data. + + Args: + data (dict): Containing keys to be filled into template. + + Returns: + TemplatesResultDict: Output `TemplateResult` have `strict` + attribute set to False so accessing unfilled keys in templates + won't raise any exceptions. + """ + return self.format(in_data, strict=False) \ No newline at end of file From 2e24e1c4254650d310e462f0a263a3260835bd64 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Mar 2024 18:31:52 +0100 Subject: [PATCH 032/150] added some helper classes --- .../ayon_core/pipeline/anatomy/templates.py | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/client/ayon_core/pipeline/anatomy/templates.py b/client/ayon_core/pipeline/anatomy/templates.py index 1eee5b0945..565106a23e 100644 --- a/client/ayon_core/pipeline/anatomy/templates.py +++ b/client/ayon_core/pipeline/anatomy/templates.py @@ -92,6 +92,142 @@ class AnatomyStringTemplate(StringTemplate): return AnatomyTemplateResult(result, rootless_path) +class TemplateItem: + """Template item under template category. + + This item data usually contains 'file' and 'directory' by anatomy + definition, enhanced by common data ('frame_padding', + 'version_padding'). It adds 'path' key which is combination of + 'file' and 'directory' values. + + Args: + anatomy_templates (AnatomyTemplates): Anatomy templates object. + template_data (dict[str, Any]): Templates data. + + """ + def __init__(self, anatomy_templates, template_data): + template_data = copy.deepcopy(template_data) + + # Backwards compatibility for 'folder' + # TODO remove when deprecation not needed anymore + if ( + "folder" not in template_data + and "directory" in template_data + ): + template_data["folder"] = template_data["directory"] + + # Add 'path' key + if ( + "path" not in template_data + and "file" in template_data + and "directory" in template_data + ): + template_data["path"] = "/".join( + (template_data["directory"], template_data["file"]) + ) + + for key, value in template_data.items(): + if isinstance(value, str): + value = AnatomyStringTemplate(anatomy_templates, value) + template_data[key] = value + + self._template_data = template_data + self._anatomy_templates = anatomy_templates + + def __getitem__(self, key): + return self._template_data[key] + + def get(self, key, default=None): + return self._template_data.get(key, default) + + def format(self, data, strict=True): + output = {} + for key, value in self._template_data.items(): + if isinstance(value, AnatomyStringTemplate): + value = value.format(data) + output[key] = value + return output + + +class TemplateCategory: + """Template category. + + Template category groups template items for specific usage. Categories + available at the moment are 'work', 'publish', 'hero', 'delivery', + 'staging' and 'others'. + + Args: + anatomy_templates (AnatomyTemplates): Anatomy templates object. + category_name (str): Category name. + category_data (dict[str, Any]): Category data. + + """ + def __init__(self, anatomy_templates, category_name, category_data): + for key, value in category_data.items(): + if isinstance(value, dict): + value = TemplateItem(anatomy_templates, value) + elif isinstance(value, str): + value = AnatomyStringTemplate(anatomy_templates, value) + category_data[key] = value + self._name = category_name + self._name_prefix = "{}_".format(category_name) + self._category_data = category_data + + def __getitem__(self, key): + new_key = self._convert_getter_key(key) + return self._category_data[new_key] + + def get(self, key, default=None): + new_key = self._convert_getter_key(key) + return self._category_data.get(new_key, default) + + @property + def name(self): + """Category name. + + Returns: + str: Category name. + + """ + return self._name + + def format(self, data, strict=True): + output = {} + for key, value in self._category_data.items(): + if isinstance(value, TemplateItem): + value = value.format(data, strict) + elif isinstance(value, AnatomyStringTemplate): + value = value.format(data) + + output[key] = value + return output + + def _convert_getter_key(self, key): + """Convert key for backwards compatibility. + + OpenPype compatible settings did contain template keys prefixed by + category name e.g. 'publish_render' which should be just 'render'. + + This method keeps the backwards compatibility but only if the key + starts with the category name prefix and the key is available in + roots. + + Args: + key (str): Key to be converted. + + Returns: + str: Converted string. + + """ + if key in self._category_data: + return key + if key.startswith(self._name_prefix): + new_key = key[len(self._name_prefix):] + if new_key in self._category_data: + return new_key + return key + + class AnatomyTemplates(TemplatesDict): inner_key_pattern = re.compile(r"(\{@.*?[^{}0]*\})") inner_key_name_pattern = re.compile(r"\{@(.*?[^{}0]*)\}") From df23e27f91ce4c79845d6f1c68e05591aca90cf1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Mar 2024 18:40:56 +0100 Subject: [PATCH 033/150] added some helper methods --- .../ayon_core/pipeline/anatomy/templates.py | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/client/ayon_core/pipeline/anatomy/templates.py b/client/ayon_core/pipeline/anatomy/templates.py index 565106a23e..f8dd8179ae 100644 --- a/client/ayon_core/pipeline/anatomy/templates.py +++ b/client/ayon_core/pipeline/anatomy/templates.py @@ -388,6 +388,77 @@ class AnatomyTemplates(TemplatesDict): default templates. """ + @property + def frame_padding(self): + """Default frame padding. + + Returns: + int: Frame padding used by default in templates. + + """ + self._validate_discovery() + return self["frame_padding"] + + @property + def version_padding(self): + """Default version padding. + + Returns: + int: Version padding used by default in templates. + + """ + self._validate_discovery() + return self["version_padding"] + + @classmethod + def get_rootless_path_from_result(cls, result): + """Calculate rootless path from formatting result. + + Args: + result (TemplateResult): Result of StringTemplate formatting. + + Returns: + str: Rootless path if result contains one of anatomy roots. + """ + + used_values = result.used_values + missing_keys = result.missing_keys + template = result.template + invalid_types = result.invalid_types + if ( + "root" not in used_values + or "root" in missing_keys + or "{root" not in template + ): + return + + for invalid_type in invalid_types: + if "root" in invalid_type: + return + + root_keys = cls._dict_to_subkeys_list({"root": used_values["root"]}) + if not root_keys: + return + + output = str(result) + for used_root_keys in root_keys: + if not used_root_keys: + continue + + used_value = used_values + root_key = None + for key in used_root_keys: + used_value = used_value[key] + if root_key is None: + root_key = key + else: + root_key += "[{}]".format(key) + + root_key = "{" + root_key + "}" + output = output.replace(str(used_value), root_key) + + return output + if self.project_name is None: # QUESTION create project specific if not found? raise AssertionError(( From f2f11e444efe2a8c54b85205ee7fa6f2c8298e5c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Mar 2024 18:42:51 +0100 Subject: [PATCH 034/150] modified templates --- .../ayon_core/pipeline/anatomy/templates.py | 687 +++++++++++------- 1 file changed, 408 insertions(+), 279 deletions(-) diff --git a/client/ayon_core/pipeline/anatomy/templates.py b/client/ayon_core/pipeline/anatomy/templates.py index f8dd8179ae..63dfd9b1b0 100644 --- a/client/ayon_core/pipeline/anatomy/templates.py +++ b/client/ayon_core/pipeline/anatomy/templates.py @@ -1,19 +1,19 @@ import os -import copy import re +import copy import collections import numbers -import six - -from ayon_core.lib import Logger from ayon_core.lib.path_templates import ( TemplateResult, StringTemplate, - TemplatesDict, ) -from .exceptions import AnatomyTemplateUnsolved +from .exceptions import ( + ProjectNotSet, + TemplateMissingKey, + AnatomyTemplateUnsolved, +) from .roots import RootItem @@ -67,7 +67,12 @@ class AnatomyTemplateResult(TemplateResult): class AnatomyStringTemplate(StringTemplate): - """String template which has access to anatomy.""" + """String template which has access to anatomy. + + Args: + anatomy_templates (AnatomyTemplates): Anatomy templates object. + template (str): Template string. + """ def __init__(self, anatomy_templates, template): self.anatomy_templates = anatomy_templates @@ -88,10 +93,154 @@ class AnatomyStringTemplate(StringTemplate): data = copy.deepcopy(data) data["root"] = anatomy_templates.anatomy.roots result = StringTemplate.format(self, data) - rootless_path = anatomy_templates.rootless_path_from_result(result) + rootless_path = anatomy_templates.get_rootless_path_from_result( + result + ) return AnatomyTemplateResult(result, rootless_path) +def _merge_dict(main_dict, enhance_dict): + """Merges dictionaries by keys. + + Function call itself if value on key is again dictionary. + + Args: + main_dict (dict): First dict to merge second one into. + enhance_dict (dict): Second dict to be merged. + + Returns: + dict: Merged result. + + .. note:: does not override whole value on first found key + but only values differences from enhance_dict + + """ + + merge_queue = collections.deque() + merge_queue.append((main_dict, enhance_dict)) + while merge_queue: + queue_item = merge_queue.popleft() + l_dict, r_dict = queue_item + + for key, value in r_dict.items(): + if key not in l_dict: + l_dict[key] = value + elif isinstance(value, dict) and isinstance(l_dict[key], dict): + merge_queue.append((l_dict[key], value)) + else: + l_dict[key] = value + return main_dict + + +class TemplatesResultDict(dict): + """Holds and wrap 'AnatomyTemplateResult' for easy bug report. + + Dictionary like object which holds 'AnatomyTemplateResult' in the same + data structure as base dictionary of anatomy templates. It can raise + + """ + + def __init__(self, in_data, key=None, parent=None, strict=None): + super(TemplatesResultDict, self).__init__() + for _key, _value in in_data.items(): + if isinstance(_value, TemplatesResultDict): + _value.parent = self + elif isinstance(_value, dict): + _value = self.__class__(_value, _key, self) + self[_key] = _value + + if strict is None and parent is None: + strict = True + + self.key = key + self.parent = parent + self._is_strict = strict + + def __getitem__(self, key): + if key not in self.keys(): + hier = self.get_hierarchy() + hier.append(key) + raise TemplateMissingKey(hier) + + value = super(TemplatesResultDict, self).__getitem__(key) + if isinstance(value, self.__class__): + return value + + # Raise exception when expected solved templates and it is not. + if self.is_strict and hasattr(value, "validate"): + value.validate() + return value + + def get_is_strict(self): + return self._is_strict + + def set_is_strict(self, is_strict): + if is_strict is None and self.parent is None: + is_strict = True + self._is_strict = is_strict + for child in self.values(): + if isinstance(child, self.__class__): + child.set_is_strict(is_strict) + elif isinstance(child, AnatomyTemplateResult): + child.strict = is_strict + + strict = property(get_is_strict, set_is_strict) + is_strict = property(get_is_strict, set_is_strict) + + def get_hierarchy(self): + """Return dictionary keys one by one to root parent.""" + if self.key is None: + return [] + + if self.parent is None: + return [self.key] + + par_hier = list(self.parent.get_hierarchy()) + par_hier.append(self.key) + return par_hier + + @property + def missing_keys(self): + """Return missing keys of all children templates.""" + missing_keys = set() + for value in self.values(): + missing_keys |= value.missing_keys + return missing_keys + + @property + def invalid_types(self): + """Return invalid types of all children templates.""" + invalid_types = {} + for value in self.values(): + invalid_types = _merge_dict(invalid_types, value.invalid_types) + return invalid_types + + @property + def used_values(self): + """Return used values for all children templates.""" + used_values = {} + for value in self.values(): + used_values = _merge_dict(used_values, value.used_values) + return used_values + + def get_solved(self): + """Get only solved key from templates.""" + result = {} + for key, value in self.items(): + if isinstance(value, self.__class__): + value = value.get_solved() + if not value: + continue + result[key] = value + + elif ( + not hasattr(value, "solved") or + value.solved + ): + result[key] = value + return self.__class__(result, key=self.key, parent=self.parent) + + class TemplateItem: """Template item under template category. @@ -146,7 +295,7 @@ class TemplateItem: if isinstance(value, AnatomyStringTemplate): value = value.format(data) output[key] = value - return output + return TemplatesResultDict(output, strict=strict) class TemplateCategory: @@ -199,8 +348,10 @@ class TemplateCategory: elif isinstance(value, AnatomyStringTemplate): value = value.format(data) + if isinstance(value, TemplatesResultDict): + value.key = key output[key] = value - return output + return TemplatesResultDict(output, key=self.name, strict=strict) def _convert_getter_key(self, key): """Convert key for backwards compatibility. @@ -229,164 +380,76 @@ class TemplateCategory: class AnatomyTemplates(TemplatesDict): +class AnatomyTemplates: inner_key_pattern = re.compile(r"(\{@.*?[^{}0]*\})") inner_key_name_pattern = re.compile(r"\{@(.*?[^{}0]*)\}") def __init__(self, anatomy): - self._log = Logger.get_logger(self.__class__.__name__) - super(AnatomyTemplates, self).__init__() - self.anatomy = anatomy - self.loaded_project = None + self._anatomy = anatomy + + self._loaded_project = None + self._raw_templates = None + self._templates = None + self._objected_templates = None + + def __getitem__(self, key): + self._validate_discovery() + return self._objected_templates[key] + + def get(self, key, default=None): + self._validate_discovery() + return self._objected_templates.get(key, default) + + def keys(self): + return self._objected_templates.keys() def reset(self): self._raw_templates = None self._templates = None self._objected_templates = None + @property + def anatomy(self): + """Anatomy instance. + + Returns: + Anatomy: Anatomy instance. + + """ + return self._anatomy + @property def project_name(self): - return self.anatomy.project_name + """Project name. + + Returns: + Union[str, None]: Project name if set, otherwise None. + + """ + return self._anatomy.project_name @property def roots(self): - return self.anatomy.roots + """Anatomy roots object. + + Returns: + RootItem: Anatomy roots data. + + """ + return self._anatomy.roots @property def templates(self): - self._validate_discovery() - return self._templates + """Templates data. - @property - def objected_templates(self): - self._validate_discovery() - return self._objected_templates - - def _validate_discovery(self): - if self.project_name != self.loaded_project: - self.reset() - - if self._templates is None: - self._discover() - self.loaded_project = self.project_name - - def _format_value(self, value, data): - if isinstance(value, RootItem): - return self._solve_dict(value, data) - return super(AnatomyTemplates, self)._format_value(value, data) - - @staticmethod - def _ayon_template_conversion(templates): - def _convert_template_item(template_item): - # Change 'directory' to 'folder' - if "directory" in template_item: - template_item["folder"] = template_item["directory"] - - if ( - "path" not in template_item - and "file" in template_item - and "folder" in template_item - ): - template_item["path"] = "/".join( - (template_item["folder"], template_item["file"]) - ) - - def _get_default_template_name(templates): - default_template = None - for name, template in templates.items(): - if name == "default": - return "default" - - if default_template is None: - default_template = name - - return default_template - - def _fill_template_category(templates, cat_templates, cat_key): - default_template_name = _get_default_template_name(cat_templates) - for template_name, cat_template in cat_templates.items(): - _convert_template_item(cat_template) - if template_name == default_template_name: - templates[cat_key] = cat_template - else: - new_name = "{}_{}".format(cat_key, template_name) - templates["others"][new_name] = cat_template - - others_templates = templates.pop("others", None) or {} - new_others_templates = {} - templates["others"] = new_others_templates - for name, template in others_templates.items(): - _convert_template_item(template) - new_others_templates[name] = template - - for key in ( - "work", - "publish", - "hero", - ): - cat_templates = templates.pop(key) - _fill_template_category(templates, cat_templates, key) - - delivery_templates = templates.pop("delivery", None) or {} - new_delivery_templates = {} - for name, delivery_template in delivery_templates.items(): - new_delivery_templates[name] = "/".join( - (delivery_template["directory"], delivery_template["file"]) - ) - templates["delivery"] = new_delivery_templates - - def set_templates(self, templates): - if not templates: - self.reset() - return - - templates = copy.deepcopy(templates) - # TODO remove AYON convertion - self._ayon_template_conversion(templates) - - self._raw_templates = copy.deepcopy(templates) - v_queue = collections.deque() - v_queue.append(templates) - while v_queue: - item = v_queue.popleft() - if not isinstance(item, dict): - continue - - for key in tuple(item.keys()): - value = item[key] - if isinstance(value, dict): - v_queue.append(value) - - elif ( - isinstance(value, six.string_types) - and "{task}" in value - ): - item[key] = value.replace("{task}", "{task[name]}") - - solved_templates = self.solve_template_inner_links(templates) - self._templates = solved_templates - self._objected_templates = self.create_objected_templates( - solved_templates - ) - - def _create_template_object(self, template): - return AnatomyStringTemplate(self, template) - - def default_templates(self): - """Return default templates data with solved inner keys.""" - return self.solve_template_inner_links( - self.anatomy["templates"] - ) - - def _discover(self): - """ Loads anatomy templates from yaml. - Default templates are loaded if project is not set or project does - not have set it's own. - TODO: create templates if not exist. + Templates data with replaced common data. Returns: - TemplatesResultDict: Contain templates data for current project of - default templates. + dict[str, Any]: Templates data. + """ + self._validate_discovery() + return self._templates @property def frame_padding(self): @@ -459,17 +522,152 @@ class AnatomyTemplates(TemplatesDict): return output - if self.project_name is None: - # QUESTION create project specific if not found? - raise AssertionError(( - "Project \"{0}\" does not have his own templates." - " Trying to use default." - ).format(self.project_name)) + def format(self, data, strict=True): + """Fill all templates based on entered data. - self.set_templates(self.anatomy["templates"]) + Args: + data (dict[str, Any]): Fill data used for template formatting. + strict (Optional[bool]): Raise exception is accessed value is + not fully filled. + + Returns: + TemplatesResultDict: Output `TemplateResult` have `strict` + attribute set to False so accessing unfilled keys in templates + won't raise any exceptions. + + """ + self._validate_discovery() + copy_data = copy.deepcopy(data) + roots = self._anatomy.roots + if roots: + copy_data["root"] = roots + + return self._solve_dict(copy_data, strict) + + def format_all(self, in_data): + """Fill all templates based on entered data. + + Deprecated: + Use `format` method with `strict=False` instead. + + Args: + in_data (dict): Containing keys to be filled into template. + + Returns: + TemplatesResultDict: Output `TemplateResult` have `strict` + attribute set to False so accessing unfilled keys in templates + won't raise any exceptions. + + """ + return self.format(in_data, strict=False) + + def get_template(self, category_name, template_name, subkey=None): + """Get template item from category. + + Args: + category_name (str): Category name. + template_name (str): Template name. + subkey (Optional[str]): Subkey name. + + Returns: + Any: Template item or subkey value. + + """ + self._validate_discovery() + category = self.get(category_name) + if category is None: + return None + + template_item = category.get(template_name) + if template_item is None: + return template_item + + if subkey is None: + return template_item + + return template_item.get(subkey) + + def _solve_dict(self, data, strict): + """ Solves templates with entered data. + + Args: + data (dict): Containing keys to be filled into template. + + Returns: + dict: With `TemplateResult` in values containing filled or + partially filled templates. + + """ + output = {} + for key, value in self._objected_templates.items(): + if isinstance(value, TemplateCategory): + value = value.format(data, strict) + elif isinstance(value, AnatomyStringTemplate): + value = value.format(data) + output[key] = value + return TemplatesResultDict(output, strict=strict) + + def _validate_discovery(self): + """Validate if templates are discovered and loaded for anatomy project. + + When project changes the cached data are reset and discovered again. + """ + if self.project_name != self._loaded_project: + self.reset() + + if self._templates is None: + self._discover() + self._loaded_project = self.project_name + + def _create_objected_templates(self, templates): + """Create objected templates from templates data. + + Args: + templates (dict[str, Any]): Templates data from project entity. + + Returns: + dict[str, Any]: Values are cnmverted to template objects. + + """ + objected_templates = {} + for category_name, category_value in copy.deepcopy(templates).items(): + if isinstance(category_value, dict): + category_value = TemplateCategory( + self, category_name, category_value + ) + elif isinstance(category_value, str): + category_value = AnatomyStringTemplate(self, category_value) + objected_templates[category_name] = category_value + return objected_templates + + def _discover(self): + """Load and cache templates from project entity.""" + if self.project_name is None: + raise ProjectNotSet("Anatomy project is not set.") + + templates = self.anatomy["templates"] + self._raw_templates = copy.deepcopy(templates) + + templates = copy.deepcopy(templates) + # Make sure all the keys are available + for key in ( + "publish", + "hero", + "work", + "delivery", + "staging", + "others", + ): + templates.setdefault(key, {}) + + solved_templates = self._solve_template_inner_links(templates) + self._templates = solved_templates + self._objected_templates = self._create_objected_templates( + solved_templates + ) @classmethod - def replace_inner_keys(cls, matches, value, key_values, key): + def _replace_inner_keys(cls, matches, value, key_values, key): """Replacement of inner keys in template values.""" for match in matches: anatomy_sub_keys = ( @@ -493,7 +691,7 @@ class AnatomyTemplates(TemplatesDict): if not ( isinstance(replace_value, numbers.Number) - or isinstance(replace_value, six.string_types) + or isinstance(replace_value, str) ): raise ValueError(( "Anatomy templates can't be filled." @@ -507,7 +705,7 @@ class AnatomyTemplates(TemplatesDict): return value @classmethod - def prepare_inner_keys(cls, key_values): + def _prepare_inner_keys(cls, key_values): """Check values of inner keys. Check if inner key exist in template group and has valid value. @@ -521,14 +719,14 @@ class AnatomyTemplates(TemplatesDict): for key in tuple(keys_to_solve): value = key_values[key] - if isinstance(value, six.string_types): + if isinstance(value, str): matches = cls.inner_key_pattern.findall(value) if not matches: keys_to_solve.remove(key) continue found = True - key_values[key] = cls.replace_inner_keys( + key_values[key] = cls._replace_inner_keys( matches, value, key_values, key ) continue @@ -545,7 +743,7 @@ class AnatomyTemplates(TemplatesDict): subdict_found = True found = True - key_values[key][_key] = cls.replace_inner_keys( + key_values[key][_key] = cls._replace_inner_keys( matches, _value, key_values, "{}.{}".format(key, _key) ) @@ -559,7 +757,7 @@ class AnatomyTemplates(TemplatesDict): return key_values @classmethod - def solve_template_inner_links(cls, templates): + def _solve_template_inner_links(cls, templates): """Solve templates inner keys identified by "{@*}". Process is split into 2 parts. @@ -599,132 +797,63 @@ class AnatomyTemplates(TemplatesDict): key_1: "value_1" key_2: "value_2" key_4: "value_3/value_2" + + Returns: + dict[str, Any]: Solved templates data. + """ default_key_values = templates.pop("common", {}) - for key, value in tuple(templates.items()): - if isinstance(value, dict): - continue - default_key_values[key] = templates.pop(key) - - # Pop "others" key before before expected keys are processed - other_templates = templates.pop("others") or {} - - keys_by_subkey = {} - for sub_key, sub_value in templates.items(): - key_values = {} - key_values.update(default_key_values) - key_values.update(sub_value) - keys_by_subkey[sub_key] = cls.prepare_inner_keys(key_values) - - for sub_key, sub_value in other_templates.items(): - if sub_key in keys_by_subkey: - self.log.warning(( - "Key \"{}\" is duplicated in others. Skipping." - ).format(sub_key)) - continue - - key_values = {} - key_values.update(default_key_values) - key_values.update(sub_value) - keys_by_subkey[sub_key] = cls.prepare_inner_keys(key_values) - - default_keys_by_subkeys = cls.prepare_inner_keys(default_key_values) + output = {} + for category_name, category_value in templates.items(): + new_category_value = {} + for key, value in category_value.items(): + key_values = copy.deepcopy(default_key_values) + key_values.update(value) + new_category_value[key] = cls._prepare_inner_keys(key_values) + output[category_name] = new_category_value + default_keys_by_subkeys = cls._prepare_inner_keys(default_key_values) for key, value in default_keys_by_subkeys.items(): - keys_by_subkey[key] = value + output[key] = value - return keys_by_subkey + return output @classmethod - def _dict_to_subkeys_list(cls, subdict, pre_keys=None): - if pre_keys is None: - pre_keys = [] + def _dict_to_subkeys_list(cls, subdict): + """Convert dictionary to list of subkeys. + + Example:: + + _dict_to_subkeys_list({ + "root": { + "work": "path/to/work", + "publish": "path/to/publish" + } + }) + [ + ["root", "work"], + ["root", "publish"] + ] + + + Args: + dict[str, Any]: Dictionary to be converted. + + Returns: + list[list[str]]: List of subkeys. + + """ output = [] - for key in subdict: - value = subdict[key] - result = list(pre_keys) - result.append(key) - if isinstance(value, dict): - for item in cls._dict_to_subkeys_list(value, result): - output.append(item) - else: - output.append(result) - return output - - def _keys_to_dicts(self, key_list, value): - if not key_list: - return None - if len(key_list) == 1: - return {key_list[0]: value} - return {key_list[0]: self._keys_to_dicts(key_list[1:], value)} - - @classmethod - def rootless_path_from_result(cls, result): - """Calculate rootless path from formatting result. - - Args: - result (TemplateResult): Result of StringTemplate formatting. - - Returns: - str: Rootless path if result contains one of anatomy roots. - """ - - used_values = result.used_values - missing_keys = result.missing_keys - template = result.template - invalid_types = result.invalid_types - if ( - "root" not in used_values - or "root" in missing_keys - or "{root" not in template - ): - return - - for invalid_type in invalid_types: - if "root" in invalid_type: - return - - root_keys = cls._dict_to_subkeys_list({"root": used_values["root"]}) - if not root_keys: - return - - output = str(result) - for used_root_keys in root_keys: - if not used_root_keys: - continue - - used_value = used_values - root_key = None - for key in used_root_keys: - used_value = used_value[key] - if root_key is None: - root_key = key + subkey_queue = collections.deque() + subkey_queue.append((subdict, [])) + while subkey_queue: + queue_item = subkey_queue.popleft() + data, pre_keys = queue_item + for key, value in data.items(): + result = list(pre_keys) + result.append(key) + if isinstance(value, dict): + subkey_queue.append((value, result)) else: - root_key += "[{}]".format(key) - - root_key = "{" + root_key + "}" - output = output.replace(str(used_value), root_key) - + output.append(result) return output - - def format(self, data, strict=True): - copy_data = copy.deepcopy(data) - roots = self.roots - if roots: - copy_data["root"] = roots - result = super(AnatomyTemplates, self).format(copy_data) - result.strict = strict - return result - - def format_all(self, in_data, only_keys=True): - """ Solves templates based on entered data. - - Args: - data (dict): Containing keys to be filled into template. - - Returns: - TemplatesResultDict: Output `TemplateResult` have `strict` - attribute set to False so accessing unfilled keys in templates - won't raise any exceptions. - """ - return self.format(in_data, strict=False) \ No newline at end of file From 1182c40140fcf71149ca62ea4f9be315aee73ec4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Mar 2024 18:44:01 +0100 Subject: [PATCH 035/150] modified roots --- client/ayon_core/pipeline/anatomy/roots.py | 192 ++++++++++----------- 1 file changed, 91 insertions(+), 101 deletions(-) diff --git a/client/ayon_core/pipeline/anatomy/roots.py b/client/ayon_core/pipeline/anatomy/roots.py index 9290a2ca38..2773559d49 100644 --- a/client/ayon_core/pipeline/anatomy/roots.py +++ b/client/ayon_core/pipeline/anatomy/roots.py @@ -1,12 +1,11 @@ import os -import numbers import platform - -import six +import numbers from ayon_core.lib import Logger from ayon_core.lib.path_templates import FormatObject + class RootItem(FormatObject): """Represents one item or roots. @@ -15,21 +14,13 @@ class RootItem(FormatObject): is used for formatting of template. Args: + parent (AnatomyRoots): Parent object. root_raw_data (dict): Dictionary containing root values by platform names. ["windows", "linux" and "darwin"] - name (str, optional): Root name which is representing. Used with + name (str): Root name which is representing. Used with multi root setup otherwise None value is expected. - parent_keys (list, optional): All dictionary parent keys. Values of - `parent_keys` are used for get full key which RootItem is - representing. Used for replacing root value in path with - formattable key. e.g. parent_keys == ["work"] -> {root[work]} - parent (object, optional): It is expected to be `Roots` object. - Value of `parent` won't affect code logic much. """ - - def __init__( - self, root_raw_data, name=None, parent_keys=None, parent=None - ): + def __init__(self, parent, root_raw_data, name): super(RootItem, self).__init__() self._log = None lowered_platform_keys = {} @@ -38,12 +29,11 @@ class RootItem(FormatObject): self.raw_data = lowered_platform_keys self.cleaned_data = self._clean_roots(lowered_platform_keys) self.name = name - self.parent_keys = parent_keys or [] self.parent = parent - self.available_platforms = list(lowered_platform_keys.keys()) + self.available_platforms = set(lowered_platform_keys.keys()) self.value = lowered_platform_keys.get(platform.system().lower()) - self.clean_value = self.clean_root(self.value) + self.clean_value = self._clean_root(self.value) def __format__(self, *args, **kwargs): return self.value.__format__(*args, **kwargs) @@ -64,7 +54,7 @@ class RootItem(FormatObject): self.parent.project_name ) - raise AssertionError( + raise KeyError( "Root key \"{}\" is missing{}.".format( key, additional_info ) @@ -76,6 +66,7 @@ class RootItem(FormatObject): self._log = Logger.get_logger(self.__class__.__name__) return self._log + @property def full_key(self): """Full key value for dictionary formatting in template. @@ -83,32 +74,40 @@ class RootItem(FormatObject): str: Return full replacement key for formatting. This helps when multiple roots are set. In that case e.g. `"root[work]"` is returned. + """ - if not self.name: - return "root" + return "root[{}]".format(self.name) - joined_parent_keys = "".join( - ["[{}]".format(key) for key in self.parent_keys] - ) - return "root{}".format(joined_parent_keys) + @staticmethod + def _clean_path(path): + """Just replace backslashes with forward slashes. - def clean_path(self, path): - """Just replace backslashes with forward slashes.""" + Args: + path (str): Path which should be cleaned. + + Returns: + str: Cleaned path with forward slashes. + + """ return str(path).replace("\\", "/") - def clean_root(self, root): - """Makes sure root value does not end with slash.""" - if root: - root = self.clean_path(root) - while root.endswith("/"): - root = root[:-1] - return root + def _clean_root(self, root): + """Clean root value. + + Args: + root (str): Root value which should be cleaned. + + Returns: + str: Cleaned root value. + + """ + return self._clean_path(root).rstrip("/") def _clean_roots(self, raw_data): """Clean all values of raw root item values.""" cleaned = {} for key, value in raw_data.items(): - cleaned[key] = self.clean_root(value) + cleaned[key] = self._clean_root(value) return cleaned def path_remapper(self, path, dst_platform=None, src_platform=None): @@ -121,27 +120,20 @@ class RootItem(FormatObject): src_platform (str, optional): Specify source platform. This is recommended to not use and keep unset until you really want to use specific platform. - roots (dict/RootItem/None, optional): It is possible to remap - path with different roots then instance where method was - called has. Returns: - str/None: When path does not contain known root then - None is returned else returns remapped path with "{root}" - or "{root[]}". + Union[str, None]: When path does not contain known root then + None is returned else returns remapped path with + "{root[]}". + """ - cleaned_path = self.clean_path(path) + cleaned_path = self._clean_path(path) if dst_platform: dst_root_clean = self.cleaned_data.get(dst_platform) if not dst_root_clean: - key_part = "" - full_key = self.full_key() - if full_key != "root": - key_part += "\"{}\" ".format(full_key) - self.log.warning( - "Root {}miss platform \"{}\" definition.".format( - key_part, dst_platform + "Root \"{}\" miss platform \"{}\" definition.".format( + self.full_key, dst_platform ) ) return None @@ -154,7 +146,7 @@ class RootItem(FormatObject): if src_root_clean is None: self.log.warning( "Root \"{}\" miss platform \"{}\" definition.".format( - self.full_key(), src_platform + self.full_key, src_platform ) ) return None @@ -172,19 +164,12 @@ class RootItem(FormatObject): if not result: return None - def parent_dict(keys, value): - if not keys: - return value - - key = keys.pop(0) - return {key: parent_dict(keys, value)} - if dst_platform: - format_value = parent_dict(list(self.parent_keys), dst_root_clean) + fill_data = {self.name: dst_root_clean} else: - format_value = parent_dict(list(self.parent_keys), self.value) + fill_data = {self.name: self.value} - return template.format(**{"root": format_value}) + return template.format(**{"root": fill_data}) def find_root_template_from_path(self, path): """Replaces known root value with formattable key in path. @@ -218,7 +203,7 @@ class RootItem(FormatObject): result = False output = str(path) - mod_path = self.clean_path(path) + mod_path = self._clean_path(path) for root_os, root_path in self.cleaned_data.items(): # Skip empty paths if not root_path: @@ -231,27 +216,26 @@ class RootItem(FormatObject): if _mod_path.startswith(root_path): result = True - replacement = "{" + self.full_key() + "}" + replacement = "{" + self.full_key + "}" output = replacement + mod_path[len(root_path):] break return (result, output) -class Roots: +class AnatomyRoots: """Object which should be used for formatting "root" key in templates. Args: - anatomy Anatomy: Anatomy object created for a specific project. + anatomy (Anatomy): Anatomy object created for a specific project. """ env_prefix = "AYON_PROJECT_ROOT" - roots_filename = "roots.json" def __init__(self, anatomy): self._log = None - self.anatomy = anatomy - self.loaded_project = None + self._anatomy = anatomy + self._loaded_project = None self._roots = None def __format__(self, *args, **kwargs): @@ -266,6 +250,16 @@ class Roots: self._log = Logger.get_logger(self.__class__.__name__) return self._log + @property + def anatomy(self): + """Parent Anatomy object. + + Returns: + Anatomy: Parent anatomy object. + + """ + return self._anatomy + def reset(self): """Reset current roots value.""" self._roots = None @@ -277,19 +271,20 @@ class Roots: Args: path (str): Source path which need to be remapped. - dst_platform (str, optional): Specify destination platform + dst_platform (Optional[str]): Specify destination platform for which remapping should happen. - src_platform (str, optional): Specify source platform. This is + src_platform (Optional[str]): Specify source platform. This is recommended to not use and keep unset until you really want to use specific platform. - roots (dict/RootItem/None, optional): It is possible to remap + roots (Optional[Union[dict, RootItem])): It is possible to remap path with different roots then instance where method was called has. Returns: - str/None: When path does not contain known root then + Union[str, None]: When path does not contain known root then None is returned else returns remapped path with "{root}" or "{root[]}". + """ if roots is None: roots = self.roots @@ -318,8 +313,8 @@ class Roots: Args: path (str): Source path where root will be searched. - roots (Roots/dict, optional): It is possible to use different - roots than instance where method was triggered has. + roots (Optional[Union[AnatomyRoots, dict]): It is possible to use + different roots than instance where method was triggered has. Returns: tuple: Output contains tuple with bool representing success as @@ -344,7 +339,9 @@ class Roots: for root_name, _root in roots.items(): success, result = self.find_root_template_from_path(path, _root) if success: - self.log.info("Found match in root \"{}\".".format(root_name)) + self.log.debug( + "Found match in root \"{}\".".format(root_name) + ) return success, result self.log.warning("No matching root was found in current setting.") @@ -446,7 +443,7 @@ class Roots: } if isinstance(roots, RootItem): - key_items = [Roots.env_prefix] + key_items = [AnatomyRoots.env_prefix] for _key in keys: key_items.append(_key.upper()) key = "_".join(key_items) @@ -463,25 +460,31 @@ class Roots: @property def project_name(self): - """Return project name which will be used for loading root values.""" - return self.anatomy.project_name + """Current project name which will be used for loading root values. + + Returns: + str: Project name. + """ + return self._anatomy.project_name @property def roots(self): """Property for filling "root" key in templates. This property returns roots for current project or default root values. + Warning: Default roots value may cause issues when project use different roots settings. That may happen when project use multiroot templates but default roots miss their keys. + """ - if self.project_name != self.loaded_project: + if self.project_name != self._loaded_project: self._roots = None if self._roots is None: self._roots = self._discover() - self.loaded_project = self.project_name + self._loaded_project = self.project_name return self._roots def _discover(self): @@ -494,10 +497,10 @@ class Roots: setting is used. """ - return self._parse_dict(self.anatomy["roots"], parent=self) + return self._parse_dict(self._anatomy["roots"], self) @staticmethod - def _parse_dict(data, key=None, parent_keys=None, parent=None): + def _parse_dict(data, parent): """Parse roots raw data into RootItem or dictionary with RootItems. Converting raw roots data to `RootItem` helps to handle platform keys. @@ -506,29 +509,16 @@ class Roots: Args: data (dict): Should contain raw roots data to be parsed. - key (str, optional): Current root key. Set by recursion. - parent_keys (list): Parent dictionary keys. Set by recursion. - parent (Roots, optional): Parent object set in `RootItem` - helps to keep RootItem instance updated with `Roots` object. + parent (AnatomyRoots): Parent object set as parent + for ``RootItem``. Returns: - `RootItem` or `dict` with multiple `RootItem`s when multiroot - setting is used. + dict[str, RootItem]: Root items by name. + """ - if not parent_keys: - parent_keys = [] - is_last = False - for value in data.values(): - if isinstance(value, six.string_types): - is_last = True - break - - if is_last: - return RootItem(data, key, parent_keys, parent=parent) - output = {} - for _key, value in data.items(): - _parent_keys = list(parent_keys) - _parent_keys.append(_key) - output[_key] = Roots._parse_dict(value, _key, _parent_keys, parent) + for root_name, root_values in data.items(): + output[root_name] = RootItem( + parent, root_values, root_name + ) return output From b40d734a3b82c77c8d5fd935f7f2850898184661 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Mar 2024 18:44:24 +0100 Subject: [PATCH 036/150] modified anatomy to use new options of templates and roots --- client/ayon_core/pipeline/anatomy/anatomy.py | 88 +++++++++++++++----- 1 file changed, 69 insertions(+), 19 deletions(-) diff --git a/client/ayon_core/pipeline/anatomy/anatomy.py b/client/ayon_core/pipeline/anatomy/anatomy.py index 5eaf918663..77ba83234f 100644 --- a/client/ayon_core/pipeline/anatomy/anatomy.py +++ b/client/ayon_core/pipeline/anatomy/anatomy.py @@ -11,7 +11,7 @@ from ayon_core.lib import Logger, get_local_site_id from ayon_core.addon import AddonsManager from .exceptions import RootCombinationError, ProjectNotSet -from .roots import Roots +from .roots import AnatomyRoots from .templates import AnatomyTemplates log = Logger.get_logger(__name__) @@ -20,21 +20,20 @@ log = Logger.get_logger(__name__) class BaseAnatomy(object): """Anatomy module helps to keep project settings. - Wraps key project specifications, AnatomyTemplates and Roots. + Wraps key project specifications, AnatomyTemplates and AnatomyRoots. """ root_key_regex = re.compile(r"{(root?[^}]+)}") root_name_regex = re.compile(r"root\[([^]]+)\]") def __init__(self, project_entity, root_overrides=None): - project_name = project_entity["name"] - self.project_name = project_name - self.project_code = project_entity["code"] + self._project_name = project_entity["name"] + self._project_code = project_entity["code"] self._data = self._prepare_anatomy_data( project_entity, root_overrides ) self._templates_obj = AnatomyTemplates(self) - self._roots_obj = Roots(self) + self._roots_obj = AnatomyRoots(self) # Anatomy used as dictionary # - implemented only getters returning copy @@ -42,7 +41,9 @@ class BaseAnatomy(object): return copy.deepcopy(self._data[key]) def get(self, key, default=None): - return copy.deepcopy(self._data).get(key, default) + if key not in self._data: + return default + return copy.deepcopy(self._data[key]) def keys(self): return copy.deepcopy(self._data).keys() @@ -53,6 +54,26 @@ class BaseAnatomy(object): def items(self): return copy.deepcopy(self._data).items() + @property + def project_name(self): + """Project name for which is anatomy prepared. + + Returns: + str: Project name. + + """ + return self._project_name + + @property + def project_code(self): + """Project name for which is anatomy prepared. + + Returns: + str: Project code. + + """ + return self._project_code + def _prepare_anatomy_data(self, project_entity, root_overrides): """Prepare anatomy data for further processing. @@ -78,12 +99,33 @@ class BaseAnatomy(object): """Return `AnatomyTemplates` object of current Anatomy instance.""" return self._templates_obj + def get_template(self, category_name, template_name, subkey=None): + """Get template item from category. + + Args: + category_name (str): Category name. + template_name (str): Template name. + subkey (Optional[str]): Subkey name. + + Returns: + Any: Template item, subkey value as AnatomyStringTemplate or None. + + """ + return self._templates_obj.get_template( + category_name, template_name, subkey + ) + def format(self, *args, **kwargs): """Wrap `format` method of Anatomy's `templates_obj`.""" return self._templates_obj.format(*args, **kwargs) def format_all(self, *args, **kwargs): - """Wrap `format_all` method of Anatomy's `templates_obj`.""" + """Wrap `format_all` method of Anatomy's `templates_obj`. + + Deprecated: + Use ``format`` method with ``strict=False`` instead. + + """ return self._templates_obj.format_all(*args, **kwargs) @property @@ -93,7 +135,12 @@ class BaseAnatomy(object): @property def roots_obj(self): - """Return `Roots` object of current Anatomy instance.""" + """Roots wrapper object. + + Returns: + AnatomyRoots: Roots wrapper. + + """ return self._roots_obj def root_environments(self): @@ -110,15 +157,15 @@ class BaseAnatomy(object): return self.roots_obj.root_environmets_fill_data(template) def find_root_template_from_path(self, *args, **kwargs): - """Wrapper for Roots `find_root_template_from_path`.""" + """Wrapper for AnatomyRoots `find_root_template_from_path`.""" return self.roots_obj.find_root_template_from_path(*args, **kwargs) def path_remapper(self, *args, **kwargs): - """Wrapper for Roots `path_remapper`.""" + """Wrapper for AnatomyRoots `path_remapper`.""" return self.roots_obj.path_remapper(*args, **kwargs) def all_root_paths(self): - """Wrapper for Roots `all_root_paths`.""" + """Wrapper for AnatomyRoots `all_root_paths`.""" return self.roots_obj.all_root_paths() def set_root_environments(self): @@ -142,14 +189,17 @@ class BaseAnatomy(object): """ output = set() - if isinstance(data, dict): - for value in data.values(): - for root in self._root_keys_from_templates(value): - output.add(root) + keys_queue = collections.deque() + keys_queue.append(data) + while keys_queue: + queue_data = keys_queue.popleft() + if isinstance(queue_data, dict): + for value in queue_data.values(): + keys_queue.append(value) - elif isinstance(data, str): - for group in re.findall(self.root_key_regex, data): - output.add(group) + elif isinstance(queue_data, str): + for group in re.findall(self.root_key_regex, queue_data): + output.add(group) return output From 9bf5859f67a4b9a1df6273c636fe3a805a7dc720 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Mar 2024 18:46:40 +0100 Subject: [PATCH 037/150] places using anatomy templates are awared about categories --- .../plugins/publish/collect_render_path.py | 22 ++++----- client/ayon_core/hosts/fusion/api/plugin.py | 2 +- client/ayon_core/hosts/hiero/api/lib.py | 4 +- .../plugins/publish/extract_workfile_xgen.py | 6 +-- .../maya/plugins/publish/extract_xgen.py | 5 +- client/ayon_core/hosts/nuke/api/lib.py | 22 ++++----- .../hosts/nuke/startup/custom_write_node.py | 10 ++-- .../hosts/photoshop/api/launch_logic.py | 9 ++-- .../tvpaint/plugins/load/load_workfile.py | 11 ++--- .../unreal/hooks/pre_workfile_preparation.py | 4 +- client/ayon_core/lib/applications.py | 2 +- .../publish/submit_celaction_deadline.py | 8 ++-- .../plugins/publish/submit_fusion_deadline.py | 7 ++- .../plugins/publish/submit_nuke_deadline.py | 9 ++-- .../publish/submit_publish_cache_job.py | 21 ++------ .../plugins/publish/submit_publish_job.py | 21 ++------ client/ayon_core/pipeline/context_tools.py | 2 +- client/ayon_core/pipeline/delivery.py | 13 ++--- client/ayon_core/pipeline/farm/tools.py | 2 +- client/ayon_core/pipeline/publish/lib.py | 35 +++++--------- client/ayon_core/pipeline/usdlib.py | 4 +- .../pipeline/workfile/path_resolving.py | 15 +++--- .../plugins/actions/open_file_explorer.py | 8 +++- client/ayon_core/plugins/load/delivery.py | 6 ++- .../publish/collect_otio_subset_resources.py | 6 ++- .../plugins/publish/collect_resources_path.py | 23 +++------ client/ayon_core/plugins/publish/integrate.py | 12 ++--- .../plugins/publish/integrate_hero_version.py | 48 +++++-------------- .../tools/push_to_project/models/integrate.py | 13 +++-- client/ayon_core/tools/texture_copy/app.py | 4 +- .../tools/workfiles/models/workfiles.py | 11 ++--- 31 files changed, 148 insertions(+), 217 deletions(-) diff --git a/client/ayon_core/hosts/celaction/plugins/publish/collect_render_path.py b/client/ayon_core/hosts/celaction/plugins/publish/collect_render_path.py index abe670b691..a7eef0fce4 100644 --- a/client/ayon_core/hosts/celaction/plugins/publish/collect_render_path.py +++ b/client/ayon_core/hosts/celaction/plugins/publish/collect_render_path.py @@ -18,7 +18,7 @@ class CollectRenderPath(pyblish.api.InstancePlugin): def process(self, instance): anatomy = instance.context.data["anatomy"] anatomy_data = copy.deepcopy(instance.data["anatomyData"]) - padding = anatomy.templates.get("frame_padding", 4) + padding = anatomy.templates_obj.frame_padding product_type = "render" anatomy_data.update({ "frame": f"%0{padding}d", @@ -28,15 +28,14 @@ class CollectRenderPath(pyblish.api.InstancePlugin): }) anatomy_data["product"]["type"] = product_type - anatomy_filled = anatomy.format(anatomy_data) - # get anatomy rendering keys r_anatomy_key = self.anatomy_template_key_render_files m_anatomy_key = self.anatomy_template_key_metadata # get folder and path for rendering images from celaction - render_dir = anatomy_filled[r_anatomy_key]["folder"] - render_path = anatomy_filled[r_anatomy_key]["path"] + r_template_item = anatomy.get_template("publish", r_anatomy_key) + render_dir = r_template_item["directory"].format_strict(anatomy_data) + render_path = r_template_item["path"].format_strict(anatomy_data) self.log.debug("__ render_path: `{}`".format(render_path)) # create dir if it doesnt exists @@ -51,11 +50,12 @@ class CollectRenderPath(pyblish.api.InstancePlugin): instance.data["path"] = render_path # get anatomy for published renders folder path - if anatomy_filled.get(m_anatomy_key): - instance.data["publishRenderMetadataFolder"] = anatomy_filled[ - m_anatomy_key]["folder"] - self.log.info("Metadata render path: `{}`".format( - instance.data["publishRenderMetadataFolder"] - )) + m_template_item = anatomy.get_template("publish", m_anatomy_key) + if m_template_item is not None: + metadata_path = m_template_item["directory"].format_strict( + anatomy_data + ) + instance.data["publishRenderMetadataFolder"] = metadata_path + self.log.info("Metadata render path: `{}`".format(metadata_path)) self.log.info(f"Render output path set to: `{render_path}`") diff --git a/client/ayon_core/hosts/fusion/api/plugin.py b/client/ayon_core/hosts/fusion/api/plugin.py index f63b5eaec3..492841f967 100644 --- a/client/ayon_core/hosts/fusion/api/plugin.py +++ b/client/ayon_core/hosts/fusion/api/plugin.py @@ -133,7 +133,7 @@ class GenericCreateSaver(Creator): formatting_data = deepcopy(data) # get frame padding from anatomy templates - frame_padding = self.project_anatomy.templates["frame_padding"] + frame_padding = self.project_anatomy.templates_obj.frame_padding # get output format ext = data["creator_attributes"]["image_format"] diff --git a/client/ayon_core/hosts/hiero/api/lib.py b/client/ayon_core/hosts/hiero/api/lib.py index c46269b532..666119593a 100644 --- a/client/ayon_core/hosts/hiero/api/lib.py +++ b/client/ayon_core/hosts/hiero/api/lib.py @@ -632,7 +632,7 @@ def sync_avalon_data_to_workfile(): project_name = get_current_project_name() anatomy = Anatomy(project_name) - work_template = anatomy.templates["work"]["path"] + work_template = anatomy.get_template("work", "default", "path") work_root = anatomy.root_value_for_template(work_template) active_project_root = ( os.path.join(work_root, project_name) @@ -825,7 +825,7 @@ class PublishAction(QtWidgets.QAction): # root_node = hiero.core.nuke.RootNode() # # anatomy = Anatomy(get_current_project_name()) -# work_template = anatomy.templates["work"]["path"] +# work_template = anatomy.get_template("work", "default", "path") # root_path = anatomy.root_value_for_template(work_template) # # nuke_script.addNode(root_node) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_workfile_xgen.py b/client/ayon_core/hosts/maya/plugins/publish/extract_workfile_xgen.py index 9aaba532b2..a0328725ca 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_workfile_xgen.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_workfile_xgen.py @@ -128,9 +128,9 @@ class ExtractWorkfileXgen(publish.Extractor): alembic_files.append(alembic_file) template_data = copy.deepcopy(instance.data["anatomyData"]) - published_maya_path = StringTemplate( - instance.context.data["anatomy"].templates["publish"]["file"] - ).format(template_data) + anatomy = instance.context.data["anatomy"] + publish_template = anatomy.get_template("publish", "default", "file") + published_maya_path = publish_template.format(template_data) published_basename, _ = os.path.splitext(published_maya_path) for source in alembic_files: diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_xgen.py b/client/ayon_core/hosts/maya/plugins/publish/extract_xgen.py index ee864bd89b..eff4dcc881 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_xgen.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_xgen.py @@ -39,8 +39,9 @@ class ExtractXgen(publish.Extractor): # Get published xgen file name. template_data = copy.deepcopy(instance.data["anatomyData"]) template_data.update({"ext": "xgen"}) - templates = instance.context.data["anatomy"].templates["publish"] - xgen_filename = StringTemplate(templates["file"]).format(template_data) + anatomy = instance.context.data["anatomy"] + file_template = anatomy.get_template("publish", "default", "file") + xgen_filename = file_template.format(template_data) xgen_path = os.path.join( self.staging_dir(instance), xgen_filename diff --git a/client/ayon_core/hosts/nuke/api/lib.py b/client/ayon_core/hosts/nuke/api/lib.py index 8e39475f10..1bb0ff79e0 100644 --- a/client/ayon_core/hosts/nuke/api/lib.py +++ b/client/ayon_core/hosts/nuke/api/lib.py @@ -982,26 +982,18 @@ def format_anatomy(data): project_name = get_current_project_name() anatomy = Anatomy(project_name) - log.debug("__ anatomy.templates: {}".format(anatomy.templates)) - padding = None - if "frame_padding" in anatomy.templates.keys(): - padding = int(anatomy.templates["frame_padding"]) - elif "render" in anatomy.templates.keys(): - padding = int( - anatomy.templates["render"].get( - "frame_padding" - ) - ) + frame_padding = anatomy.templates_obj.frame_padding - version = data.get("version", None) - if not version: + version = data.get("version") + if version is None: file = script_name() data["version"] = get_version_from_path(file) folder_path = data["folderPath"] task_name = data["task"] host_name = get_current_host_name() + context_data = get_template_data_with_names( project_name, folder_path, task_name, host_name ) @@ -1013,7 +1005,7 @@ def format_anatomy(data): "name": data["productName"], "type": data["productType"], }, - "frame": "#" * padding, + "frame": "#" * frame_padding, }) return anatomy.format(data) @@ -1171,7 +1163,9 @@ def create_write_node( anatomy_filled = format_anatomy(data) # build file path to workfiles - fdir = str(anatomy_filled["work"]["folder"]).replace("\\", "/") + fdir = str( + anatomy_filled["work"]["default"]["directory"] + ).replace("\\", "/") data["work"] = fdir fpath = StringTemplate(data["fpath_template"]).format_strict(data) diff --git a/client/ayon_core/hosts/nuke/startup/custom_write_node.py b/client/ayon_core/hosts/nuke/startup/custom_write_node.py index 075c8e7a17..098c3da3a8 100644 --- a/client/ayon_core/hosts/nuke/startup/custom_write_node.py +++ b/client/ayon_core/hosts/nuke/startup/custom_write_node.py @@ -2,7 +2,7 @@ import os import nuke import nukescripts -from ayon_core.pipeline import Anatomy +from ayon_core.pipeline import Anatomy, get_current_project_name from ayon_core.hosts.nuke.api.lib import ( set_node_knobs_from_settings, get_nuke_imageio_settings @@ -102,13 +102,9 @@ class WriteNodeKnobSettingPanel(nukescripts.PythonPanel): for knob in ext_knob_list: ext = knob["value"] - anatomy = Anatomy() + anatomy = Anatomy(get_current_project_name()) - frame_padding = int( - anatomy.templates["render"].get( - "frame_padding" - ) - ) + frame_padding = anatomy.templates_obj.frame_padding for write_node in write_selected_nodes: # data for mapping the path # TODO add more fill data diff --git a/client/ayon_core/hosts/photoshop/api/launch_logic.py b/client/ayon_core/hosts/photoshop/api/launch_logic.py index 17fe7d5920..ccb7b0048b 100644 --- a/client/ayon_core/hosts/photoshop/api/launch_logic.py +++ b/client/ayon_core/hosts/photoshop/api/launch_logic.py @@ -392,17 +392,14 @@ class PhotoshopRoute(WebSocketRoute): ) data["root"] = anatomy.roots - file_template = anatomy.templates[template_key]["file"] + work_template = anatomy.get_template("work", template_key) # Define saving file extension extensions = host.get_workfile_extensions() - folder_template = anatomy.templates[template_key]["folder"] - work_root = StringTemplate.format_strict_template( - folder_template, data - ) + work_root = work_template["directory"].format_strict(data) last_workfile_path = get_last_workfile( - work_root, file_template, data, extensions, True + work_root, work_template["file"], data, extensions, True ) return last_workfile_path diff --git a/client/ayon_core/hosts/tvpaint/plugins/load/load_workfile.py b/client/ayon_core/hosts/tvpaint/plugins/load/load_workfile.py index 4bb34089bd..16cb54f4a3 100644 --- a/client/ayon_core/hosts/tvpaint/plugins/load/load_workfile.py +++ b/client/ayon_core/hosts/tvpaint/plugins/load/load_workfile.py @@ -80,7 +80,7 @@ class LoadWorkfile(plugin.Loader): ) data["root"] = anatomy.roots - file_template = anatomy.templates[template_key]["file"] + work_template = anatomy.get_template("work", template_key) # Define saving file extension extensions = host.get_workfile_extensions() @@ -91,14 +91,11 @@ class LoadWorkfile(plugin.Loader): # Fall back to the first extension supported for this host. extension = extensions[0] - data["ext"] = extension + data["ext"] = extension.lstrip(".") - folder_template = anatomy.templates[template_key]["folder"] - work_root = StringTemplate.format_strict_template( - folder_template, data - ) + work_root = work_template["directory"].format_strict(data) version = get_last_workfile_with_version( - work_root, file_template, data, extensions + work_root, work_template["file"], data, extensions )[1] if version is None: diff --git a/client/ayon_core/hosts/unreal/hooks/pre_workfile_preparation.py b/client/ayon_core/hosts/unreal/hooks/pre_workfile_preparation.py index 2f78bf026d..040beccb5e 100644 --- a/client/ayon_core/hosts/unreal/hooks/pre_workfile_preparation.py +++ b/client/ayon_core/hosts/unreal/hooks/pre_workfile_preparation.py @@ -66,7 +66,9 @@ class UnrealPrelaunchHook(PreLaunchHook): self.host_name, ) # Fill templates - template_obj = anatomy.templates_obj[workfile_template_key]["file"] + template_obj = anatomy.get_template( + "work", workfile_template_key, "file" + ) # Return filename return template_obj.format_strict(workdir_data) diff --git a/client/ayon_core/lib/applications.py b/client/ayon_core/lib/applications.py index 4bf0c31d93..c4f1d168b5 100644 --- a/client/ayon_core/lib/applications.py +++ b/client/ayon_core/lib/applications.py @@ -1862,7 +1862,7 @@ def _prepare_last_workfile(data, workdir, addons_manager): project_settings=project_settings ) # Find last workfile - file_template = str(anatomy.templates[template_key]["file"]) + file_template = anatomy.get_template("work", template_key, "file") workdir_data.update({ "version": 1, diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py index bc3636da63..e11b74ae81 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py @@ -74,6 +74,8 @@ class CelactionSubmitDeadline(pyblish.api.InstancePlugin): render_path = os.path.normpath(render_path) script_name = os.path.basename(script_path) + anatomy = instance.context.data["anatomy"] + publish_template = anatomy.get_template("publish", "default", "path") for item in instance.context: if "workfile" in item.data["productType"]: msg = "Workfile (scene) must be published along" @@ -84,9 +86,9 @@ class CelactionSubmitDeadline(pyblish.api.InstancePlugin): template_data["representation"] = rep template_data["ext"] = rep template_data["comment"] = None - anatomy_filled = instance.context.data["anatomy"].format( - template_data) - template_filled = anatomy_filled["publish"]["path"] + template_filled = publish_template.format_strict( + template_data + ) script_path = os.path.normpath(template_filled) self.log.info( diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py index 837ed91c60..f99a7c6ba3 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py @@ -123,6 +123,8 @@ class FusionSubmitDeadline( script_path = context.data["currentFile"] + anatomy = instance.context.data["anatomy"] + publish_template = anatomy.get_template("publish", "default", "path") for item in context: if "workfile" in item.data["families"]: msg = "Workfile (scene) must be published along" @@ -133,8 +135,9 @@ class FusionSubmitDeadline( template_data["representation"] = rep template_data["ext"] = rep template_data["comment"] = None - anatomy_filled = context.data["anatomy"].format(template_data) - template_filled = anatomy_filled["publish"]["path"] + template_filled = publish_template.format_strict( + template_data + ) script_path = os.path.normpath(template_filled) self.log.info( diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py index a3111454b3..193cad1d24 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -196,6 +196,9 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, def _get_published_workfile_path(self, context): """This method is temporary while the class is not inherited from AbstractSubmitDeadline""" + anatomy = context.data["anatomy"] + # WARNING Hardcoded template name 'default' > may not be used + publish_template = anatomy.get_template("publish", "default", "path") for instance in context: if ( instance.data["productType"] != "workfile" @@ -216,11 +219,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, template_data["ext"] = ext template_data["comment"] = None - anatomy = context.data["anatomy"] - # WARNING Hardcoded template name 'publish' > may not be used - template_obj = anatomy.templates_obj["publish"]["path"] - - template_filled = template_obj.format(template_data) + template_filled = publish_template.format(template_data) script_path = os.path.normpath(template_filled) self.log.info( "Using published scene for render {}".format( diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py index 0561e0f65c..dbc5a12fa8 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py @@ -450,23 +450,10 @@ class ProcessSubmittedCacheJobOnFarm(pyblish.api.InstancePlugin, "type": product_type, } - render_templates = anatomy.templates_obj[template_name] - if "folder" in render_templates: - publish_folder = render_templates["folder"].format_strict( - template_data - ) - else: - # solve deprecated situation when `folder` key is not underneath - # `publish` anatomy - self.log.warning(( - "Deprecation warning: Anatomy does not have set `folder`" - " key underneath `publish` (in global of for project `{}`)." - ).format(project_name)) - - file_path = render_templates["path"].format_strict(template_data) - publish_folder = os.path.dirname(file_path) - - return publish_folder + render_dir_template = anatomy.get_template( + "publish", template_name, "directory" + ) + return render_dir_template.format_strict(template_data) @classmethod def get_attribute_defs(cls): diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py index 7a6abd5507..ce1c75e2ee 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py @@ -573,23 +573,10 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, "type": product_type, } - render_templates = anatomy.templates_obj[template_name] - if "folder" in render_templates: - publish_folder = render_templates["folder"].format_strict( - template_data - ) - else: - # solve deprecated situation when `folder` key is not underneath - # `publish` anatomy - self.log.warning(( - "Deprecation warning: Anatomy does not have set `folder`" - " key underneath `publish` (in global of for project `{}`)." - ).format(project_name)) - - file_path = render_templates["path"].format_strict(template_data) - publish_folder = os.path.dirname(file_path) - - return publish_folder + render_dir_template = anatomy.get_template( + "publish", template_name, "directory" + ) + return render_dir_template.format_strict(template_data) @classmethod def get_attribute_defs(cls): diff --git a/client/ayon_core/pipeline/context_tools.py b/client/ayon_core/pipeline/context_tools.py index 50c384bf88..bf21b43e0b 100644 --- a/client/ayon_core/pipeline/context_tools.py +++ b/client/ayon_core/pipeline/context_tools.py @@ -545,7 +545,7 @@ def get_workdir_from_session(session=None, template_key=None): ) anatomy = Anatomy(project_name) - template_obj = anatomy.templates_obj[template_key]["folder"] + template_obj = anatomy.get_template("work", template_key, "directory") path = template_obj.format_strict(template_data) if path: path = os.path.normpath(path) diff --git a/client/ayon_core/pipeline/delivery.py b/client/ayon_core/pipeline/delivery.py index d2b78422e3..666909ef8d 100644 --- a/client/ayon_core/pipeline/delivery.py +++ b/client/ayon_core/pipeline/delivery.py @@ -77,8 +77,8 @@ def check_destination_path( """ anatomy_data.update(datetime_data) - anatomy_filled = anatomy.format_all(anatomy_data) - dest_path = anatomy_filled["delivery"][template_name] + path_template = anatomy.get_template("delivery", template_name, "path") + dest_path = path_template.format(anatomy_data) report_items = collections.defaultdict(list) if not dest_path.solved: @@ -150,7 +150,7 @@ def deliver_single_file( if format_dict: anatomy_data = copy.deepcopy(anatomy_data) anatomy_data["root"] = format_dict["root"] - template_obj = anatomy.templates_obj["delivery"][template_name] + template_obj = anatomy.get_template("delivery", template_name, "path") delivery_path = template_obj.format_strict(anatomy_data) # Backwards compatibility when extension contained `.` @@ -220,8 +220,9 @@ def deliver_sequence( report_items["Source file was not found"].append(msg) return report_items, 0 - delivery_templates = anatomy.templates.get("delivery") or {} - delivery_template = delivery_templates.get(template_name) + delivery_template = anatomy.get_template( + "delivery", template_name, "path" + ) if delivery_template is None: msg = ( "Delivery template \"{}\" in anatomy of project \"{}\"" @@ -277,7 +278,7 @@ def deliver_sequence( anatomy_data["frame"] = frame_indicator if format_dict: anatomy_data["root"] = format_dict["root"] - template_obj = anatomy.templates_obj["delivery"][template_name] + template_obj = anatomy.get_template("delivery", template_name, "path") delivery_path = template_obj.format_strict(anatomy_data) delivery_path = os.path.normpath(delivery_path.replace("\\", "/")) diff --git a/client/ayon_core/pipeline/farm/tools.py b/client/ayon_core/pipeline/farm/tools.py index 8ab3b87ff6..1ed7e4b3f5 100644 --- a/client/ayon_core/pipeline/farm/tools.py +++ b/client/ayon_core/pipeline/farm/tools.py @@ -54,7 +54,7 @@ def from_published_scene(instance, replace_in_path=True): template_data["comment"] = None anatomy = instance.context.data['anatomy'] - template_obj = anatomy.templates_obj["publish"]["path"] + template_obj = anatomy.get_template("publish", "default", "path") template_filled = template_obj.format_strict(template_data) file_path = os.path.normpath(template_filled) diff --git a/client/ayon_core/pipeline/publish/lib.py b/client/ayon_core/pipeline/publish/lib.py index b4ed69b5d7..8d2ae85694 100644 --- a/client/ayon_core/pipeline/publish/lib.py +++ b/client/ayon_core/pipeline/publish/lib.py @@ -742,29 +742,18 @@ def get_custom_staging_dir_info( anatomy = Anatomy(project_name) template_name = profile["template_name"] or TRANSIENT_DIR_TEMPLATE - _validate_transient_template(project_name, template_name, anatomy) - custom_staging_dir = anatomy.templates[template_name]["folder"] + custom_staging_dir = anatomy.get_template( + "staging", template_name, "directory" + ) + if custom_staging_dir is None: + raise ValueError(( + "Anatomy of project \"{}\" does not have set" + " \"{}\" template key!" + ).format(project_name, template_name)) is_persistent = profile["custom_staging_dir_persistent"] - return custom_staging_dir, is_persistent - - -def _validate_transient_template(project_name, template_name, anatomy): - """Check that transient template is correctly configured. - - Raises: - ValueError - if misconfigured template - """ - if template_name not in anatomy.templates: - raise ValueError(("Anatomy of project \"{}\" does not have set" - " \"{}\" template key!" - ).format(project_name, template_name)) - - if "folder" not in anatomy.templates[template_name]: - raise ValueError(("There is not set \"folder\" template in \"{}\" anatomy" # noqa - " for project \"{}\"." - ).format(template_name, project_name)) + return str(custom_staging_dir), is_persistent def get_published_workfile_instance(context): @@ -815,9 +804,9 @@ def replace_with_published_scene_path(instance, replace_in_path=True): template_data["ext"] = rep.get("ext") template_data["comment"] = None - anatomy = instance.context.data['anatomy'] - anatomy_filled = anatomy.format(template_data) - template_filled = anatomy_filled["publish"]["path"] + anatomy = instance.context.data["anatomy"] + template = anatomy.get_template("publish", "default", "path") + template_filled = template.format_strict(template_data) file_path = os.path.normpath(template_filled) log.info("Using published scene for render {}".format(file_path)) diff --git a/client/ayon_core/pipeline/usdlib.py b/client/ayon_core/pipeline/usdlib.py index dedcee6f99..9fabd1dce5 100644 --- a/client/ayon_core/pipeline/usdlib.py +++ b/client/ayon_core/pipeline/usdlib.py @@ -341,7 +341,9 @@ def get_usd_master_path(folder_entity, product_name, representation): "version": 0, # stub version zero }) - template_obj = anatomy.templates_obj["publish"]["path"] + template_obj = anatomy.get_template( + "publish", "default","path" + ) path = template_obj.format_strict(template_data) # Remove the version folder diff --git a/client/ayon_core/pipeline/workfile/path_resolving.py b/client/ayon_core/pipeline/workfile/path_resolving.py index 5d36f432ad..239b8c1096 100644 --- a/client/ayon_core/pipeline/workfile/path_resolving.py +++ b/client/ayon_core/pipeline/workfile/path_resolving.py @@ -135,7 +135,7 @@ def get_workdir_with_workdir_data( project_settings ) - template_obj = anatomy.templates_obj[template_key]["folder"] + template_obj = anatomy.get_template("work", template_key, "directory") # Output is TemplateResult object which contain useful data output = template_obj.format_strict(workdir_data) if output: @@ -309,11 +309,12 @@ def get_last_workfile( Returns file with version 1 if there is not workfile yet. Args: - workdir(str): Path to dir where workfiles are stored. - file_template(str): Template of file name. - fill_data(Dict[str, Any]): Data for filling template. - extensions(Iterable[str]): All allowed file extensions of workfile. - full_path(bool): Full path to file is returned if set to True. + workdir (str): Path to dir where workfiles are stored. + file_template (str): Template of file name. + fill_data (Dict[str, Any]): Data for filling template. + extensions (Iterable[str]): All allowed file extensions of workfile. + full_path (Optional[bool]): Full path to file is returned if + set to True. Returns: str: Last or first workfile as filename of full path to filename. @@ -334,7 +335,7 @@ def get_last_workfile( data.pop("comment", None) if not data.get("ext"): data["ext"] = extensions[0] - data["ext"] = data["ext"].replace('.', '') + data["ext"] = data["ext"].lstrip(".") filename = StringTemplate.format_strict_template(file_template, data) if full_path: diff --git a/client/ayon_core/plugins/actions/open_file_explorer.py b/client/ayon_core/plugins/actions/open_file_explorer.py index c221752f11..9369a25eb0 100644 --- a/client/ayon_core/plugins/actions/open_file_explorer.py +++ b/client/ayon_core/plugins/actions/open_file_explorer.py @@ -70,7 +70,9 @@ class OpenTaskPath(LauncherAction): data = get_template_data(project_entity, folder_entity, task_entity) anatomy = Anatomy(project_name) - workdir = anatomy.templates_obj["work"]["folder"].format(data) + workdir = anatomy.get_template( + "work", "default", "folder" + ).format(data) # Remove any potential un-formatted parts of the path valid_workdir = self._find_first_filled_path(workdir) @@ -85,7 +87,9 @@ class OpenTaskPath(LauncherAction): return valid_workdir data.pop("task", None) - workdir = anatomy.templates_obj["work"]["folder"].format(data) + workdir = anatomy.get_template( + "work", "default", "folder" + ).format(data) valid_workdir = self._find_first_filled_path(workdir) if valid_workdir: # Normalize diff --git a/client/ayon_core/plugins/load/delivery.py b/client/ayon_core/plugins/load/delivery.py index 453bdfb87a..5289e594f7 100644 --- a/client/ayon_core/plugins/load/delivery.py +++ b/client/ayon_core/plugins/load/delivery.py @@ -282,7 +282,11 @@ class DeliveryOptionsDialog(QtWidgets.QDialog): """Adds list of delivery templates from Anatomy to dropdown.""" templates = {} for template_name, value in anatomy.templates["delivery"].items(): - if not isinstance(value, str) or not value.startswith('{root'): + path_template = value["path"] + if ( + not isinstance(path_template, str) + or not path_template.startswith('{root') + ): continue templates[template_name] = value diff --git a/client/ayon_core/plugins/publish/collect_otio_subset_resources.py b/client/ayon_core/plugins/publish/collect_otio_subset_resources.py index 3f47e6e3bf..ad3a4d2c23 100644 --- a/client/ayon_core/plugins/publish/collect_otio_subset_resources.py +++ b/client/ayon_core/plugins/publish/collect_otio_subset_resources.py @@ -43,8 +43,10 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): template_name = self.get_template_name(instance) anatomy = instance.context.data["anatomy"] - publish_template_category = anatomy.templates[template_name] - template = os.path.normpath(publish_template_category["path"]) + publish_path_template = anatomy.get_template( + "publish", template_name, "path" + ) + template = os.path.normpath(publish_path_template) self.log.debug( ">> template: {}".format(template)) diff --git a/client/ayon_core/plugins/publish/collect_resources_path.py b/client/ayon_core/plugins/publish/collect_resources_path.py index 6a871124f1..05015909d5 100644 --- a/client/ayon_core/plugins/publish/collect_resources_path.py +++ b/client/ayon_core/plugins/publish/collect_resources_path.py @@ -79,23 +79,12 @@ class CollectResourcesPath(pyblish.api.InstancePlugin): "representation": "TEMP" }) - publish_templates = anatomy.templates_obj["publish"] - if "folder" in publish_templates: - publish_folder = publish_templates["folder"].format_strict( - template_data - ) - else: - # solve deprecated situation when `folder` key is not underneath - # `publish` anatomy - self.log.warning(( - "Deprecation warning: Anatomy does not have set `folder`" - " key underneath `publish` (in global of for project `{}`)." - ).format(anatomy.project_name)) - - file_path = publish_templates["path"].format_strict(template_data) - publish_folder = os.path.dirname(file_path) - - publish_folder = os.path.normpath(publish_folder) + publish_templates = anatomy.get_template( + "publish", "default", "directory" + ) + publish_folder = os.path.normpath( + publish_templates.format_strict(template_data) + ) resources_folder = os.path.join(publish_folder, "resources") instance.data["publishDir"] = publish_folder diff --git a/client/ayon_core/plugins/publish/integrate.py b/client/ayon_core/plugins/publish/integrate.py index b7839338ae..16c8ebaaf5 100644 --- a/client/ayon_core/plugins/publish/integrate.py +++ b/client/ayon_core/plugins/publish/integrate.py @@ -665,8 +665,9 @@ class IntegrateAsset(pyblish.api.InstancePlugin): self.log.debug("Anatomy template name: {}".format(template_name)) anatomy = instance.context.data["anatomy"] - publish_template_category = anatomy.templates[template_name] - template = os.path.normpath(publish_template_category["path"]) + publish_template = anatomy.get_template("publish", template_name) + path_template_obj = publish_template["path"] + template = os.path.normpath(path_template_obj) is_udim = bool(repre.get("udim")) @@ -698,7 +699,6 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # - template_data (Dict[str, Any]): source data used to fill template # - to add required data to 'repre_context' not used for # formatting - path_template_obj = anatomy.templates_obj[template_name]["path"] # Treat template with 'orignalBasename' in special way if "{originalBasename}" in template: @@ -753,9 +753,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): if not is_udim: # Change padding for frames if template has defined higher # padding. - template_padding = int( - publish_template_category["frame_padding"] - ) + template_padding = anatomy.templates_obj.frame_padding if template_padding > destination_padding: destination_padding = template_padding @@ -841,7 +839,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # todo: Are we sure the assumption each representation # ends up in the same folder is valid? if not instance.data.get("publishDir"): - template_obj = anatomy.templates_obj[template_name]["folder"] + template_obj = publish_template["directory"] template_filled = template_obj.format_strict(template_data) instance.data["publishDir"] = template_filled diff --git a/client/ayon_core/plugins/publish/integrate_hero_version.py b/client/ayon_core/plugins/publish/integrate_hero_version.py index 6d690ba94b..e313c94393 100644 --- a/client/ayon_core/plugins/publish/integrate_hero_version.py +++ b/client/ayon_core/plugins/publish/integrate_hero_version.py @@ -103,22 +103,15 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): project_name = anatomy.project_name template_key = self._get_template_key(project_name, instance) + hero_template = anatomy.get_template("hero", template_key, "path") - if template_key not in anatomy.templates: + if hero_template is None: self.log.warning(( "!!! Anatomy of project \"{}\" does not have set" " \"{}\" template key!" ).format(project_name, template_key)) return - if "path" not in anatomy.templates[template_key]: - self.log.warning(( - "!!! There is not set \"path\" template in \"{}\" anatomy" - " for project \"{}\"." - ).format(template_key, project_name)) - return - - hero_template = anatomy.templates[template_key]["path"] self.log.debug("`hero` template check was successful. `{}`".format( hero_template )) @@ -327,7 +320,9 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): try: src_to_dst_file_paths = [] repre_integrate_data = [] - path_template_obj = anatomy.templates_obj[template_key]["path"] + path_template_obj = anatomy.get_template( + "hero", template_key, "path" + ) for repre_info in published_repres.values(): # Skip if new repre does not have published repre files @@ -383,9 +378,7 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): anatomy_data ) head, tail = _template_filled.split(frame_splitter) - padding = int( - anatomy.templates[template_key]["frame_padding"] - ) + padding = anatomy.templates_obj.frame_padding dst_col = clique.Collection( head=head, padding=padding, tail=tail @@ -545,29 +538,12 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): "originalBasename": instance.data.get("originalBasename") }) - if "folder" in anatomy.templates[template_key]: - template_obj = anatomy.templates_obj[template_key]["folder"] - publish_folder = template_obj.format_strict(template_data) - else: - # This is for cases of Deprecated anatomy without `folder` - # TODO remove when all clients have solved this issue - self.log.warning(( - "Deprecation warning: Anatomy does not have set `folder`" - " key underneath `publish` (in global of for project `{}`)." - ).format(anatomy.project_name)) - # solve deprecated situation when `folder` key is not underneath - # `publish` anatomy - template_data.update({ - "frame": "FRAME_TEMP", - "representation": "TEMP" - }) - template_obj = anatomy.templates_obj[template_key]["path"] - file_path = template_obj.format_strict(template_data) - - # Directory - publish_folder = os.path.dirname(file_path) - - publish_folder = os.path.normpath(publish_folder) + template_obj = anatomy.get_template( + "hero", template_key, "directory" + ) + publish_folder = os.path.normpath( + template_obj.format_strict(template_data) + ) self.log.debug("hero publish dir: \"{}\"".format(publish_folder)) diff --git a/client/ayon_core/tools/push_to_project/models/integrate.py b/client/ayon_core/tools/push_to_project/models/integrate.py index 8a29da2fe4..0987dc0c56 100644 --- a/client/ayon_core/tools/push_to_project/models/integrate.py +++ b/client/ayon_core/tools/push_to_project/models/integrate.py @@ -973,12 +973,9 @@ class ProjectPushItemProcess: "version": version_entity["version"] }) - path_template = anatomy.templates[template_name]["path"].replace( - "\\", "/" - ) - file_template = StringTemplate( - anatomy.templates[template_name]["file"] - ) + publish_template = anatomy.get_template("publish", template_name) + path_template = publish_template["path"].replace("\\", "/") + file_template = publish_template["file"] self._log_info("Preparing files to transfer") processed_repre_items = self._prepare_file_transactions( anatomy, template_name, formatting_data, file_template @@ -1014,7 +1011,9 @@ class ProjectPushItemProcess: if repre_output_name is not None: repre_format_data["output"] = repre_output_name - template_obj = anatomy.templates_obj[template_name]["folder"] + template_obj = anatomy.get_template( + "publish", template_name, "directory" + ) folder_path = template_obj.format_strict(formatting_data) repre_context = folder_path.used_values folder_path_rootless = folder_path.rootless diff --git a/client/ayon_core/tools/texture_copy/app.py b/client/ayon_core/tools/texture_copy/app.py index b3484fbcd0..9a94ba44f7 100644 --- a/client/ayon_core/tools/texture_copy/app.py +++ b/client/ayon_core/tools/texture_copy/app.py @@ -40,7 +40,9 @@ class TextureCopy: }, }) anatomy = Anatomy(project_name, project_entity=project_entity) - template_obj = anatomy.templates_obj["texture"]["path"] + template_obj = anatomy.get_template( + "publish", "texture", "path" + ) return template_obj.format_strict(template_data) def _get_version(self, path): diff --git a/client/ayon_core/tools/workfiles/models/workfiles.py b/client/ayon_core/tools/workfiles/models/workfiles.py index 35a49a908f..f4ca08a45a 100644 --- a/client/ayon_core/tools/workfiles/models/workfiles.py +++ b/client/ayon_core/tools/workfiles/models/workfiles.py @@ -253,8 +253,9 @@ class WorkareaModel: return list(comment_hints), current_comment def _get_workdir(self, anatomy, template_key, fill_data): - template_info = anatomy.templates_obj[template_key] - directory_template = template_info["folder"] + directory_template = anatomy.get_template( + "work", template_key, "directory" + ) return directory_template.format_strict(fill_data).normalized() def get_workarea_save_as_data(self, folder_id, task_id): @@ -299,8 +300,7 @@ class WorkareaModel: workdir = self._get_workdir(anatomy, template_key, fill_data) - template_info = anatomy.templates_obj[template_key] - file_template = template_info["file"] + file_template = anatomy.get_template("work", template_key, "file") comment_hints, comment = self._get_comments_from_root( file_template, @@ -342,8 +342,7 @@ class WorkareaModel: workdir = self._get_workdir(anatomy, template_key, fill_data) - template_info = anatomy.templates_obj[template_key] - file_template = template_info["file"] + file_template = anatomy.get_template("work", template_key, "file") if use_last_version: version = self._get_last_workfile_version( From 4d26f516b72172993218c5ecc62e7e97c86647c4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Mar 2024 18:46:49 +0100 Subject: [PATCH 038/150] fix default template names --- client/ayon_core/pipeline/publish/constants.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/pipeline/publish/constants.py b/client/ayon_core/pipeline/publish/constants.py index 92e3fb089f..38f5ffef3f 100644 --- a/client/ayon_core/pipeline/publish/constants.py +++ b/client/ayon_core/pipeline/publish/constants.py @@ -6,6 +6,6 @@ ValidateContentsOrder = pyblish.api.ValidatorOrder + 0.1 ValidateSceneOrder = pyblish.api.ValidatorOrder + 0.2 ValidateMeshOrder = pyblish.api.ValidatorOrder + 0.3 -DEFAULT_PUBLISH_TEMPLATE = "publish" -DEFAULT_HERO_PUBLISH_TEMPLATE = "hero" -TRANSIENT_DIR_TEMPLATE = "transient" +DEFAULT_PUBLISH_TEMPLATE = "default" +DEFAULT_HERO_PUBLISH_TEMPLATE = "default" +TRANSIENT_DIR_TEMPLATE = "default" From 3e00d850ccec6f0f43d7a63a3471c4b5ec0d8b0c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Mar 2024 18:47:22 +0100 Subject: [PATCH 039/150] removed unused 'TemplatesDict' and 'TemplatesResultDict' from path templates --- client/ayon_core/lib/__init__.py | 6 - client/ayon_core/lib/path_templates.py | 268 +------------------------ 2 files changed, 2 insertions(+), 272 deletions(-) diff --git a/client/ayon_core/lib/__init__.py b/client/ayon_core/lib/__init__.py index 6f76506dd8..918d14c8a7 100644 --- a/client/ayon_core/lib/__init__.py +++ b/client/ayon_core/lib/__init__.py @@ -81,11 +81,8 @@ from .log import ( ) from .path_templates import ( - merge_dict, - TemplateMissingKey, TemplateUnsolved, StringTemplate, - TemplatesDict, FormatObject, ) @@ -265,11 +262,8 @@ __all__ = [ "get_version_from_path", "get_last_version_from_path", - "merge_dict", - "TemplateMissingKey", "TemplateUnsolved", "StringTemplate", - "TemplatesDict", "FormatObject", "terminal", diff --git a/client/ayon_core/lib/path_templates.py b/client/ayon_core/lib/path_templates.py index 9be1736abf..09d11ea1de 100644 --- a/client/ayon_core/lib/path_templates.py +++ b/client/ayon_core/lib/path_templates.py @@ -1,8 +1,6 @@ import os import re -import copy import numbers -import collections import six @@ -12,44 +10,6 @@ SUB_DICT_PATTERN = re.compile(r"([^\[\]]+)") OPTIONAL_PATTERN = re.compile(r"(<.*?[^{0]*>)[^0-9]*?") -def merge_dict(main_dict, enhance_dict): - """Merges dictionaries by keys. - - Function call itself if value on key is again dictionary. - - Args: - main_dict (dict): First dict to merge second one into. - enhance_dict (dict): Second dict to be merged. - - Returns: - dict: Merged result. - - .. note:: does not overrides whole value on first found key - but only values differences from enhance_dict - - """ - for key, value in enhance_dict.items(): - if key not in main_dict: - main_dict[key] = value - elif isinstance(value, dict) and isinstance(main_dict[key], dict): - main_dict[key] = merge_dict(main_dict[key], value) - else: - main_dict[key] = value - return main_dict - - -class TemplateMissingKey(Exception): - """Exception for cases when key does not exist in template.""" - - msg = "Template key does not exist: `{}`." - - def __init__(self, parents): - parent_join = "".join(["[\"{0}\"]".format(key) for key in parents]) - super(TemplateMissingKey, self).__init__( - self.msg.format(parent_join) - ) - - class TemplateUnsolved(Exception): """Exception for unsolved template when strict is set to True.""" @@ -240,137 +200,6 @@ class StringTemplate(object): new_parts.extend(tmp_parts[idx]) return new_parts - -class TemplatesDict(object): - def __init__(self, templates=None): - self._raw_templates = None - self._templates = None - self._objected_templates = None - self.set_templates(templates) - - def set_templates(self, templates): - if templates is None: - self._raw_templates = None - self._templates = None - self._objected_templates = None - elif isinstance(templates, dict): - self._raw_templates = copy.deepcopy(templates) - self._templates = templates - self._objected_templates = self.create_objected_templates( - templates) - else: - raise TypeError("<{}> argument must be a dict, not {}.".format( - self.__class__.__name__, str(type(templates)) - )) - - def __getitem__(self, key): - return self.objected_templates[key] - - def get(self, key, *args, **kwargs): - return self.objected_templates.get(key, *args, **kwargs) - - @property - def raw_templates(self): - return self._raw_templates - - @property - def templates(self): - return self._templates - - @property - def objected_templates(self): - return self._objected_templates - - def _create_template_object(self, template): - """Create template object from a template string. - - Separated into method to give option change class of templates. - - Args: - template (str): Template string. - - Returns: - StringTemplate: Object of template. - """ - - return StringTemplate(template) - - def create_objected_templates(self, templates): - if not isinstance(templates, dict): - raise TypeError("Expected dict object, got {}".format( - str(type(templates)) - )) - - objected_templates = copy.deepcopy(templates) - inner_queue = collections.deque() - inner_queue.append(objected_templates) - while inner_queue: - item = inner_queue.popleft() - if not isinstance(item, dict): - continue - for key in tuple(item.keys()): - value = item[key] - if isinstance(value, six.string_types): - item[key] = self._create_template_object(value) - elif isinstance(value, dict): - inner_queue.append(value) - return objected_templates - - def _format_value(self, value, data): - if isinstance(value, StringTemplate): - return value.format(data) - - if isinstance(value, dict): - return self._solve_dict(value, data) - return value - - def _solve_dict(self, templates, data): - """ Solves templates with entered data. - - Args: - templates (dict): All templates which will be formatted. - data (dict): Containing keys to be filled into template. - - Returns: - dict: With `TemplateResult` in values containing filled or - partially filled templates. - """ - output = collections.defaultdict(dict) - for key, value in templates.items(): - output[key] = self._format_value(value, data) - - return output - - def format(self, in_data, only_keys=True, strict=True): - """ Solves templates based on entered data. - - Args: - data (dict): Containing keys to be filled into template. - only_keys (bool, optional): Decides if environ will be used to - fill templates or only keys in data. - - Returns: - TemplatesResultDict: Output `TemplateResult` have `strict` - attribute set to True so accessing unfilled keys in templates - will raise exceptions with explaned error. - """ - # Create a copy of inserted data - data = copy.deepcopy(in_data) - - # Add environment variable to data - if only_keys is False: - for key, val in os.environ.items(): - env_key = "$" + key - if env_key not in data: - data[env_key] = val - - solved = self._solve_dict(self.objected_templates, data) - - output = TemplatesResultDict(solved) - output.strict = strict - return output - - class TemplateResult(str): """Result of template format with most of information in. @@ -379,8 +208,8 @@ class TemplateResult(str): only used keys. solved (bool): For check if all required keys were filled. template (str): Original template. - missing_keys (list): Missing keys that were not in the data. Include - missing optional keys. + missing_keys (Iterable[str]): Missing keys that were not in the data. + Include missing optional keys. invalid_types (dict): When key was found in data, but value had not allowed DataType. Allowed data types are `numbers`, `str`(`basestring`) and `dict`. Dictionary may cause invalid type @@ -445,99 +274,6 @@ class TemplateResult(str): ) -class TemplatesResultDict(dict): - """Holds and wrap TemplateResults for easy bug report.""" - - def __init__(self, in_data, key=None, parent=None, strict=None): - super(TemplatesResultDict, self).__init__() - for _key, _value in in_data.items(): - if isinstance(_value, dict): - _value = self.__class__(_value, _key, self) - self[_key] = _value - - self.key = key - self.parent = parent - self.strict = strict - if self.parent is None and strict is None: - self.strict = True - - def __getitem__(self, key): - if key not in self.keys(): - hier = self.hierarchy() - hier.append(key) - raise TemplateMissingKey(hier) - - value = super(TemplatesResultDict, self).__getitem__(key) - if isinstance(value, self.__class__): - return value - - # Raise exception when expected solved templates and it is not. - if self.raise_on_unsolved and hasattr(value, "validate"): - value.validate() - return value - - @property - def raise_on_unsolved(self): - """To affect this change `strict` attribute.""" - if self.strict is not None: - return self.strict - return self.parent.raise_on_unsolved - - def hierarchy(self): - """Return dictionary keys one by one to root parent.""" - if self.parent is None: - return [] - - hier_keys = [] - par_hier = self.parent.hierarchy() - if par_hier: - hier_keys.extend(par_hier) - hier_keys.append(self.key) - - return hier_keys - - @property - def missing_keys(self): - """Return missing keys of all children templates.""" - missing_keys = set() - for value in self.values(): - missing_keys |= value.missing_keys - return missing_keys - - @property - def invalid_types(self): - """Return invalid types of all children templates.""" - invalid_types = {} - for value in self.values(): - invalid_types = merge_dict(invalid_types, value.invalid_types) - return invalid_types - - @property - def used_values(self): - """Return used values for all children templates.""" - used_values = {} - for value in self.values(): - used_values = merge_dict(used_values, value.used_values) - return used_values - - def get_solved(self): - """Get only solved key from templates.""" - result = {} - for key, value in self.items(): - if isinstance(value, self.__class__): - value = value.get_solved() - if not value: - continue - result[key] = value - - elif ( - not hasattr(value, "solved") or - value.solved - ): - result[key] = value - return self.__class__(result, key=self.key, parent=self.parent) - - class TemplatePartResult: """Result to store result of template parts.""" def __init__(self, optional=False): From 587bfe462f6548b7bd7463ad8d9d4285ae98f886 Mon Sep 17 00:00:00 2001 From: moonyuet Date: Mon, 18 Mar 2024 12:15:58 +0100 Subject: [PATCH 040/150] code tweaks according to big roy's comment --- .../plugins/publish/validate_renderpasses.py | 27 +++++++++---------- .../max/server/settings/publishers.py | 2 +- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_renderpasses.py b/client/ayon_core/hosts/max/plugins/publish/validate_renderpasses.py index 4e2298045c..0fbdb2940e 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_renderpasses.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_renderpasses.py @@ -12,19 +12,16 @@ from ayon_core.hosts.max.api.lib_rendersettings import RenderSettings class ValidateRenderPasses(OptionalPyblishPluginMixin, pyblish.api.InstancePlugin): - """Validates Render Passes before Deadline Submission + """Validates Render Passes before farm submission """ order = ValidateContentsOrder families = ["maxrender"] hosts = ["max"] label = "Validate Render Passes" - optional = True actions = [RepairAction] def process(self, instance): - if not self.is_active(instance.data): - return invalid = self.get_invalid(instance) if invalid: bullet_point_invalid_statement = "\n".join( @@ -64,7 +61,7 @@ class ValidateRenderPasses(OptionalPyblishPluginMixin, Args: instance (pyblish.api.Instance): instance - filename (str): filename of the Max scene + workfile_name (str): filename of the Max scene Returns: list: list of invalid filename which doesn't match @@ -72,11 +69,11 @@ class ValidateRenderPasses(OptionalPyblishPluginMixin, """ invalid = [] file = rt.maxFileName - filename, ext = os.path.splitext(file) - if filename not in rt.rendOutputFilename: + workfile_name, ext = os.path.splitext(file) + if workfile_name not in rt.rendOutputFilename: cls.log.error( "Render output folder must include" - f" the max scene name {filename} " + f" the max scene name {workfile_name} " ) invalid_folder_name = os.path.dirname( rt.rendOutputFilename).replace( @@ -104,13 +101,11 @@ class ValidateRenderPasses(OptionalPyblishPluginMixin, render_elem_num = render_elem.NumRenderElements() for i in range(render_elem_num): renderlayer_name = render_elem.GetRenderElement(i) - renderpass = str(renderlayer_name).split(":")[-1] + renderpass = str(renderlayer_name).rsplit(":", 1)[-1] rend_file = render_elem.GetRenderElementFilename(i) if not rend_file: - cls.log.error( - f"No filepath for render element {renderpass}") - invalid.append((f"Invalid {renderpass}", - "No filepath")) + continue + rend_fname, ext = os.path.splitext( os.path.basename(rend_file)) invalid_filenames = cls.get_invalid_filenames( @@ -123,6 +118,10 @@ class ValidateRenderPasses(OptionalPyblishPluginMixin, cls.log.debug( "Renderpass validation does not support Arnold yet," " validation skipped...") + else: + cls.log.debug( + "Skipping render element validation " + f"for renderer : {renderer}") return invalid @classmethod @@ -140,7 +139,7 @@ class ValidateRenderPasses(OptionalPyblishPluginMixin, """ invalid = [] if instance.name not in file_name: - cls.log.error("The renderpass should have instance name inside.") + cls.log.error("The renderpass filename should contain the instance name.") invalid.append((f"Invalid instance name", file_name)) if renderpass is not None: diff --git a/server_addon/max/server/settings/publishers.py b/server_addon/max/server/settings/publishers.py index 819bca1509..5e1b348d92 100644 --- a/server_addon/max/server/settings/publishers.py +++ b/server_addon/max/server/settings/publishers.py @@ -191,7 +191,7 @@ DEFAULT_PUBLISH_SETTINGS = { }, "ValidateRenderPasses": { "enabled": True, - "optional": True, + "optional": False, "active": True }, "ExtractModelObj": { From e498b0aafe7ed27aca104fff520acab01ba7a880 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 18 Mar 2024 13:57:39 +0100 Subject: [PATCH 041/150] use 'product_types' instead of 'families' in loader plugins --- client/ayon_core/pipeline/load/plugins.py | 6 +++--- client/ayon_core/pipeline/load/utils.py | 2 +- client/ayon_core/pipeline/workfile/build_workfile.py | 9 +++++---- .../pipeline/workfile/workfile_template_builder.py | 12 ++++++------ 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/client/ayon_core/pipeline/load/plugins.py b/client/ayon_core/pipeline/load/plugins.py index b5d228380a..8c015f19fe 100644 --- a/client/ayon_core/pipeline/load/plugins.py +++ b/client/ayon_core/pipeline/load/plugins.py @@ -24,7 +24,7 @@ class LoaderPlugin(list): """ - families = [] + product_types = [] representations = [] extensions = {"*"} order = 0 @@ -118,7 +118,7 @@ class LoaderPlugin(list): On override make sure it is overriden as class or static method. - This checks the version's families and the representation for the given + This checks the product type and the representation for the given loader plugin. Args: @@ -130,7 +130,7 @@ class LoaderPlugin(list): """ plugin_repre_names = cls.get_representations() - plugin_product_types = cls.families + plugin_product_types = cls.product_types if ( not plugin_repre_names or not plugin_product_types diff --git a/client/ayon_core/pipeline/load/utils.py b/client/ayon_core/pipeline/load/utils.py index 8fdaf52e27..f3d39800cd 100644 --- a/client/ayon_core/pipeline/load/utils.py +++ b/client/ayon_core/pipeline/load/utils.py @@ -735,7 +735,7 @@ def get_representation_path(representation, root=None): def is_compatible_loader(Loader, context): """Return whether a loader is compatible with a context. - This checks the version's families and the representation for the given + This checks the product type and the representation for the given Loader. Returns: diff --git a/client/ayon_core/pipeline/workfile/build_workfile.py b/client/ayon_core/pipeline/workfile/build_workfile.py index 6d7ea2c7ad..5ff8b21259 100644 --- a/client/ayon_core/pipeline/workfile/build_workfile.py +++ b/client/ayon_core/pipeline/workfile/build_workfile.py @@ -315,9 +315,9 @@ class BuildWorkfile: ).format(json.dumps(profile, indent=4))) continue - # Check families - profile_families = profile.get("product_types") - if not profile_families: + # Check product types + profile_product_types = profile.get("product_types") + if not profile_product_types: self.log.warning(( "Build profile is missing families configuration: {0}" ).format(json.dumps(profile, indent=4))) @@ -334,7 +334,8 @@ class BuildWorkfile: # Prepare lowered families and representation names profile["product_types_lowered"] = [ - fam.lower() for fam in profile_families + product_type.lower() + for product_type in profile_product_types ] profile["repre_names_lowered"] = [ name.lower() for name in profile_repre_names diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index e8b5268a6d..124952b2c0 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -1286,14 +1286,14 @@ class PlaceholderLoadMixin(object): loader_items = list(sorted(loader_items, key=lambda i: i["label"])) options = options or {} - # Get families from all loaders excluding "*" - families = set() + # Get product types from all loaders excluding "*" + product_types = set() for loader in loaders_by_name.values(): - families.update(loader.families) - families.discard("*") + product_types.update(loader.product_types) + product_types.discard("*") # Sort for readability - families = list(sorted(families)) + product_types = list(sorted(product_types)) builder_type_enum_items = [ {"label": "Current folder", "value": "context_folder"}, @@ -1333,7 +1333,7 @@ class PlaceholderLoadMixin(object): "product_type", label="Product type", default=product_type, - items=families + items=product_types ), attribute_definitions.TextDef( "representation", From e29bcbcf54be09829e9d07bd0a7ef08e0dbd7494 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 18 Mar 2024 13:59:00 +0100 Subject: [PATCH 042/150] rename 'families' attribute to 'product_types' in loader plugins --- .../plugins/load/load_background.py | 2 +- .../aftereffects/plugins/load/load_file.py | 2 +- .../blender/plugins/load/import_workfile.py | 4 +-- .../hosts/blender/plugins/load/load_abc.py | 2 +- .../hosts/blender/plugins/load/load_action.py | 2 +- .../blender/plugins/load/load_animation.py | 2 +- .../hosts/blender/plugins/load/load_audio.py | 2 +- .../hosts/blender/plugins/load/load_blend.py | 2 +- .../blender/plugins/load/load_blendscene.py | 2 +- .../blender/plugins/load/load_camera_abc.py | 2 +- .../blender/plugins/load/load_camera_fbx.py | 2 +- .../hosts/blender/plugins/load/load_fbx.py | 2 +- .../blender/plugins/load/load_layout_json.py | 2 +- .../hosts/blender/plugins/load/load_look.py | 2 +- .../hosts/flame/plugins/load/load_clip.py | 2 +- .../flame/plugins/load/load_clip_batch.py | 2 +- .../hosts/fusion/plugins/load/actions.py | 30 ++++++++-------- .../hosts/fusion/plugins/load/load_alembic.py | 2 +- .../hosts/fusion/plugins/load/load_fbx.py | 2 +- .../fusion/plugins/load/load_sequence.py | 2 +- .../hosts/fusion/plugins/load/load_usd.py | 2 +- .../fusion/plugins/load/load_workfile.py | 2 +- client/ayon_core/hosts/harmony/api/README.md | 2 +- .../hosts/harmony/plugins/load/load_audio.py | 2 +- .../harmony/plugins/load/load_background.py | 2 +- .../plugins/load/load_imagesequence.py | 9 ++++- .../harmony/plugins/load/load_palette.py | 2 +- .../harmony/plugins/load/load_template.py | 2 +- .../plugins/load/load_template_workfile.py | 4 +-- .../hosts/hiero/plugins/load/load_clip.py | 10 ++---- .../hosts/hiero/plugins/load/load_effects.py | 2 +- .../hosts/houdini/plugins/load/actions.py | 4 +-- .../houdini/plugins/load/load_alembic.py | 2 +- .../plugins/load/load_alembic_archive.py | 2 +- .../hosts/houdini/plugins/load/load_ass.py | 2 +- .../hosts/houdini/plugins/load/load_bgeo.py | 2 +- .../hosts/houdini/plugins/load/load_camera.py | 2 +- .../hosts/houdini/plugins/load/load_fbx.py | 2 +- .../hosts/houdini/plugins/load/load_hda.py | 2 +- .../hosts/houdini/plugins/load/load_image.py | 2 +- .../plugins/load/load_redshift_proxy.py | 2 +- .../houdini/plugins/load/load_usd_layer.py | 2 +- .../plugins/load/load_usd_reference.py | 2 +- .../hosts/houdini/plugins/load/load_vdb.py | 2 +- .../houdini/plugins/load/show_usdview.py | 2 +- .../hosts/max/plugins/load/load_camera_fbx.py | 2 +- .../hosts/max/plugins/load/load_max_scene.py | 7 ++-- .../hosts/max/plugins/load/load_model.py | 2 +- .../hosts/max/plugins/load/load_model_fbx.py | 2 +- .../hosts/max/plugins/load/load_model_obj.py | 2 +- .../hosts/max/plugins/load/load_model_usd.py | 2 +- .../hosts/max/plugins/load/load_pointcache.py | 2 +- .../plugins/load/load_pointcache_ornatrix.py | 2 +- .../hosts/max/plugins/load/load_pointcloud.py | 2 +- .../max/plugins/load/load_redshift_proxy.py | 2 +- .../hosts/max/plugins/load/load_tycache.py | 2 +- .../plugins/publish/collect_frame_range.py | 11 ++++-- .../maya/plugins/load/_load_animation.py | 12 ++++--- .../hosts/maya/plugins/load/actions.py | 20 ++++++----- .../maya/plugins/load/load_arnold_standin.py | 4 ++- .../hosts/maya/plugins/load/load_assembly.py | 2 +- .../hosts/maya/plugins/load/load_audio.py | 2 +- .../hosts/maya/plugins/load/load_gpucache.py | 2 +- .../hosts/maya/plugins/load/load_image.py | 2 +- .../maya/plugins/load/load_image_plane.py | 2 +- .../hosts/maya/plugins/load/load_look.py | 2 +- .../hosts/maya/plugins/load/load_matchmove.py | 2 +- .../hosts/maya/plugins/load/load_maya_usd.py | 2 +- .../maya/plugins/load/load_multiverse_usd.py | 10 ++++-- .../plugins/load/load_multiverse_usd_over.py | 2 +- .../maya/plugins/load/load_redshift_proxy.py | 2 +- .../hosts/maya/plugins/load/load_reference.py | 34 ++++++++++--------- .../maya/plugins/load/load_rendersetup.py | 2 +- .../maya/plugins/load/load_vdb_to_arnold.py | 2 +- .../maya/plugins/load/load_vdb_to_redshift.py | 2 +- .../maya/plugins/load/load_vdb_to_vray.py | 2 +- .../hosts/maya/plugins/load/load_vrayproxy.py | 2 +- .../hosts/maya/plugins/load/load_vrayscene.py | 2 +- .../hosts/maya/plugins/load/load_xgen.py | 2 +- .../maya/plugins/load/load_yeti_cache.py | 2 +- .../hosts/maya/plugins/load/load_yeti_rig.py | 2 +- .../maya/plugins/publish/collect_animation.py | 2 +- .../hosts/nuke/plugins/load/actions.py | 22 ++++++------ .../hosts/nuke/plugins/load/load_backdrop.py | 2 +- .../nuke/plugins/load/load_camera_abc.py | 2 +- .../hosts/nuke/plugins/load/load_clip.py | 2 +- .../hosts/nuke/plugins/load/load_effects.py | 2 +- .../nuke/plugins/load/load_effects_ip.py | 2 +- .../hosts/nuke/plugins/load/load_gizmo.py | 2 +- .../hosts/nuke/plugins/load/load_gizmo_ip.py | 2 +- .../hosts/nuke/plugins/load/load_image.py | 2 +- .../hosts/nuke/plugins/load/load_matchmove.py | 2 +- .../hosts/nuke/plugins/load/load_model.py | 2 +- .../hosts/nuke/plugins/load/load_ociolook.py | 2 +- .../nuke/plugins/load/load_script_precomp.py | 2 +- .../nuke/plugins/publish/collect_backdrop.py | 2 +- .../nuke/plugins/publish/collect_gizmo.py | 2 +- .../photoshop/plugins/load/load_image.py | 2 +- .../plugins/load/load_image_from_sequence.py | 2 +- .../photoshop/plugins/load/load_reference.py | 2 +- .../hosts/resolve/plugins/load/load_clip.py | 2 +- .../plugins/publish/extract_workfile.py | 2 +- .../plugins/load/load_mesh.py | 2 +- .../hosts/tvpaint/plugins/load/load_image.py | 2 +- .../plugins/load/load_reference_image.py | 2 +- .../hosts/tvpaint/plugins/load/load_sound.py | 2 +- .../tvpaint/plugins/load/load_workfile.py | 2 +- .../plugins/load/load_alembic_animation.py | 2 +- .../unreal/plugins/load/load_animation.py | 2 +- .../hosts/unreal/plugins/load/load_camera.py | 2 +- .../plugins/load/load_geometrycache_abc.py | 2 +- .../hosts/unreal/plugins/load/load_layout.py | 2 +- .../plugins/load/load_layout_existing.py | 2 +- .../plugins/load/load_skeletalmesh_abc.py | 2 +- .../plugins/load/load_skeletalmesh_fbx.py | 2 +- .../plugins/load/load_staticmesh_abc.py | 2 +- .../plugins/load/load_staticmesh_fbx.py | 2 +- .../hosts/unreal/plugins/load/load_uasset.py | 4 +-- .../unreal/plugins/load/load_yeticache.py | 2 +- client/ayon_core/plugins/load/copy_file.py | 2 +- .../ayon_core/plugins/load/copy_file_path.py | 2 +- .../plugins/load/delete_old_versions.py | 2 +- client/ayon_core/plugins/load/delivery.py | 2 +- client/ayon_core/plugins/load/open_djv.py | 2 +- client/ayon_core/plugins/load/open_file.py | 2 +- .../ayon_core/plugins/load/push_to_library.py | 2 +- 126 files changed, 216 insertions(+), 191 deletions(-) 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 e45de5524e..9c43dc0e77 100644 --- a/client/ayon_core/hosts/aftereffects/plugins/load/load_background.py +++ b/client/ayon_core/hosts/aftereffects/plugins/load/load_background.py @@ -20,7 +20,7 @@ class BackgroundLoader(api.AfterEffectsLoader): metadata """ label = "Load JSON Background" - families = ["background"] + product_types = ["background"] representations = ["json"] def load(self, context, name=None, namespace=None, data=None): 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 29536945dd..2d9ba1b677 100644 --- a/client/ayon_core/hosts/aftereffects/plugins/load/load_file.py +++ b/client/ayon_core/hosts/aftereffects/plugins/load/load_file.py @@ -12,7 +12,7 @@ class FileLoader(api.AfterEffectsLoader): """ label = "Load file" - families = ["image", + product_types = ["image", "plate", "render", "prerender", diff --git a/client/ayon_core/hosts/blender/plugins/load/import_workfile.py b/client/ayon_core/hosts/blender/plugins/load/import_workfile.py index 7144c132e8..f64437e753 100644 --- a/client/ayon_core/hosts/blender/plugins/load/import_workfile.py +++ b/client/ayon_core/hosts/blender/plugins/load/import_workfile.py @@ -44,7 +44,7 @@ class AppendBlendLoader(plugin.AssetLoader): """ representations = ["blend"] - families = ["workfile"] + product_types = ["workfile"] label = "Append Workfile" order = 9 @@ -69,7 +69,7 @@ class ImportBlendLoader(plugin.AssetLoader): """ representations = ["blend"] - families = ["workfile"] + product_types = ["workfile"] label = "Import Workfile" order = 9 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 7ae9936ed5..a6e834e20a 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_abc.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_abc.py @@ -26,7 +26,7 @@ class CacheModelLoader(plugin.AssetLoader): Note: At least for now it only supports Alembic files. """ - families = ["model", "pointcache", "animation"] + product_types = ["model", "pointcache", "animation"] representations = ["abc"] label = "Load Alembic" 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 a079387b9f..446fa20fec 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_action.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_action.py @@ -24,7 +24,7 @@ class BlendActionLoader(plugin.AssetLoader): moment. """ - families = ["action"] + product_types = ["action"] representations = ["blend"] label = "Link Action" diff --git a/client/ayon_core/hosts/blender/plugins/load/load_animation.py b/client/ayon_core/hosts/blender/plugins/load/load_animation.py index b805790c28..5da270f9e4 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_animation.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_animation.py @@ -16,7 +16,7 @@ class BlendAnimationLoader(plugin.AssetLoader): moment. """ - families = ["animation"] + product_types = ["animation"] representations = ["blend"] label = "Link Animation" 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 de74bf95d3..6e72c58bc9 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_audio.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_audio.py @@ -20,7 +20,7 @@ from ayon_core.hosts.blender.api.pipeline import ( class AudioLoader(plugin.AssetLoader): """Load audio in Blender.""" - families = ["audio"] + product_types = ["audio"] representations = ["wav"] label = "Load Audio" 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 465485ba86..8cc246f46e 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_blend.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_blend.py @@ -20,7 +20,7 @@ from ayon_core.hosts.blender.api.pipeline import ( class BlendLoader(plugin.AssetLoader): """Load assets from a .blend file.""" - families = ["model", "rig", "layout", "camera"] + product_types = ["model", "rig", "layout", "camera"] representations = ["blend"] label = "Append Blend" 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 2c93b739f8..41964a906a 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_blendscene.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_blendscene.py @@ -18,7 +18,7 @@ from ayon_core.hosts.blender.api.pipeline import ( class BlendSceneLoader(plugin.AssetLoader): """Load assets from a .blend file.""" - families = ["blendScene"] + product_types = ["blendScene"] representations = ["blend"] label = "Append Blend" 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 98a4e2c2ec..4c8bfba9a5 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 @@ -23,7 +23,7 @@ class AbcCameraLoader(plugin.AssetLoader): Stores the imported asset in an empty named after the asset. """ - families = ["camera"] + product_types = ["camera"] representations = ["abc"] label = "Load Camera (ABC)" 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 f31e53270e..d14f145f81 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 @@ -23,7 +23,7 @@ class FbxCameraLoader(plugin.AssetLoader): Stores the imported asset in an empty named after the asset. """ - families = ["camera"] + product_types = ["camera"] representations = ["fbx"] label = "Load Camera (FBX)" 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 1eaab4e7b7..c330dca160 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_fbx.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_fbx.py @@ -23,7 +23,7 @@ class FbxModelLoader(plugin.AssetLoader): Stores the imported asset in an empty named after the asset. """ - families = ["model", "rig"] + product_types = ["model", "rig"] representations = ["fbx"] label = "Load FBX" 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 f3d14ac018..ccd0ddb432 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 @@ -26,7 +26,7 @@ from ayon_core.hosts.blender.api import plugin class JsonLayoutLoader(plugin.AssetLoader): """Load layout published from Unreal.""" - families = ["layout"] + product_types = ["layout"] representations = ["json"] label = "Load Layout" 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 7cefbd5148..b3c0d705ce 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_look.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_look.py @@ -23,7 +23,7 @@ class BlendLookLoader(plugin.AssetLoader): contains the model. There is no further need to 'containerise' it. """ - families = ["look"] + product_types = ["look"] representations = ["json"] label = "Load Look" 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 faa2b7145d..014833db1b 100644 --- a/client/ayon_core/hosts/flame/plugins/load/load_clip.py +++ b/client/ayon_core/hosts/flame/plugins/load/load_clip.py @@ -17,7 +17,7 @@ class LoadClip(opfapi.ClipLoader): during conforming to project """ - families = ["render2d", "source", "plate", "render", "review"] + product_types = ["render2d", "source", "plate", "render", "review"] representations = ["*"] extensions = set( ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS) diff --git a/client/ayon_core/hosts/flame/plugins/load/load_clip_batch.py b/client/ayon_core/hosts/flame/plugins/load/load_clip_batch.py index 7eaaa308df..4884eb643f 100644 --- a/client/ayon_core/hosts/flame/plugins/load/load_clip_batch.py +++ b/client/ayon_core/hosts/flame/plugins/load/load_clip_batch.py @@ -16,7 +16,7 @@ class LoadClipBatch(opfapi.ClipLoader): during conforming to project """ - families = ["render2d", "source", "plate", "render", "review"] + product_types = ["render2d", "source", "plate", "render", "review"] representations = ["*"] extensions = set( ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS) diff --git a/client/ayon_core/hosts/fusion/plugins/load/actions.py b/client/ayon_core/hosts/fusion/plugins/load/actions.py index 2fac884b2e..38413a3e41 100644 --- a/client/ayon_core/hosts/fusion/plugins/load/actions.py +++ b/client/ayon_core/hosts/fusion/plugins/load/actions.py @@ -8,13 +8,14 @@ from ayon_core.pipeline import load class FusionSetFrameRangeLoader(load.LoaderPlugin): """Set frame range excluding pre- and post-handles""" - families = ["animation", - "camera", - "imagesequence", - "render", - "yeticache", - "pointcache", - "render"] + product_types = [ + "animation", + "camera", + "imagesequence", + "render", + "yeticache", + "pointcache", + "render"] representations = ["*"] extensions = {"*"} @@ -43,13 +44,14 @@ class FusionSetFrameRangeLoader(load.LoaderPlugin): class FusionSetFrameRangeWithHandlesLoader(load.LoaderPlugin): """Set frame range including pre- and post-handles""" - families = ["animation", - "camera", - "imagesequence", - "render", - "yeticache", - "pointcache", - "render"] + product_types = [ + "animation", + "camera", + "imagesequence", + "render", + "yeticache", + "pointcache", + "render"] representations = ["*"] label = "Set frame range (with handles)" 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 d78a8b0ba0..a81e09cc7f 100644 --- a/client/ayon_core/hosts/fusion/plugins/load/load_alembic.py +++ b/client/ayon_core/hosts/fusion/plugins/load/load_alembic.py @@ -12,7 +12,7 @@ from ayon_core.hosts.fusion.api import ( class FusionLoadAlembicMesh(load.LoaderPlugin): """Load Alembic mesh into Fusion""" - families = ["pointcache", "model"] + product_types = ["pointcache", "model"] representations = ["*"] extensions = {"abc"} 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 d2e1885b0c..33d9dd48f7 100644 --- a/client/ayon_core/hosts/fusion/plugins/load/load_fbx.py +++ b/client/ayon_core/hosts/fusion/plugins/load/load_fbx.py @@ -12,7 +12,7 @@ from ayon_core.hosts.fusion.api import ( class FusionLoadFBXMesh(load.LoaderPlugin): """Load FBX mesh into Fusion""" - families = ["*"] + product_types = ["*"] representations = ["*"] extensions = { "3ds", 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 dfd7e4231b..d2fe3b2bf0 100644 --- a/client/ayon_core/hosts/fusion/plugins/load/load_sequence.py +++ b/client/ayon_core/hosts/fusion/plugins/load/load_sequence.py @@ -129,7 +129,7 @@ def loader_shift(loader, frame, relative=True): class FusionLoadSequence(load.LoaderPlugin): """Load image sequence into Fusion""" - families = [ + product_types = [ "imagesequence", "review", "render", 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 a609af6197..4648ff2ceb 100644 --- a/client/ayon_core/hosts/fusion/plugins/load/load_usd.py +++ b/client/ayon_core/hosts/fusion/plugins/load/load_usd.py @@ -16,7 +16,7 @@ class FusionLoadUSD(load.LoaderPlugin): Support for USD was added since Fusion 18.5 """ - families = ["*"] + product_types = ["*"] representations = ["*"] extensions = {"usd", "usda", "usdz"} diff --git a/client/ayon_core/hosts/fusion/plugins/load/load_workfile.py b/client/ayon_core/hosts/fusion/plugins/load/load_workfile.py index d50fded502..b0ae358355 100644 --- a/client/ayon_core/hosts/fusion/plugins/load/load_workfile.py +++ b/client/ayon_core/hosts/fusion/plugins/load/load_workfile.py @@ -14,7 +14,7 @@ from ayon_core.hosts.fusion.api import ( class FusionLoadWorkfile(load.LoaderPlugin): """Load the content of a workfile into Fusion""" - families = ["workfile"] + product_types = ["workfile"] representations = ["*"] extensions = {"comp"} diff --git a/client/ayon_core/hosts/harmony/api/README.md b/client/ayon_core/hosts/harmony/api/README.md index b9c21e1882..4b9415357f 100644 --- a/client/ayon_core/hosts/harmony/api/README.md +++ b/client/ayon_core/hosts/harmony/api/README.md @@ -582,7 +582,7 @@ class ImageSequenceLoader(load.LoaderPlugin): """Load images Stores the imported asset in a container named after the asset. """ - families = ["mindbender.imagesequence"] + product_types = ["mindbender.imagesequence"] representations = ["*"] def load(self, context, name=None, namespace=None, data=None): 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 0f8aab6d57..bc1e64e48b 100644 --- a/client/ayon_core/hosts/harmony/plugins/load/load_audio.py +++ b/client/ayon_core/hosts/harmony/plugins/load/load_audio.py @@ -35,7 +35,7 @@ function %s(args) class ImportAudioLoader(load.LoaderPlugin): """Import audio.""" - families = ["shot", "audio"] + product_types = ["shot", "audio"] representations = ["wav"] label = "Import Audio" 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 72b26c826a..13b06a226c 100644 --- a/client/ayon_core/hosts/harmony/plugins/load/load_background.py +++ b/client/ayon_core/hosts/harmony/plugins/load/load_background.py @@ -233,7 +233,7 @@ class BackgroundLoader(load.LoaderPlugin): """Load images Stores the imported asset in a container named after the asset. """ - families = ["background"] + product_types = ["background"] representations = ["json"] def load(self, context, name=None, namespace=None, data=None): 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 0fbcd03c92..33f19cefcd 100644 --- a/client/ayon_core/hosts/harmony/plugins/load/load_imagesequence.py +++ b/client/ayon_core/hosts/harmony/plugins/load/load_imagesequence.py @@ -20,7 +20,14 @@ class ImageSequenceLoader(load.LoaderPlugin): Stores the imported asset in a container named after the asset. """ - families = ["shot", "render", "image", "plate", "reference", "review"] + product_types = [ + "shot", + "render", + "image", + "plate", + "reference", + "review", + ] representations = ["*"] extensions = {"jpeg", "png", "jpg"} 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 79ae2fb154..cc95b06bd6 100644 --- a/client/ayon_core/hosts/harmony/plugins/load/load_palette.py +++ b/client/ayon_core/hosts/harmony/plugins/load/load_palette.py @@ -11,7 +11,7 @@ import ayon_core.hosts.harmony.api as harmony class ImportPaletteLoader(load.LoaderPlugin): """Import palettes.""" - families = ["palette", "harmony.palette"] + product_types = ["palette", "harmony.palette"] representations = ["plt"] label = "Import Palette" 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 2d9af362eb..21b51bce48 100644 --- a/client/ayon_core/hosts/harmony/plugins/load/load_template.py +++ b/client/ayon_core/hosts/harmony/plugins/load/load_template.py @@ -23,7 +23,7 @@ class TemplateLoader(load.LoaderPlugin): """ - families = ["template", "workfile"] + product_types = ["template", "workfile"] representations = ["*"] label = "Load Template" icon = "gift" 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 59d66b7cfc..ec58d61ef0 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 @@ -13,7 +13,7 @@ import ayon_core.hosts.harmony.api as harmony class ImportTemplateLoader(load.LoaderPlugin): """Import templates.""" - families = ["harmony.template", "workfile"] + product_types = ["harmony.template", "workfile"] representations = ["*"] label = "Import Template" @@ -60,6 +60,6 @@ class ImportTemplateLoader(load.LoaderPlugin): class ImportWorkfileLoader(ImportTemplateLoader): """Import workfiles.""" - families = ["workfile"] + product_types = ["workfile"] representations = ["zip"] label = "Import Workfile" 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 c2ff907650..58da259429 100644 --- a/client/ayon_core/hosts/hiero/plugins/load/load_clip.py +++ b/client/ayon_core/hosts/hiero/plugins/load/load_clip.py @@ -15,7 +15,7 @@ class LoadClip(phiero.SequenceLoader): during conforming to project """ - families = ["render2d", "source", "plate", "render", "review"] + product_types = ["render2d", "source", "plate", "render", "review"] representations = ["*"] extensions = set( ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS) @@ -59,13 +59,7 @@ class LoadClip(phiero.SequenceLoader): if option == "representations": continue - if option == "product_types": - # TODO remove the key conversion when loaders can filter by - # product types - # convert 'product_types' to 'families' - option = "families" - - elif option == "clip_name_template": + if option == "clip_name_template": # TODO remove the formatting replacement value = ( value 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 521a7c4494..b1ad48af75 100644 --- a/client/ayon_core/hosts/hiero/plugins/load/load_effects.py +++ b/client/ayon_core/hosts/hiero/plugins/load/load_effects.py @@ -14,7 +14,7 @@ from ayon_core.lib import Logger class LoadEffects(load.LoaderPlugin): """Loading colorspace soft effect exported from nukestudio""" - families = ["effect"] + product_types = ["effect"] representations = ["*"] extension = {"json"} diff --git a/client/ayon_core/hosts/houdini/plugins/load/actions.py b/client/ayon_core/hosts/houdini/plugins/load/actions.py index 049869be25..0b5665adb4 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/actions.py +++ b/client/ayon_core/hosts/houdini/plugins/load/actions.py @@ -8,7 +8,7 @@ from ayon_core.pipeline import load class SetFrameRangeLoader(load.LoaderPlugin): """Set frame range excluding pre- and post-handles""" - families = [ + product_types = [ "animation", "camera", "pointcache", @@ -45,7 +45,7 @@ class SetFrameRangeLoader(load.LoaderPlugin): class SetFrameRangeWithHandlesLoader(load.LoaderPlugin): """Set frame range including pre- and post-handles""" - families = [ + product_types = [ "animation", "camera", "pointcache", 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 5235923b4c..681fecaba9 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_alembic.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_alembic.py @@ -9,7 +9,7 @@ from ayon_core.hosts.houdini.api import pipeline class AbcLoader(load.LoaderPlugin): """Load Alembic""" - families = ["model", "animation", "pointcache", "gpuCache"] + product_types = ["model", "animation", "pointcache", "gpuCache"] label = "Load Alembic" representations = ["abc"] order = -10 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 6585df3f17..3ee0a936ca 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 @@ -9,7 +9,7 @@ from ayon_core.hosts.houdini.api import pipeline class AbcArchiveLoader(load.LoaderPlugin): """Load Alembic as full geometry network hierarchy """ - families = ["model", "animation", "pointcache", "gpuCache"] + product_types = ["model", "animation", "pointcache", "gpuCache"] label = "Load Alembic as Archive" representations = ["abc"] order = -5 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 628b5b2f34..9c5c6e18d2 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_ass.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_ass.py @@ -11,7 +11,7 @@ from ayon_core.hosts.houdini.api import pipeline class AssLoader(load.LoaderPlugin): """Load .ass with Arnold Procedural""" - families = ["ass"] + product_types = ["ass"] label = "Load Arnold Procedural" representations = ["ass"] order = -10 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 f02067db75..4e3fd36b5f 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_bgeo.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_bgeo.py @@ -13,7 +13,7 @@ class BgeoLoader(load.LoaderPlugin): """Load bgeo files to Houdini.""" label = "Load bgeo" - families = ["model", "pointcache", "bgeo"] + product_types = ["model", "pointcache", "bgeo"] representations = [ "bgeo", "bgeosc", "bgeogz", "bgeo.sc", "bgeo.gz", "bgeo.lzma", "bgeo.bz2"] 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 50fc7f4eb6..2ab4e314b5 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_camera.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_camera.py @@ -87,7 +87,7 @@ def transfer_non_default_values(src, dest, ignore=None): class CameraLoader(load.LoaderPlugin): """Load camera from an Alembic file""" - families = ["camera"] + product_types = ["camera"] label = "Load Camera (abc)" representations = ["abc"] order = -10 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 2ebbed5e12..9b801e69eb 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_fbx.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_fbx.py @@ -16,7 +16,7 @@ class FbxLoader(load.LoaderPlugin): order = -10 - families = ["*"] + product_types = ["*"] representations = ["*"] extensions = {"fbx"} 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 07949fd177..b4d38665c1 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_hda.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_hda.py @@ -10,7 +10,7 @@ from ayon_core.hosts.houdini.api import pipeline class HdaLoader(load.LoaderPlugin): """Load Houdini Digital Asset file.""" - families = ["hda"] + product_types = ["hda"] label = "Load Hda" representations = ["hda"] order = -10 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 cc9dcc30c8..148d174652 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_image.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_image.py @@ -44,7 +44,7 @@ def get_image_avalon_container(): class ImageLoader(load.LoaderPlugin): """Load images into COP2""" - families = ["imagesequence"] + product_types = ["imagesequence"] label = "Load Image (COP2)" representations = ["*"] order = -10 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 86c7ae0272..dc353ac1b5 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 @@ -13,7 +13,7 @@ import hou class RedshiftProxyLoader(load.LoaderPlugin): """Load Redshift Proxy""" - families = ["redshiftproxy"] + product_types = ["redshiftproxy"] label = "Load Redshift Proxy" representations = ["rs"] order = -10 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 6ee21f87ec..6169c40f92 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 @@ -9,7 +9,7 @@ from ayon_core.hosts.houdini.api import lib class USDSublayerLoader(load.LoaderPlugin): """Sublayer USD file in Solaris""" - families = [ + product_types = [ "usd", "usdCamera", ] 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 d0421083c6..5b58024c80 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 @@ -9,7 +9,7 @@ from ayon_core.hosts.houdini.api import lib class USDReferenceLoader(load.LoaderPlugin): """Reference USD file in Solaris""" - families = [ + product_types = [ "usd", "usdCamera", ] 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 7b2803ab5d..79797f6b66 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_vdb.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_vdb.py @@ -11,7 +11,7 @@ from ayon_core.hosts.houdini.api import pipeline class VdbLoader(load.LoaderPlugin): """Load VDB""" - families = ["vdbcache"] + product_types = ["vdbcache"] label = "Load VDB" representations = ["vdb"] order = -10 diff --git a/client/ayon_core/hosts/houdini/plugins/load/show_usdview.py b/client/ayon_core/hosts/houdini/plugins/load/show_usdview.py index 2f86f23b68..8b668d004f 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/show_usdview.py +++ b/client/ayon_core/hosts/houdini/plugins/load/show_usdview.py @@ -11,7 +11,7 @@ class ShowInUsdview(load.LoaderPlugin): label = "Show in usdview" representations = ["*"] - families = ["*"] + product_types = ["*"] extensions = {"usd", "usda", "usdlc", "usdnc", "abc"} order = 15 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 664904eb4e..e82c6061d9 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 @@ -18,7 +18,7 @@ from ayon_core.pipeline import get_representation_path, load class FbxLoader(load.LoaderPlugin): """Fbx Loader.""" - families = ["camera"] + product_types = ["camera"] representations = ["fbx"] order = -9 icon = "code-fork" 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 0090b2256a..7e6a126426 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 @@ -72,9 +72,10 @@ class MaterialDupOptionsWindow(QtWidgets.QDialog): class MaxSceneLoader(load.LoaderPlugin): """Max Scene Loader.""" - families = ["camera", - "maxScene", - "model"] + product_types = [ + "camera", + "maxScene", + "model"] representations = ["max"] order = -8 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 0c39c1ba0d..4090692afc 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_model.py +++ b/client/ayon_core/hosts/max/plugins/load/load_model.py @@ -14,7 +14,7 @@ from ayon_core.hosts.max.api.lib import ( class ModelAbcLoader(load.LoaderPlugin): """Loading model with the Alembic loader.""" - families = ["model"] + product_types = ["model"] label = "Load Model with Alembic" representations = ["abc"] order = -10 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 7f5f1255ec..1ab59c6cc8 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 @@ -17,7 +17,7 @@ from ayon_core.hosts.max.api.lib import maintained_selection class FbxModelLoader(load.LoaderPlugin): """Fbx Model Loader.""" - families = ["model"] + product_types = ["model"] representations = ["fbx"] order = -9 icon = "code-fork" 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 4b8d260921..120c483a7d 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 @@ -20,7 +20,7 @@ from ayon_core.pipeline import get_representation_path, load class ObjLoader(load.LoaderPlugin): """Obj Loader.""" - families = ["model"] + product_types = ["model"] representations = ["obj"] order = -9 icon = "code-fork" 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 6bcff2b6a5..9cc2375cf2 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 @@ -22,7 +22,7 @@ from ayon_core.pipeline import get_representation_path, load class ModelUSDLoader(load.LoaderPlugin): """Loading model with the USD loader.""" - families = ["model"] + product_types = ["model"] label = "Load Model(USD)" representations = ["usda"] order = -10 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 613d240447..0a1d6cbd85 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_pointcache.py +++ b/client/ayon_core/hosts/max/plugins/load/load_pointcache.py @@ -18,7 +18,7 @@ from ayon_core.hosts.max.api.pipeline import ( class AbcLoader(load.LoaderPlugin): """Alembic loader.""" - families = ["camera", "animation", "pointcache"] + product_types = ["camera", "animation", "pointcache"] label = "Load Alembic" representations = ["abc"] order = -10 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 7cf7d162c1..7e27afd7e8 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 @@ -21,7 +21,7 @@ from pymxs import runtime as rt class OxAbcLoader(load.LoaderPlugin): """Ornatrix Alembic loader.""" - families = ["camera", "animation", "pointcache"] + product_types = ["camera", "animation", "pointcache"] label = "Load Alembic with Ornatrix" representations = ["abc"] order = -10 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 6b9e5d6fb1..6246979de1 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_pointcloud.py +++ b/client/ayon_core/hosts/max/plugins/load/load_pointcloud.py @@ -17,7 +17,7 @@ from ayon_core.pipeline import get_representation_path, load class PointCloudLoader(load.LoaderPlugin): """Point Cloud Loader.""" - families = ["pointcloud"] + product_types = ["pointcloud"] representations = ["prt"] order = -8 icon = "code-fork" 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 40c7701797..34732a9ef0 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 @@ -23,7 +23,7 @@ class RedshiftProxyLoader(load.LoaderPlugin): """Load rs files with Redshift Proxy""" label = "Load Redshift Proxy" - families = ["redshiftproxy"] + product_types = ["redshiftproxy"] representations = ["rs"] order = -9 icon = "code-fork" 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 24cb762b2d..3460b913ed 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_tycache.py +++ b/client/ayon_core/hosts/max/plugins/load/load_tycache.py @@ -16,7 +16,7 @@ from ayon_core.pipeline import get_representation_path, load class TyCacheLoader(load.LoaderPlugin): """TyCache Loader.""" - families = ["tycache"] + product_types = ["tycache"] representations = ["tyc"] order = -8 icon = "code-fork" diff --git a/client/ayon_core/hosts/max/plugins/publish/collect_frame_range.py b/client/ayon_core/hosts/max/plugins/publish/collect_frame_range.py index 6fc8de90d1..a70665ce3c 100644 --- a/client/ayon_core/hosts/max/plugins/publish/collect_frame_range.py +++ b/client/ayon_core/hosts/max/plugins/publish/collect_frame_range.py @@ -9,9 +9,14 @@ class CollectFrameRange(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.01 label = "Collect Frame Range" hosts = ['max'] - families = ["camera", "maxrender", - "pointcache", "pointcloud", - "review", "redshiftproxy"] + product_types = [ + "camera", + "maxrender", + "pointcache", + "pointcloud", + "review", + "redshiftproxy" + ] def process(self, instance): if instance.data["productType"] == "maxrender": diff --git a/client/ayon_core/hosts/maya/plugins/load/_load_animation.py b/client/ayon_core/hosts/maya/plugins/load/_load_animation.py index e6dc1e520a..b9033bf2e4 100644 --- a/client/ayon_core/hosts/maya/plugins/load/_load_animation.py +++ b/client/ayon_core/hosts/maya/plugins/load/_load_animation.py @@ -46,9 +46,10 @@ def _process_reference(file_url, name, namespace, options): class AbcLoader(ayon_core.hosts.maya.api.plugin.ReferenceLoader): """Loader to reference an Alembic file""" - families = ["animation", - "camera", - "pointcache"] + product_types = [ + "animation", + "camera", + "pointcache"] representations = ["abc"] label = "Reference animation" @@ -75,8 +76,9 @@ class AbcLoader(ayon_core.hosts.maya.api.plugin.ReferenceLoader): class FbxLoader(ayon_core.hosts.maya.api.plugin.ReferenceLoader): """Loader to reference an Fbx files""" - families = ["animation", - "camera"] + product_types = [ + "animation", + "camera"] representations = ["fbx"] label = "Reference animation" diff --git a/client/ayon_core/hosts/maya/plugins/load/actions.py b/client/ayon_core/hosts/maya/plugins/load/actions.py index 5f4095eeec..0cf031d012 100644 --- a/client/ayon_core/hosts/maya/plugins/load/actions.py +++ b/client/ayon_core/hosts/maya/plugins/load/actions.py @@ -13,10 +13,11 @@ import ayon_core.hosts.maya.api.plugin class SetFrameRangeLoader(load.LoaderPlugin): """Set frame range excluding pre- and post-handles""" - families = ["animation", - "camera", - "proxyAbc", - "pointcache"] + product_types = [ + "animation", + "camera", + "proxyAbc", + "pointcache"] representations = ["abc"] label = "Set frame range" @@ -46,10 +47,11 @@ class SetFrameRangeLoader(load.LoaderPlugin): class SetFrameRangeWithHandlesLoader(load.LoaderPlugin): """Set frame range including pre- and post-handles""" - families = ["animation", - "camera", - "proxyAbc", - "pointcache"] + product_types = [ + "animation", + "camera", + "proxyAbc", + "pointcache"] representations = ["abc"] label = "Set frame range (with handles)" @@ -91,7 +93,7 @@ class ImportMayaLoader(ayon_core.hosts.maya.api.plugin.Loader): """ representations = ["ma", "mb", "obj"] - families = [ + product_types = [ "model", "pointcache", "proxyAbc", 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 0219a72515..04445e68cf 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 @@ -29,7 +29,9 @@ def is_sequence(files): class ArnoldStandinLoader(load.LoaderPlugin): """Load as Arnold standin""" - families = ["ass", "animation", "model", "proxyAbc", "pointcache", "usd"] + product_types = [ + "ass", "animation", "model", "proxyAbc", "pointcache", "usd" + ] representations = ["ass", "abc", "usda", "usdc", "usd"] label = "Load as Arnold standin" 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 7ab7fc4207..1db253822e 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_assembly.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_assembly.py @@ -12,7 +12,7 @@ from ayon_core.hosts.maya.api import setdress class AssemblyLoader(load.LoaderPlugin): - families = ["assembly"] + product_types = ["assembly"] representations = ["json"] label = "Load Set Dress" 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 ee37a7ad8c..2720a5ddfa 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_audio.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_audio.py @@ -11,7 +11,7 @@ from ayon_core.hosts.maya.api.lib import unique_namespace, get_container_members class AudioLoader(load.LoaderPlugin): """Specific loader of audio.""" - families = ["audio"] + product_types = ["audio"] label = "Load audio" representations = ["wav"] icon = "volume-up" 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 080d20b0a6..ca0eadc677 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_gpucache.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_gpucache.py @@ -15,7 +15,7 @@ from ayon_core.hosts.maya.api.plugin import get_load_color_for_product_type class GpuCacheLoader(load.LoaderPlugin): """Load Alembic as gpuCache""" - families = ["model", "animation", "proxyAbc", "pointcache"] + product_types = ["model", "animation", "proxyAbc", "pointcache"] representations = ["abc", "gpu_cache"] label = "Load Gpu Cache" 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 d595aa2987..7db68365ca 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_image.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_image.py @@ -93,7 +93,7 @@ def create_stencil(): class FileNodeLoader(load.LoaderPlugin): """File node loader.""" - families = ["image", "plate", "render"] + product_types = ["image", "plate", "render"] label = "Load file node" representations = ["exr", "tif", "png", "jpg"] icon = "image" 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 71ae892150..788971fcf8 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 @@ -87,7 +87,7 @@ class CameraWindow(QtWidgets.QDialog): class ImagePlaneLoader(load.LoaderPlugin): """Specific loader of plate for image planes on selected camera.""" - families = ["image", "plate", "render"] + product_types = ["image", "plate", "render"] label = "Load imagePlane" representations = ["mov", "exr", "preview", "png", "jpg"] icon = "image" 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 9226c7b16f..a2203b6e9b 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_look.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_look.py @@ -17,7 +17,7 @@ from ayon_core.tools.utils import ScrollMessageBox class LookLoader(ayon_core.hosts.maya.api.plugin.ReferenceLoader): """Specific loader for lookdev""" - families = ["look"] + product_types = ["look"] representations = ["ma"] label = "Reference look" diff --git a/client/ayon_core/hosts/maya/plugins/load/load_matchmove.py b/client/ayon_core/hosts/maya/plugins/load/load_matchmove.py index 885d2dbae1..749369de87 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_matchmove.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_matchmove.py @@ -8,7 +8,7 @@ class MatchmoveLoader(load.LoaderPlugin): Supported script types are .py and .mel """ - families = ["matchmove"] + product_types = ["matchmove"] representations = ["py", "mel"] defaults = ["Camera", "Object", "Mocap"] 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 0a8adaf87f..885941ee59 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 @@ -16,7 +16,7 @@ from ayon_core.hosts.maya.api.pipeline import containerise class MayaUsdLoader(load.LoaderPlugin): """Read USD data in a Maya USD Proxy""" - families = ["model", "usd", "pointcache", "animation"] + product_types = ["model", "usd", "pointcache", "animation"] representations = ["usd", "usda", "usdc", "usdz", "abc"] label = "Load USD to Maya Proxy" 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 aab87fc546..9d78c63205 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 @@ -20,8 +20,14 @@ from ayon_core.hosts.maya.api.pipeline import containerise class MultiverseUsdLoader(load.LoaderPlugin): """Read USD data in a Multiverse Compound""" - families = ["model", "usd", "mvUsdComposition", "mvUsdOverride", - "pointcache", "animation"] + product_types = [ + "model", + "usd", + "mvUsdComposition", + "mvUsdOverride", + "pointcache", + "animation" + ] representations = ["usd", "usda", "usdc", "usdz", "abc"] label = "Load USD to Multiverse" 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 70069d3ae6..10cf4918a4 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 @@ -19,7 +19,7 @@ from ayon_core.hosts.maya.api.pipeline import containerise class MultiverseUsdOverLoader(load.LoaderPlugin): """Reference file""" - families = ["mvUsdOverride"] + product_types = ["mvUsdOverride"] representations = ["usda", "usd", "udsz"] label = "Load Usd Override into Compound" 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 a0ed7cd6e7..a6f0a0878e 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 @@ -22,7 +22,7 @@ from ayon_core.hosts.maya.api.plugin import get_load_color_for_product_type class RedshiftProxyLoader(load.LoaderPlugin): """Load Redshift proxy""" - families = ["redshiftproxy"] + product_types = ["redshiftproxy"] representations = ["rs"] label = "Import Redshift Proxy" 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 75f5cee5a5..a3914f2efe 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_reference.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_reference.py @@ -89,21 +89,23 @@ def preserve_modelpanel_cameras(container, log=None): class ReferenceLoader(plugin.ReferenceLoader): """Reference file""" - families = ["model", - "pointcache", - "proxyAbc", - "animation", - "mayaAscii", - "mayaScene", - "setdress", - "layout", - "camera", - "rig", - "camerarig", - "staticMesh", - "skeletalMesh", - "mvLook", - "matchmove"] + product_types = [ + "model", + "pointcache", + "proxyAbc", + "animation", + "mayaAscii", + "mayaScene", + "setdress", + "layout", + "camera", + "rig", + "camerarig", + "staticMesh", + "skeletalMesh", + "mvLook", + "matchmove" + ] representations = ["ma", "abc", "fbx", "mb"] @@ -270,7 +272,7 @@ class MayaUSDReferenceLoader(ReferenceLoader): """Reference USD file to native Maya nodes using MayaUSDImport reference""" label = "Reference Maya USD" - families = ["usd"] + product_types = ["usd"] representations = ["usd"] extensions = {"usd", "usda", "usdc"} 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 0f01a65539..905be00ae7 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_rendersetup.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_rendersetup.py @@ -24,7 +24,7 @@ import maya.app.renderSetup.model.renderSetup as renderSetup class RenderSetupLoader(load.LoaderPlugin): """Load json preset for RenderSetup overwriting current one.""" - families = ["rendersetup"] + product_types = ["rendersetup"] representations = ["json"] defaults = ['Main'] 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 05dac0e260..d8cf3774c8 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 @@ -12,7 +12,7 @@ from ayon_core.hosts.maya.api.plugin import get_load_color_for_product_type class LoadVDBtoArnold(load.LoaderPlugin): """Load OpenVDB for Arnold in aiVolume""" - families = ["vdbcache"] + product_types = ["vdbcache"] representations = ["vdb"] label = "Load VDB to Arnold" 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 b6cd513d9e..f5ed2dfdc2 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 @@ -18,7 +18,7 @@ class LoadVDBtoRedShift(load.LoaderPlugin): """ - families = ["vdbcache"] + product_types = ["vdbcache"] representations = ["vdb"] label = "Load VDB to RedShift" 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 8b5923bdbb..e5f1674ef3 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 @@ -77,7 +77,7 @@ def _fix_duplicate_vvg_callbacks(): class LoadVDBtoVRay(load.LoaderPlugin): """Load OpenVDB in a V-Ray Volume Grid""" - families = ["vdbcache"] + product_types = ["vdbcache"] representations = ["vdb"] label = "Load VDB to VRay" 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 fe1f8425d8..b9f144f5da 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_vrayproxy.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_vrayproxy.py @@ -27,7 +27,7 @@ from ayon_core.hosts.maya.api.plugin import get_load_color_for_product_type class VRayProxyLoader(load.LoaderPlugin): """Load VRay Proxy with Alembic or VrayMesh.""" - families = ["vrayproxy", "model", "pointcache", "animation"] + product_types = ["vrayproxy", "model", "pointcache", "animation"] representations = ["vrmesh", "abc"] label = "Import VRay Proxy" 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 3d21464edc..046a94775f 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_vrayscene.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_vrayscene.py @@ -17,7 +17,7 @@ from ayon_core.hosts.maya.api.plugin import get_load_color_for_product_type class VRaySceneLoader(load.LoaderPlugin): """Load Vray scene""" - families = ["vrayscene_layer"] + product_types = ["vrayscene_layer"] representations = ["vrscene"] label = "Import VRay Scene" 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 58a6d86292..5b7d630dc3 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_xgen.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_xgen.py @@ -20,7 +20,7 @@ from ayon_core.pipeline import get_representation_path class XgenLoader(ayon_core.hosts.maya.api.plugin.ReferenceLoader): """Load Xgen as reference""" - families = ["xgen"] + product_types = ["xgen"] representations = ["ma", "mb"] label = "Reference Xgen" 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 18a094c29d..0ae0f95580 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 @@ -36,7 +36,7 @@ def set_attribute(node, attr, value): class YetiCacheLoader(load.LoaderPlugin): """Load Yeti Cache with one or more Yeti nodes""" - families = ["yeticache", "yetiRig"] + product_types = ["yeticache", "yetiRig"] representations = ["fur"] label = "Load Yeti Cache" diff --git a/client/ayon_core/hosts/maya/plugins/load/load_yeti_rig.py b/client/ayon_core/hosts/maya/plugins/load/load_yeti_rig.py index 310c943198..609231900d 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_yeti_rig.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_yeti_rig.py @@ -7,7 +7,7 @@ from ayon_core.hosts.maya.api import lib class YetiRigLoader(plugin.ReferenceLoader): """This loader will load Yeti rig.""" - families = ["yetiRig"] + product_types = ["yetiRig"] representations = ["ma"] label = "Load Yeti Rig" diff --git a/client/ayon_core/hosts/maya/plugins/publish/collect_animation.py b/client/ayon_core/hosts/maya/plugins/publish/collect_animation.py index 2ab6511ece..1ba750a6e0 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/collect_animation.py +++ b/client/ayon_core/hosts/maya/plugins/publish/collect_animation.py @@ -16,7 +16,7 @@ class CollectAnimationOutputGeometry(pyblish.api.InstancePlugin): """ order = pyblish.api.CollectorOrder + 0.4 - families = ["animation"] + product_types = ["animation"] label = "Collect Animation Output Geometry" hosts = ["maya"] diff --git a/client/ayon_core/hosts/nuke/plugins/load/actions.py b/client/ayon_core/hosts/nuke/plugins/load/actions.py index 707a8a3c4d..a9086e2ebc 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/actions.py +++ b/client/ayon_core/hosts/nuke/plugins/load/actions.py @@ -12,11 +12,12 @@ log = Logger.get_logger(__name__) class SetFrameRangeLoader(load.LoaderPlugin): """Set frame range excluding pre- and post-handles""" - families = ["animation", - "camera", - "write", - "yeticache", - "pointcache"] + product_types = [ + "animation", + "camera", + "write", + "yeticache", + "pointcache"] representations = ["*"] extensions = {"*"} @@ -44,11 +45,12 @@ class SetFrameRangeLoader(load.LoaderPlugin): class SetFrameRangeWithHandlesLoader(load.LoaderPlugin): """Set frame range including pre- and post-handles""" - families = ["animation", - "camera", - "write", - "yeticache", - "pointcache"] + product_types = [ + "animation", + "camera", + "write", + "yeticache", + "pointcache"] representations = ["*"] label = "Set frame range (with handles)" 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 51fe7dac34..321d327fc5 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_backdrop.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_backdrop.py @@ -21,7 +21,7 @@ from ayon_core.hosts.nuke.api import containerise, update_container class LoadBackdropNodes(load.LoaderPlugin): """Loading Published Backdrop nodes (workfile, nukenodes)""" - families = ["workfile", "nukenodes"] + product_types = ["workfile", "nukenodes"] representations = ["*"] extensions = {"nk"} 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 bb7c8ea7c8..574b74e7a1 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 @@ -20,7 +20,7 @@ class AlembicCameraLoader(load.LoaderPlugin): This will load alembic camera into script. """ - families = ["camera"] + product_types = ["camera"] representations = ["*"] extensions = {"abc"} 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 e972b99b85..9094414080 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_clip.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_clip.py @@ -35,7 +35,7 @@ class LoadClip(plugin.NukeLoader): """ log = Logger.get_logger(__name__) - families = [ + product_types = [ "source", "plate", "render", 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 50ce0d1ad7..a0e935f480 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_effects.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_effects.py @@ -18,7 +18,7 @@ from ayon_core.hosts.nuke.api import ( class LoadEffects(load.LoaderPlugin): """Loading colorspace soft effect exported from nukestudio""" - families = ["effect"] + product_types = ["effect"] representations = ["*"] extensions = {"json"} 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 8c58241316..b5f6509fbe 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 @@ -19,7 +19,7 @@ from ayon_core.hosts.nuke.api import ( class LoadEffectsInputProcess(load.LoaderPlugin): """Loading colorspace soft effect exported from nukestudio""" - families = ["effect"] + product_types = ["effect"] representations = ["*"] extensions = {"json"} 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 5130b825c4..3113d7194c 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_gizmo.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_gizmo.py @@ -21,7 +21,7 @@ from ayon_core.hosts.nuke.api import ( class LoadGizmo(load.LoaderPlugin): """Loading nuke Gizmo""" - families = ["gizmo"] + product_types = ["gizmo"] representations = ["*"] extensions = {"nk"} 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 8aa73ca0e4..27e7c17756 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 @@ -23,7 +23,7 @@ from ayon_core.hosts.nuke.api import ( class LoadGizmoInputProcess(load.LoaderPlugin): """Loading colorspace soft effect exported from nukestudio""" - families = ["gizmo"] + product_types = ["gizmo"] representations = ["*"] extensions = {"nk"} 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 002592758f..c530d234f5 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_image.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_image.py @@ -23,7 +23,7 @@ from ayon_core.lib.transcoding import ( class LoadImage(load.LoaderPlugin): """Load still image into Nuke""" - families = [ + product_types = [ "render2d", "source", "plate", diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_matchmove.py b/client/ayon_core/hosts/nuke/plugins/load/load_matchmove.py index 412181f3d9..a524a6961d 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_matchmove.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_matchmove.py @@ -7,7 +7,7 @@ class MatchmoveLoader(load.LoaderPlugin): This will run matchmove script to create track in script. """ - families = ["matchmove"] + product_types = ["matchmove"] representations = ["*"] extensions = {"py"} 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 fcf1c2eda9..4ef2721915 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_model.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_model.py @@ -18,7 +18,7 @@ class AlembicModelLoader(load.LoaderPlugin): This will load alembic model or anim into script. """ - families = ["model", "pointcache", "animation"] + product_types = ["model", "pointcache", "animation"] representations = ["*"] extensions = {"abc"} 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 94af7c40e2..586fbc3209 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_ociolook.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_ociolook.py @@ -20,7 +20,7 @@ from ayon_core.hosts.nuke.api import ( class LoadOcioLookNodes(load.LoaderPlugin): """Loading Ocio look to the nuke.Node graph""" - families = ["ociolook"] + product_types = ["ociolook"] representations = ["*"] extensions = {"json"} 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 9fb168f322..36e1f09c0a 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 @@ -16,7 +16,7 @@ from ayon_core.hosts.nuke.api import ( class LinkAsGroup(load.LoaderPlugin): """Copy the published file to be pasted at the desired location""" - families = ["workfile", "nukenodes"] + product_types = ["workfile", "nukenodes"] representations = ["*"] extensions = {"nk"} diff --git a/client/ayon_core/hosts/nuke/plugins/publish/collect_backdrop.py b/client/ayon_core/hosts/nuke/plugins/publish/collect_backdrop.py index fc17de95b4..c0fdfa6fae 100644 --- a/client/ayon_core/hosts/nuke/plugins/publish/collect_backdrop.py +++ b/client/ayon_core/hosts/nuke/plugins/publish/collect_backdrop.py @@ -11,7 +11,7 @@ class CollectBackdrops(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.22 label = "Collect Backdrop" hosts = ["nuke"] - families = ["nukenodes"] + product_types = ["nukenodes"] def process(self, instance): self.log.debug(pformat(instance.data)) diff --git a/client/ayon_core/hosts/nuke/plugins/publish/collect_gizmo.py b/client/ayon_core/hosts/nuke/plugins/publish/collect_gizmo.py index fda1c7ac31..9d40b2db0f 100644 --- a/client/ayon_core/hosts/nuke/plugins/publish/collect_gizmo.py +++ b/client/ayon_core/hosts/nuke/plugins/publish/collect_gizmo.py @@ -9,7 +9,7 @@ class CollectGizmo(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.22 label = "Collect Gizmo (group)" hosts = ["nuke"] - families = ["gizmo"] + product_types = ["gizmo"] def process(self, instance): 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 42cc2dbcbf..0cfa9400f3 100644 --- a/client/ayon_core/hosts/photoshop/plugins/load/load_image.py +++ b/client/ayon_core/hosts/photoshop/plugins/load/load_image.py @@ -11,7 +11,7 @@ class ImageLoader(photoshop.PhotoshopLoader): Stores the imported asset in a container named after the asset. """ - families = ["image", "render"] + product_types = ["image", "render"] representations = ["*"] def load(self, context, name=None, namespace=None, data=None): 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 e20c9a5138..bfedc510e8 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 @@ -24,7 +24,7 @@ class ImageFromSequenceLoader(photoshop.PhotoshopLoader): difficult. """ - families = ["render"] + product_types = ["render"] representations = ["*"] options = [] 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 f1897ad18a..6ae34503d0 100644 --- a/client/ayon_core/hosts/photoshop/plugins/load/load_reference.py +++ b/client/ayon_core/hosts/photoshop/plugins/load/load_reference.py @@ -14,7 +14,7 @@ class ReferenceLoader(photoshop.PhotoshopLoader): "Cannot write to closing transport", possible refactor. """ - families = ["image", "render"] + product_types = ["image", "render"] representations = ["*"] def load(self, context, name=None, namespace=None, data=None): 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 26ee35a90d..848e6a4a54 100644 --- a/client/ayon_core/hosts/resolve/plugins/load/load_clip.py +++ b/client/ayon_core/hosts/resolve/plugins/load/load_clip.py @@ -18,7 +18,7 @@ class LoadClip(plugin.TimelineItemLoader): during conforming to project """ - families = ["render2d", "source", "plate", "render", "review"] + product_types = ["render2d", "source", "plate", "render", "review"] representations = ["*"] extensions = set( diff --git a/client/ayon_core/hosts/resolve/plugins/publish/extract_workfile.py b/client/ayon_core/hosts/resolve/plugins/publish/extract_workfile.py index 48ebdee7e3..0874685bb6 100644 --- a/client/ayon_core/hosts/resolve/plugins/publish/extract_workfile.py +++ b/client/ayon_core/hosts/resolve/plugins/publish/extract_workfile.py @@ -12,7 +12,7 @@ class ExtractWorkfile(publish.Extractor): label = "Extract Workfile" order = pyblish.api.ExtractorOrder - families = ["workfile"] + product_types = ["workfile"] hosts = ["resolve"] def process(self, instance): 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 fb2f1db726..cf95f4506c 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -17,7 +17,7 @@ import qargparse class SubstanceLoadProjectMesh(load.LoaderPlugin): """Load mesh for project""" - families = ["*"] + product_types = ["*"] representations = ["abc", "fbx", "obj", "gltf"] label = "Load mesh" diff --git a/client/ayon_core/hosts/tvpaint/plugins/load/load_image.py b/client/ayon_core/hosts/tvpaint/plugins/load/load_image.py index 21bbe56a54..3576dcea15 100644 --- a/client/ayon_core/hosts/tvpaint/plugins/load/load_image.py +++ b/client/ayon_core/hosts/tvpaint/plugins/load/load_image.py @@ -6,7 +6,7 @@ from ayon_core.hosts.tvpaint.api.lib import execute_george_through_file class ImportImage(plugin.Loader): """Load image or image sequence to TVPaint as new layer.""" - families = ["render", "image", "background", "plate", "review"] + product_types = ["render", "image", "background", "plate", "review"] representations = ["*"] label = "Import Image" 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 2820b883ed..e7539583be 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 @@ -20,7 +20,7 @@ from ayon_core.hosts.tvpaint.api.pipeline import ( class LoadImage(plugin.Loader): """Load image or image sequence to TVPaint as new layer.""" - families = ["render", "image", "background", "plate", "review"] + product_types = ["render", "image", "background", "plate", "review"] representations = ["*"] label = "Load Image" diff --git a/client/ayon_core/hosts/tvpaint/plugins/load/load_sound.py b/client/ayon_core/hosts/tvpaint/plugins/load/load_sound.py index 86f3e6857f..d8f22b3008 100644 --- a/client/ayon_core/hosts/tvpaint/plugins/load/load_sound.py +++ b/client/ayon_core/hosts/tvpaint/plugins/load/load_sound.py @@ -22,7 +22,7 @@ class ImportSound(plugin.Loader): file contain any audio. """ - families = ["audio", "review", "plate"] + product_types = ["audio", "review", "plate"] representations = ["*"] label = "Import Sound" diff --git a/client/ayon_core/hosts/tvpaint/plugins/load/load_workfile.py b/client/ayon_core/hosts/tvpaint/plugins/load/load_workfile.py index 4bb34089bd..3d1941d266 100644 --- a/client/ayon_core/hosts/tvpaint/plugins/load/load_workfile.py +++ b/client/ayon_core/hosts/tvpaint/plugins/load/load_workfile.py @@ -24,7 +24,7 @@ from ayon_core.pipeline.version_start import get_versioning_start class LoadWorkfile(plugin.Loader): """Load workfile.""" - families = ["workfile"] + product_types = ["workfile"] representations = ["tvpp"] label = "Load Workfile" 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 9b8d22fb5e..fbf19832b4 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 @@ -14,7 +14,7 @@ import unreal # noqa class AnimationAlembicLoader(plugin.Loader): """Load Unreal SkeletalMesh from Alembic""" - families = ["animation"] + product_types = ["animation"] label = "Import Alembic Animation" representations = ["abc"] icon = "cube" 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 b2c066c871..3d0a260e4e 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_animation.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_animation.py @@ -20,7 +20,7 @@ from ayon_core.hosts.unreal.api import pipeline as unreal_pipeline class AnimationFBXLoader(plugin.Loader): """Load Unreal SkeletalMesh from FBX.""" - families = ["animation"] + product_types = ["animation"] label = "Import FBX Animation" representations = ["fbx"] icon = "cube" 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 ed159d31bd..b9b67f5f98 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_camera.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_camera.py @@ -28,7 +28,7 @@ from ayon_core.hosts.unreal.api.pipeline import ( class CameraLoader(plugin.Loader): """Load Unreal StaticMesh from FBX""" - families = ["camera"] + product_types = ["camera"] label = "Load Camera" representations = ["fbx"] icon = "cube" 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 d0c936ed06..f1cedc575b 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 @@ -19,7 +19,7 @@ import unreal # noqa class PointCacheAlembicLoader(plugin.Loader): """Load Point Cache from Alembic""" - families = ["model", "pointcache"] + product_types = ["model", "pointcache"] label = "Import Alembic Point Cache" representations = ["abc"] icon = "cube" 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 beb94ada7b..dcdd51480a 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_layout.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_layout.py @@ -40,7 +40,7 @@ from ayon_core.hosts.unreal.api.pipeline import ( class LayoutLoader(plugin.Loader): """Load Layout from a JSON file""" - families = ["layout"] + product_types = ["layout"] representations = ["json"] label = "Load Layout" 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 144df10fe0..a50019773f 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 @@ -21,7 +21,7 @@ class ExistingLayoutLoader(plugin.Loader): Load Layout for an existing scene, and match the existing assets. """ - families = ["layout"] + product_types = ["layout"] representations = ["json"] label = "Load Layout on Existing Scene" 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 4d9c0554d8..ba26bbac59 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 @@ -18,7 +18,7 @@ import unreal # noqa class SkeletalMeshAlembicLoader(plugin.Loader): """Load Unreal SkeletalMesh from Alembic""" - families = ["pointcache", "skeletalMesh"] + product_types = ["pointcache", "skeletalMesh"] label = "Import Alembic Skeletal Mesh" representations = ["abc"] icon = "cube" 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 990ad4b977..7fc0420583 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 @@ -18,7 +18,7 @@ import unreal # noqa class SkeletalMeshFBXLoader(plugin.Loader): """Load Unreal SkeletalMesh from FBX.""" - families = ["rig", "skeletalMesh"] + product_types = ["rig", "skeletalMesh"] label = "Import FBX Skeletal Mesh" representations = ["fbx"] icon = "cube" 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 5e4a28a031..9301aacf78 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 @@ -18,7 +18,7 @@ import unreal # noqa class StaticMeshAlembicLoader(plugin.Loader): """Load Unreal StaticMesh from Alembic""" - families = ["model", "staticMesh"] + product_types = ["model", "staticMesh"] label = "Import Alembic Static Mesh" representations = ["abc"] icon = "cube" 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 1db2dcf676..a7df3d1045 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 @@ -18,7 +18,7 @@ import unreal # noqa class StaticMeshFBXLoader(plugin.Loader): """Load Unreal StaticMesh from FBX.""" - families = ["model", "staticMesh"] + product_types = ["model", "staticMesh"] label = "Import FBX Static Mesh" representations = ["fbx"] icon = "cube" 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 e0b1a69aac..b8ecd40c32 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_uasset.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_uasset.py @@ -15,7 +15,7 @@ import unreal # noqa class UAssetLoader(plugin.Loader): """Load UAsset.""" - families = ["uasset"] + product_types = ["uasset"] label = "Load UAsset" representations = ["uasset"] icon = "cube" @@ -164,7 +164,7 @@ class UAssetLoader(plugin.Loader): class UMapLoader(UAssetLoader): """Load Level.""" - families = ["uasset"] + product_types = ["uasset"] label = "Load Level" representations = ["umap"] 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 7a1767e52a..a3c11544f0 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_yeticache.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_yeticache.py @@ -15,7 +15,7 @@ import unreal # noqa class YetiLoader(plugin.Loader): """Load Yeti Cache""" - families = ["yeticacheUE"] + product_types = ["yeticacheUE"] label = "Import Yeti" representations = ["abc"] icon = "pagelines" diff --git a/client/ayon_core/plugins/load/copy_file.py b/client/ayon_core/plugins/load/copy_file.py index 0da22826f0..b6fe10e170 100644 --- a/client/ayon_core/plugins/load/copy_file.py +++ b/client/ayon_core/plugins/load/copy_file.py @@ -6,7 +6,7 @@ class CopyFile(load.LoaderPlugin): """Copy the published file to be pasted at the desired location""" representations = ["*"] - families = ["*"] + product_types = ["*"] label = "Copy File" order = 10 diff --git a/client/ayon_core/plugins/load/copy_file_path.py b/client/ayon_core/plugins/load/copy_file_path.py index c3478c32f3..3b0e0fc291 100644 --- a/client/ayon_core/plugins/load/copy_file_path.py +++ b/client/ayon_core/plugins/load/copy_file_path.py @@ -6,7 +6,7 @@ from ayon_core.pipeline import load class CopyFilePath(load.LoaderPlugin): """Copy published file path to clipboard""" representations = ["*"] - families = ["*"] + product_types = ["*"] label = "Copy File Path" order = 20 diff --git a/client/ayon_core/plugins/load/delete_old_versions.py b/client/ayon_core/plugins/load/delete_old_versions.py index 8fa0c2edb6..9124278696 100644 --- a/client/ayon_core/plugins/load/delete_old_versions.py +++ b/client/ayon_core/plugins/load/delete_old_versions.py @@ -27,7 +27,7 @@ # sequence_splitter = "__sequence_splitter__" # # representations = ["*"] -# families = ["*"] +# product_types = ["*"] # tool_names = ["library_loader"] # # label = "Delete Old Versions" diff --git a/client/ayon_core/plugins/load/delivery.py b/client/ayon_core/plugins/load/delivery.py index 453bdfb87a..3529458e70 100644 --- a/client/ayon_core/plugins/load/delivery.py +++ b/client/ayon_core/plugins/load/delivery.py @@ -29,7 +29,7 @@ class Delivery(load.ProductLoaderPlugin): sequence_splitter = "__sequence_splitter__" representations = ["*"] - families = ["*"] + product_types = ["*"] tool_names = ["library_loader"] label = "Deliver Versions" diff --git a/client/ayon_core/plugins/load/open_djv.py b/client/ayon_core/plugins/load/open_djv.py index 70352c2435..efe19161a0 100644 --- a/client/ayon_core/plugins/load/open_djv.py +++ b/client/ayon_core/plugins/load/open_djv.py @@ -18,7 +18,7 @@ class OpenInDJV(load.LoaderPlugin): """Open Image Sequence with system default""" djv_list = existing_djv_path() - families = ["*"] if djv_list else [] + product_types = ["*"] if djv_list else [] representations = ["*"] extensions = { "cin", "dpx", "avi", "dv", "gif", "flv", "mkv", "mov", "mpg", "mpeg", diff --git a/client/ayon_core/plugins/load/open_file.py b/client/ayon_core/plugins/load/open_file.py index 5ae5959102..b26b261f10 100644 --- a/client/ayon_core/plugins/load/open_file.py +++ b/client/ayon_core/plugins/load/open_file.py @@ -18,7 +18,7 @@ def open(filepath): class OpenFile(load.LoaderPlugin): """Open Image Sequence or Video with system default""" - families = ["render2d"] + product_types = ["render2d"] representations = ["*"] label = "Open" diff --git a/client/ayon_core/plugins/load/push_to_library.py b/client/ayon_core/plugins/load/push_to_library.py index a191ee88f0..cd3a5e449d 100644 --- a/client/ayon_core/plugins/load/push_to_library.py +++ b/client/ayon_core/plugins/load/push_to_library.py @@ -12,7 +12,7 @@ class PushToLibraryProject(load.ProductLoaderPlugin): is_multiple_contexts_compatible = True representations = ["*"] - families = ["*"] + product_types = ["*"] label = "Push to Library project" order = 35 From 53981f3fe1f15ef09f0f19428df1d106380f8e63 Mon Sep 17 00:00:00 2001 From: moonyuet Date: Mon, 18 Mar 2024 14:05:34 +0100 Subject: [PATCH 043/150] report detail filename in debug message --- .../hosts/max/plugins/publish/validate_renderpasses.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_renderpasses.py b/client/ayon_core/hosts/max/plugins/publish/validate_renderpasses.py index 0fbdb2940e..ba948747b9 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_renderpasses.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_renderpasses.py @@ -121,7 +121,7 @@ class ValidateRenderPasses(OptionalPyblishPluginMixin, else: cls.log.debug( "Skipping render element validation " - f"for renderer : {renderer}") + f"for renderer: {renderer}") return invalid @classmethod @@ -146,7 +146,7 @@ class ValidateRenderPasses(OptionalPyblishPluginMixin, if not file_name.rstrip(".").endswith(renderpass): cls.log.error( f"Filename for {renderpass} should " - f"end with {renderpass}" + f"end with {renderpass}: {file_name}" ) invalid.append((f"Invalid {renderpass}", os.path.basename(file_name))) From 576817b8eaa832da111cf2326dabf1460191b80a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 18 Mar 2024 14:45:54 +0100 Subject: [PATCH 044/150] change 'product_types' type to set --- .../aftereffects/plugins/load/load_background.py | 2 +- .../hosts/aftereffects/plugins/load/load_file.py | 14 ++++++++------ .../blender/plugins/load/import_workfile.py | 4 ++-- .../hosts/blender/plugins/load/load_abc.py | 2 +- .../hosts/blender/plugins/load/load_action.py | 2 +- .../hosts/blender/plugins/load/load_animation.py | 2 +- .../hosts/blender/plugins/load/load_audio.py | 2 +- .../hosts/blender/plugins/load/load_blend.py | 2 +- .../blender/plugins/load/load_blendscene.py | 2 +- .../blender/plugins/load/load_camera_abc.py | 2 +- .../blender/plugins/load/load_camera_fbx.py | 2 +- .../hosts/blender/plugins/load/load_fbx.py | 2 +- .../blender/plugins/load/load_layout_json.py | 2 +- .../hosts/blender/plugins/load/load_look.py | 2 +- .../hosts/flame/plugins/load/load_clip.py | 2 +- .../hosts/flame/plugins/load/load_clip_batch.py | 2 +- .../hosts/fusion/plugins/load/actions.py | 10 ++++++---- .../hosts/fusion/plugins/load/load_alembic.py | 2 +- .../hosts/fusion/plugins/load/load_fbx.py | 2 +- .../hosts/fusion/plugins/load/load_sequence.py | 4 ++-- .../hosts/fusion/plugins/load/load_usd.py | 2 +- .../hosts/fusion/plugins/load/load_workfile.py | 2 +- client/ayon_core/hosts/harmony/api/README.md | 2 +- .../hosts/harmony/plugins/load/load_audio.py | 2 +- .../harmony/plugins/load/load_background.py | 2 +- .../harmony/plugins/load/load_imagesequence.py | 4 ++-- .../hosts/harmony/plugins/load/load_palette.py | 2 +- .../hosts/harmony/plugins/load/load_template.py | 2 +- .../plugins/load/load_template_workfile.py | 4 ++-- .../hosts/hiero/plugins/load/load_clip.py | 2 +- .../hosts/hiero/plugins/load/load_effects.py | 2 +- .../hosts/houdini/plugins/load/actions.py | 8 ++++---- .../hosts/houdini/plugins/load/load_alembic.py | 2 +- .../houdini/plugins/load/load_alembic_archive.py | 2 +- .../hosts/houdini/plugins/load/load_ass.py | 2 +- .../hosts/houdini/plugins/load/load_bgeo.py | 2 +- .../hosts/houdini/plugins/load/load_camera.py | 2 +- .../hosts/houdini/plugins/load/load_fbx.py | 2 +- .../hosts/houdini/plugins/load/load_hda.py | 2 +- .../hosts/houdini/plugins/load/load_image.py | 2 +- .../houdini/plugins/load/load_redshift_proxy.py | 2 +- .../hosts/houdini/plugins/load/load_usd_layer.py | 4 ++-- .../houdini/plugins/load/load_usd_reference.py | 4 ++-- .../hosts/houdini/plugins/load/load_vdb.py | 2 +- .../hosts/houdini/plugins/load/show_usdview.py | 2 +- .../hosts/max/plugins/load/load_camera_fbx.py | 2 +- .../hosts/max/plugins/load/load_max_scene.py | 5 +++-- .../hosts/max/plugins/load/load_model.py | 2 +- .../hosts/max/plugins/load/load_model_fbx.py | 2 +- .../hosts/max/plugins/load/load_model_obj.py | 2 +- .../hosts/max/plugins/load/load_model_usd.py | 2 +- .../hosts/max/plugins/load/load_pointcache.py | 2 +- .../max/plugins/load/load_pointcache_ornatrix.py | 2 +- .../hosts/max/plugins/load/load_pointcloud.py | 2 +- .../max/plugins/load/load_redshift_proxy.py | 2 +- .../hosts/max/plugins/load/load_tycache.py | 2 +- .../hosts/maya/plugins/load/_load_animation.py | 10 ++++++---- .../ayon_core/hosts/maya/plugins/load/actions.py | 16 +++++++++------- .../maya/plugins/load/load_arnold_standin.py | 4 ++-- .../hosts/maya/plugins/load/load_assembly.py | 2 +- .../hosts/maya/plugins/load/load_audio.py | 2 +- .../hosts/maya/plugins/load/load_gpucache.py | 2 +- .../hosts/maya/plugins/load/load_image.py | 2 +- .../hosts/maya/plugins/load/load_image_plane.py | 2 +- .../hosts/maya/plugins/load/load_look.py | 2 +- .../hosts/maya/plugins/load/load_matchmove.py | 2 +- .../hosts/maya/plugins/load/load_maya_usd.py | 2 +- .../maya/plugins/load/load_multiverse_usd.py | 6 +++--- .../plugins/load/load_multiverse_usd_over.py | 2 +- .../maya/plugins/load/load_redshift_proxy.py | 2 +- .../hosts/maya/plugins/load/load_reference.py | 8 ++++---- .../hosts/maya/plugins/load/load_rendersetup.py | 2 +- .../maya/plugins/load/load_vdb_to_arnold.py | 2 +- .../maya/plugins/load/load_vdb_to_redshift.py | 2 +- .../hosts/maya/plugins/load/load_vdb_to_vray.py | 2 +- .../hosts/maya/plugins/load/load_vrayproxy.py | 2 +- .../hosts/maya/plugins/load/load_vrayscene.py | 2 +- .../hosts/maya/plugins/load/load_xgen.py | 2 +- .../hosts/maya/plugins/load/load_yeti_cache.py | 2 +- .../hosts/maya/plugins/load/load_yeti_rig.py | 2 +- .../ayon_core/hosts/nuke/plugins/load/actions.py | 10 ++++++---- .../hosts/nuke/plugins/load/load_backdrop.py | 2 +- .../hosts/nuke/plugins/load/load_camera_abc.py | 2 +- .../hosts/nuke/plugins/load/load_clip.py | 6 +++--- .../hosts/nuke/plugins/load/load_effects.py | 2 +- .../hosts/nuke/plugins/load/load_effects_ip.py | 2 +- .../hosts/nuke/plugins/load/load_gizmo.py | 2 +- .../hosts/nuke/plugins/load/load_gizmo_ip.py | 2 +- .../hosts/nuke/plugins/load/load_image.py | 6 +++--- .../hosts/nuke/plugins/load/load_matchmove.py | 2 +- .../hosts/nuke/plugins/load/load_model.py | 2 +- .../hosts/nuke/plugins/load/load_ociolook.py | 2 +- .../nuke/plugins/load/load_script_precomp.py | 2 +- .../hosts/photoshop/plugins/load/load_image.py | 2 +- .../plugins/load/load_image_from_sequence.py | 2 +- .../photoshop/plugins/load/load_reference.py | 2 +- .../hosts/resolve/plugins/load/load_clip.py | 2 +- .../substancepainter/plugins/load/load_mesh.py | 2 +- .../hosts/tvpaint/plugins/load/load_image.py | 2 +- .../tvpaint/plugins/load/load_reference_image.py | 2 +- .../hosts/tvpaint/plugins/load/load_sound.py | 2 +- .../hosts/tvpaint/plugins/load/load_workfile.py | 2 +- .../plugins/load/load_alembic_animation.py | 2 +- .../hosts/unreal/plugins/load/load_animation.py | 2 +- .../hosts/unreal/plugins/load/load_camera.py | 2 +- .../plugins/load/load_geometrycache_abc.py | 2 +- .../hosts/unreal/plugins/load/load_layout.py | 2 +- .../unreal/plugins/load/load_layout_existing.py | 2 +- .../unreal/plugins/load/load_skeletalmesh_abc.py | 2 +- .../unreal/plugins/load/load_skeletalmesh_fbx.py | 2 +- .../unreal/plugins/load/load_staticmesh_abc.py | 2 +- .../unreal/plugins/load/load_staticmesh_fbx.py | 2 +- .../hosts/unreal/plugins/load/load_uasset.py | 4 ++-- .../hosts/unreal/plugins/load/load_yeticache.py | 2 +- client/ayon_core/pipeline/load/plugins.py | 2 +- client/ayon_core/plugins/load/copy_file.py | 2 +- client/ayon_core/plugins/load/copy_file_path.py | 2 +- .../plugins/load/delete_old_versions.py | 2 +- client/ayon_core/plugins/load/delivery.py | 2 +- client/ayon_core/plugins/load/open_djv.py | 2 +- client/ayon_core/plugins/load/open_file.py | 2 +- client/ayon_core/plugins/load/push_to_library.py | 2 +- 122 files changed, 174 insertions(+), 163 deletions(-) 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 9c43dc0e77..7c9bd2fcfd 100644 --- a/client/ayon_core/hosts/aftereffects/plugins/load/load_background.py +++ b/client/ayon_core/hosts/aftereffects/plugins/load/load_background.py @@ -20,7 +20,7 @@ class BackgroundLoader(api.AfterEffectsLoader): metadata """ label = "Load JSON Background" - product_types = ["background"] + product_types = {"background"} representations = ["json"] def load(self, context, name=None, namespace=None, data=None): 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 2d9ba1b677..79e791af7b 100644 --- a/client/ayon_core/hosts/aftereffects/plugins/load/load_file.py +++ b/client/ayon_core/hosts/aftereffects/plugins/load/load_file.py @@ -12,12 +12,14 @@ class FileLoader(api.AfterEffectsLoader): """ label = "Load file" - product_types = ["image", - "plate", - "render", - "prerender", - "review", - "audio"] + product_types = { + "image", + "plate", + "render", + "prerender", + "review", + "audio", + } representations = ["*"] def load(self, context, name=None, namespace=None, data=None): diff --git a/client/ayon_core/hosts/blender/plugins/load/import_workfile.py b/client/ayon_core/hosts/blender/plugins/load/import_workfile.py index f64437e753..3aa73a5143 100644 --- a/client/ayon_core/hosts/blender/plugins/load/import_workfile.py +++ b/client/ayon_core/hosts/blender/plugins/load/import_workfile.py @@ -44,7 +44,7 @@ class AppendBlendLoader(plugin.AssetLoader): """ representations = ["blend"] - product_types = ["workfile"] + product_types = {"workfile"} label = "Append Workfile" order = 9 @@ -69,7 +69,7 @@ class ImportBlendLoader(plugin.AssetLoader): """ representations = ["blend"] - product_types = ["workfile"] + product_types = {"workfile"} label = "Import Workfile" order = 9 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 a6e834e20a..938ae6106b 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_abc.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_abc.py @@ -26,7 +26,7 @@ class CacheModelLoader(plugin.AssetLoader): Note: At least for now it only supports Alembic files. """ - product_types = ["model", "pointcache", "animation"] + product_types = {"model", "pointcache", "animation"} representations = ["abc"] label = "Load Alembic" 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 446fa20fec..4161f8bff3 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_action.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_action.py @@ -24,7 +24,7 @@ class BlendActionLoader(plugin.AssetLoader): moment. """ - product_types = ["action"] + product_types = {"action"} representations = ["blend"] label = "Link Action" diff --git a/client/ayon_core/hosts/blender/plugins/load/load_animation.py b/client/ayon_core/hosts/blender/plugins/load/load_animation.py index 5da270f9e4..effb91c48c 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_animation.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_animation.py @@ -16,7 +16,7 @@ class BlendAnimationLoader(plugin.AssetLoader): moment. """ - product_types = ["animation"] + product_types = {"animation"} representations = ["blend"] label = "Link Animation" 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 6e72c58bc9..db83c4bca2 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_audio.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_audio.py @@ -20,7 +20,7 @@ from ayon_core.hosts.blender.api.pipeline import ( class AudioLoader(plugin.AssetLoader): """Load audio in Blender.""" - product_types = ["audio"] + product_types = {"audio"} representations = ["wav"] label = "Load Audio" 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 8cc246f46e..e84dddc88f 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_blend.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_blend.py @@ -20,7 +20,7 @@ from ayon_core.hosts.blender.api.pipeline import ( class BlendLoader(plugin.AssetLoader): """Load assets from a .blend file.""" - product_types = ["model", "rig", "layout", "camera"] + product_types = {"model", "rig", "layout", "camera"} representations = ["blend"] label = "Append Blend" 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 41964a906a..627941752f 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_blendscene.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_blendscene.py @@ -18,7 +18,7 @@ from ayon_core.hosts.blender.api.pipeline import ( class BlendSceneLoader(plugin.AssetLoader): """Load assets from a .blend file.""" - product_types = ["blendScene"] + product_types = {"blendScene"} representations = ["blend"] label = "Append Blend" 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 4c8bfba9a5..e677fc3e58 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 @@ -23,7 +23,7 @@ class AbcCameraLoader(plugin.AssetLoader): Stores the imported asset in an empty named after the asset. """ - product_types = ["camera"] + product_types = {"camera"} representations = ["abc"] label = "Load Camera (ABC)" 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 d14f145f81..14d61a6395 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 @@ -23,7 +23,7 @@ class FbxCameraLoader(plugin.AssetLoader): Stores the imported asset in an empty named after the asset. """ - product_types = ["camera"] + product_types = {"camera"} representations = ["fbx"] label = "Load Camera (FBX)" 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 c330dca160..0042482284 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_fbx.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_fbx.py @@ -23,7 +23,7 @@ class FbxModelLoader(plugin.AssetLoader): Stores the imported asset in an empty named after the asset. """ - product_types = ["model", "rig"] + product_types = {"model", "rig"} representations = ["fbx"] label = "Load FBX" 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 ccd0ddb432..7a3da1882e 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 @@ -26,7 +26,7 @@ from ayon_core.hosts.blender.api import plugin class JsonLayoutLoader(plugin.AssetLoader): """Load layout published from Unreal.""" - product_types = ["layout"] + product_types = {"layout"} representations = ["json"] label = "Load Layout" 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 b3c0d705ce..ce677a8471 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_look.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_look.py @@ -23,7 +23,7 @@ class BlendLookLoader(plugin.AssetLoader): contains the model. There is no further need to 'containerise' it. """ - product_types = ["look"] + product_types = {"look"} representations = ["json"] label = "Load Look" 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 014833db1b..f528caeb29 100644 --- a/client/ayon_core/hosts/flame/plugins/load/load_clip.py +++ b/client/ayon_core/hosts/flame/plugins/load/load_clip.py @@ -17,7 +17,7 @@ class LoadClip(opfapi.ClipLoader): during conforming to project """ - product_types = ["render2d", "source", "plate", "render", "review"] + product_types = {"render2d", "source", "plate", "render", "review"} representations = ["*"] extensions = set( ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS) diff --git a/client/ayon_core/hosts/flame/plugins/load/load_clip_batch.py b/client/ayon_core/hosts/flame/plugins/load/load_clip_batch.py index 4884eb643f..9bdd467d63 100644 --- a/client/ayon_core/hosts/flame/plugins/load/load_clip_batch.py +++ b/client/ayon_core/hosts/flame/plugins/load/load_clip_batch.py @@ -16,7 +16,7 @@ class LoadClipBatch(opfapi.ClipLoader): during conforming to project """ - product_types = ["render2d", "source", "plate", "render", "review"] + product_types = {"render2d", "source", "plate", "render", "review"} representations = ["*"] extensions = set( ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS) diff --git a/client/ayon_core/hosts/fusion/plugins/load/actions.py b/client/ayon_core/hosts/fusion/plugins/load/actions.py index 38413a3e41..9600479680 100644 --- a/client/ayon_core/hosts/fusion/plugins/load/actions.py +++ b/client/ayon_core/hosts/fusion/plugins/load/actions.py @@ -8,14 +8,15 @@ from ayon_core.pipeline import load class FusionSetFrameRangeLoader(load.LoaderPlugin): """Set frame range excluding pre- and post-handles""" - product_types = [ + product_types = { "animation", "camera", "imagesequence", "render", "yeticache", "pointcache", - "render"] + "render", + } representations = ["*"] extensions = {"*"} @@ -44,14 +45,15 @@ class FusionSetFrameRangeLoader(load.LoaderPlugin): class FusionSetFrameRangeWithHandlesLoader(load.LoaderPlugin): """Set frame range including pre- and post-handles""" - product_types = [ + product_types = { "animation", "camera", "imagesequence", "render", "yeticache", "pointcache", - "render"] + "render", + } representations = ["*"] label = "Set frame range (with handles)" 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 a81e09cc7f..ae2175964d 100644 --- a/client/ayon_core/hosts/fusion/plugins/load/load_alembic.py +++ b/client/ayon_core/hosts/fusion/plugins/load/load_alembic.py @@ -12,7 +12,7 @@ from ayon_core.hosts.fusion.api import ( class FusionLoadAlembicMesh(load.LoaderPlugin): """Load Alembic mesh into Fusion""" - product_types = ["pointcache", "model"] + product_types = {"pointcache", "model"} representations = ["*"] extensions = {"abc"} 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 33d9dd48f7..68b7cdacd1 100644 --- a/client/ayon_core/hosts/fusion/plugins/load/load_fbx.py +++ b/client/ayon_core/hosts/fusion/plugins/load/load_fbx.py @@ -12,7 +12,7 @@ from ayon_core.hosts.fusion.api import ( class FusionLoadFBXMesh(load.LoaderPlugin): """Load FBX mesh into Fusion""" - product_types = ["*"] + product_types = {"*"} representations = ["*"] extensions = { "3ds", 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 d2fe3b2bf0..f0a8233377 100644 --- a/client/ayon_core/hosts/fusion/plugins/load/load_sequence.py +++ b/client/ayon_core/hosts/fusion/plugins/load/load_sequence.py @@ -129,14 +129,14 @@ def loader_shift(loader, frame, relative=True): class FusionLoadSequence(load.LoaderPlugin): """Load image sequence into Fusion""" - product_types = [ + product_types = { "imagesequence", "review", "render", "plate", "image", "online", - ] + } representations = ["*"] extensions = set( ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS) 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 4648ff2ceb..2f8eeb4c66 100644 --- a/client/ayon_core/hosts/fusion/plugins/load/load_usd.py +++ b/client/ayon_core/hosts/fusion/plugins/load/load_usd.py @@ -16,7 +16,7 @@ class FusionLoadUSD(load.LoaderPlugin): Support for USD was added since Fusion 18.5 """ - product_types = ["*"] + product_types = {"*"} representations = ["*"] extensions = {"usd", "usda", "usdz"} diff --git a/client/ayon_core/hosts/fusion/plugins/load/load_workfile.py b/client/ayon_core/hosts/fusion/plugins/load/load_workfile.py index b0ae358355..fd2fa7c08b 100644 --- a/client/ayon_core/hosts/fusion/plugins/load/load_workfile.py +++ b/client/ayon_core/hosts/fusion/plugins/load/load_workfile.py @@ -14,7 +14,7 @@ from ayon_core.hosts.fusion.api import ( class FusionLoadWorkfile(load.LoaderPlugin): """Load the content of a workfile into Fusion""" - product_types = ["workfile"] + product_types = {"workfile"} representations = ["*"] extensions = {"comp"} diff --git a/client/ayon_core/hosts/harmony/api/README.md b/client/ayon_core/hosts/harmony/api/README.md index 4b9415357f..6d1e400476 100644 --- a/client/ayon_core/hosts/harmony/api/README.md +++ b/client/ayon_core/hosts/harmony/api/README.md @@ -582,7 +582,7 @@ class ImageSequenceLoader(load.LoaderPlugin): """Load images Stores the imported asset in a container named after the asset. """ - product_types = ["mindbender.imagesequence"] + product_types = {"mindbender.imagesequence"} representations = ["*"] def load(self, context, name=None, namespace=None, data=None): 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 bc1e64e48b..00f3ac77ec 100644 --- a/client/ayon_core/hosts/harmony/plugins/load/load_audio.py +++ b/client/ayon_core/hosts/harmony/plugins/load/load_audio.py @@ -35,7 +35,7 @@ function %s(args) class ImportAudioLoader(load.LoaderPlugin): """Import audio.""" - product_types = ["shot", "audio"] + product_types = {"shot", "audio"} representations = ["wav"] label = "Import Audio" 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 13b06a226c..74bc5a4fd8 100644 --- a/client/ayon_core/hosts/harmony/plugins/load/load_background.py +++ b/client/ayon_core/hosts/harmony/plugins/load/load_background.py @@ -233,7 +233,7 @@ class BackgroundLoader(load.LoaderPlugin): """Load images Stores the imported asset in a container named after the asset. """ - product_types = ["background"] + product_types = {"background"} representations = ["json"] def load(self, context, name=None, namespace=None, data=None): 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 33f19cefcd..bf4b87a03e 100644 --- a/client/ayon_core/hosts/harmony/plugins/load/load_imagesequence.py +++ b/client/ayon_core/hosts/harmony/plugins/load/load_imagesequence.py @@ -20,14 +20,14 @@ class ImageSequenceLoader(load.LoaderPlugin): Stores the imported asset in a container named after the asset. """ - product_types = [ + product_types = { "shot", "render", "image", "plate", "reference", "review", - ] + } representations = ["*"] extensions = {"jpeg", "png", "jpg"} 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 cc95b06bd6..d5fbeb323b 100644 --- a/client/ayon_core/hosts/harmony/plugins/load/load_palette.py +++ b/client/ayon_core/hosts/harmony/plugins/load/load_palette.py @@ -11,7 +11,7 @@ import ayon_core.hosts.harmony.api as harmony class ImportPaletteLoader(load.LoaderPlugin): """Import palettes.""" - product_types = ["palette", "harmony.palette"] + product_types = {"palette", "harmony.palette"} representations = ["plt"] label = "Import Palette" 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 21b51bce48..48064f2b75 100644 --- a/client/ayon_core/hosts/harmony/plugins/load/load_template.py +++ b/client/ayon_core/hosts/harmony/plugins/load/load_template.py @@ -23,7 +23,7 @@ class TemplateLoader(load.LoaderPlugin): """ - product_types = ["template", "workfile"] + product_types = {"template", "workfile"} representations = ["*"] label = "Load Template" icon = "gift" 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 ec58d61ef0..7bf634f00c 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 @@ -13,7 +13,7 @@ import ayon_core.hosts.harmony.api as harmony class ImportTemplateLoader(load.LoaderPlugin): """Import templates.""" - product_types = ["harmony.template", "workfile"] + product_types = {"harmony.template", "workfile"} representations = ["*"] label = "Import Template" @@ -60,6 +60,6 @@ class ImportTemplateLoader(load.LoaderPlugin): class ImportWorkfileLoader(ImportTemplateLoader): """Import workfiles.""" - product_types = ["workfile"] + product_types = {"workfile"} representations = ["zip"] label = "Import Workfile" 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 58da259429..72d7e03a9a 100644 --- a/client/ayon_core/hosts/hiero/plugins/load/load_clip.py +++ b/client/ayon_core/hosts/hiero/plugins/load/load_clip.py @@ -15,7 +15,7 @@ class LoadClip(phiero.SequenceLoader): during conforming to project """ - product_types = ["render2d", "source", "plate", "render", "review"] + product_types = {"render2d", "source", "plate", "render", "review"} representations = ["*"] extensions = set( ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS) 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 b1ad48af75..fd6b8ed694 100644 --- a/client/ayon_core/hosts/hiero/plugins/load/load_effects.py +++ b/client/ayon_core/hosts/hiero/plugins/load/load_effects.py @@ -14,7 +14,7 @@ from ayon_core.lib import Logger class LoadEffects(load.LoaderPlugin): """Loading colorspace soft effect exported from nukestudio""" - product_types = ["effect"] + product_types = {"effect"} representations = ["*"] extension = {"json"} diff --git a/client/ayon_core/hosts/houdini/plugins/load/actions.py b/client/ayon_core/hosts/houdini/plugins/load/actions.py index 0b5665adb4..c277005919 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/actions.py +++ b/client/ayon_core/hosts/houdini/plugins/load/actions.py @@ -8,13 +8,13 @@ from ayon_core.pipeline import load class SetFrameRangeLoader(load.LoaderPlugin): """Set frame range excluding pre- and post-handles""" - product_types = [ + product_types = { "animation", "camera", "pointcache", "vdbcache", "usd", - ] + } representations = ["abc", "vdb", "usd"] label = "Set frame range" @@ -45,13 +45,13 @@ class SetFrameRangeLoader(load.LoaderPlugin): class SetFrameRangeWithHandlesLoader(load.LoaderPlugin): """Set frame range including pre- and post-handles""" - product_types = [ + product_types = { "animation", "camera", "pointcache", "vdbcache", "usd", - ] + } representations = ["abc", "vdb", "usd"] label = "Set frame range (with handles)" 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 681fecaba9..3398920e87 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_alembic.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_alembic.py @@ -9,7 +9,7 @@ from ayon_core.hosts.houdini.api import pipeline class AbcLoader(load.LoaderPlugin): """Load Alembic""" - product_types = ["model", "animation", "pointcache", "gpuCache"] + product_types = {"model", "animation", "pointcache", "gpuCache"} label = "Load Alembic" representations = ["abc"] order = -10 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 3ee0a936ca..8d3becb973 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 @@ -9,7 +9,7 @@ from ayon_core.hosts.houdini.api import pipeline class AbcArchiveLoader(load.LoaderPlugin): """Load Alembic as full geometry network hierarchy """ - product_types = ["model", "animation", "pointcache", "gpuCache"] + product_types = {"model", "animation", "pointcache", "gpuCache"} label = "Load Alembic as Archive" representations = ["abc"] order = -5 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 9c5c6e18d2..fd0e8f4604 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_ass.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_ass.py @@ -11,7 +11,7 @@ from ayon_core.hosts.houdini.api import pipeline class AssLoader(load.LoaderPlugin): """Load .ass with Arnold Procedural""" - product_types = ["ass"] + product_types = {"ass"} label = "Load Arnold Procedural" representations = ["ass"] order = -10 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 4e3fd36b5f..fd8071c0de 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_bgeo.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_bgeo.py @@ -13,7 +13,7 @@ class BgeoLoader(load.LoaderPlugin): """Load bgeo files to Houdini.""" label = "Load bgeo" - product_types = ["model", "pointcache", "bgeo"] + product_types = {"model", "pointcache", "bgeo"} representations = [ "bgeo", "bgeosc", "bgeogz", "bgeo.sc", "bgeo.gz", "bgeo.lzma", "bgeo.bz2"] 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 2ab4e314b5..605e5724e6 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_camera.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_camera.py @@ -87,7 +87,7 @@ def transfer_non_default_values(src, dest, ignore=None): class CameraLoader(load.LoaderPlugin): """Load camera from an Alembic file""" - product_types = ["camera"] + product_types = {"camera"} label = "Load Camera (abc)" representations = ["abc"] order = -10 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 9b801e69eb..a0c5e0c934 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_fbx.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_fbx.py @@ -16,7 +16,7 @@ class FbxLoader(load.LoaderPlugin): order = -10 - product_types = ["*"] + product_types = {"*"} representations = ["*"] extensions = {"fbx"} 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 b4d38665c1..df77783a34 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_hda.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_hda.py @@ -10,7 +10,7 @@ from ayon_core.hosts.houdini.api import pipeline class HdaLoader(load.LoaderPlugin): """Load Houdini Digital Asset file.""" - product_types = ["hda"] + product_types = {"hda"} label = "Load Hda" representations = ["hda"] order = -10 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 148d174652..b77e4f662a 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_image.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_image.py @@ -44,7 +44,7 @@ def get_image_avalon_container(): class ImageLoader(load.LoaderPlugin): """Load images into COP2""" - product_types = ["imagesequence"] + product_types = {"imagesequence"} label = "Load Image (COP2)" representations = ["*"] order = -10 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 dc353ac1b5..a6556619fc 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 @@ -13,7 +13,7 @@ import hou class RedshiftProxyLoader(load.LoaderPlugin): """Load Redshift Proxy""" - product_types = ["redshiftproxy"] + product_types = {"redshiftproxy"} label = "Load Redshift Proxy" representations = ["rs"] order = -10 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 6169c40f92..19950e2c98 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 @@ -9,10 +9,10 @@ from ayon_core.hosts.houdini.api import lib class USDSublayerLoader(load.LoaderPlugin): """Sublayer USD file in Solaris""" - product_types = [ + product_types = { "usd", "usdCamera", - ] + } label = "Sublayer USD" representations = ["usd", "usda", "usdlc", "usdnc", "abc"] order = 1 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 5b58024c80..25f98c7c7c 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 @@ -9,10 +9,10 @@ from ayon_core.hosts.houdini.api import lib class USDReferenceLoader(load.LoaderPlugin): """Reference USD file in Solaris""" - product_types = [ + product_types = { "usd", "usdCamera", - ] + } label = "Reference USD" representations = ["usd", "usda", "usdlc", "usdnc", "abc"] order = -8 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 79797f6b66..d9808020d7 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_vdb.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_vdb.py @@ -11,7 +11,7 @@ from ayon_core.hosts.houdini.api import pipeline class VdbLoader(load.LoaderPlugin): """Load VDB""" - product_types = ["vdbcache"] + product_types = {"vdbcache"} label = "Load VDB" representations = ["vdb"] order = -10 diff --git a/client/ayon_core/hosts/houdini/plugins/load/show_usdview.py b/client/ayon_core/hosts/houdini/plugins/load/show_usdview.py index 8b668d004f..9506d9dd0c 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/show_usdview.py +++ b/client/ayon_core/hosts/houdini/plugins/load/show_usdview.py @@ -11,7 +11,7 @@ class ShowInUsdview(load.LoaderPlugin): label = "Show in usdview" representations = ["*"] - product_types = ["*"] + product_types = {"*"} extensions = {"usd", "usda", "usdlc", "usdnc", "abc"} order = 15 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 e82c6061d9..e1de6b98f9 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 @@ -18,7 +18,7 @@ from ayon_core.pipeline import get_representation_path, load class FbxLoader(load.LoaderPlugin): """Fbx Loader.""" - product_types = ["camera"] + product_types = {"camera"} representations = ["fbx"] order = -9 icon = "code-fork" 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 7e6a126426..1c2c5317cc 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 @@ -72,10 +72,11 @@ class MaterialDupOptionsWindow(QtWidgets.QDialog): class MaxSceneLoader(load.LoaderPlugin): """Max Scene Loader.""" - product_types = [ + product_types = { "camera", "maxScene", - "model"] + "model", + } representations = ["max"] order = -8 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 4090692afc..00e675d69c 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_model.py +++ b/client/ayon_core/hosts/max/plugins/load/load_model.py @@ -14,7 +14,7 @@ from ayon_core.hosts.max.api.lib import ( class ModelAbcLoader(load.LoaderPlugin): """Loading model with the Alembic loader.""" - product_types = ["model"] + product_types = {"model"} label = "Load Model with Alembic" representations = ["abc"] order = -10 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 1ab59c6cc8..4b87c60de0 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 @@ -17,7 +17,7 @@ from ayon_core.hosts.max.api.lib import maintained_selection class FbxModelLoader(load.LoaderPlugin): """Fbx Model Loader.""" - product_types = ["model"] + product_types = {"model"} representations = ["fbx"] order = -9 icon = "code-fork" 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 120c483a7d..4f8a22af07 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 @@ -20,7 +20,7 @@ from ayon_core.pipeline import get_representation_path, load class ObjLoader(load.LoaderPlugin): """Obj Loader.""" - product_types = ["model"] + product_types = {"model"} representations = ["obj"] order = -9 icon = "code-fork" 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 9cc2375cf2..bde23e119e 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 @@ -22,7 +22,7 @@ from ayon_core.pipeline import get_representation_path, load class ModelUSDLoader(load.LoaderPlugin): """Loading model with the USD loader.""" - product_types = ["model"] + product_types = {"model"} label = "Load Model(USD)" representations = ["usda"] order = -10 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 0a1d6cbd85..7f515ac6a5 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_pointcache.py +++ b/client/ayon_core/hosts/max/plugins/load/load_pointcache.py @@ -18,7 +18,7 @@ from ayon_core.hosts.max.api.pipeline import ( class AbcLoader(load.LoaderPlugin): """Alembic loader.""" - product_types = ["camera", "animation", "pointcache"] + product_types = {"camera", "animation", "pointcache"} label = "Load Alembic" representations = ["abc"] order = -10 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 7e27afd7e8..31d3f02ec0 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 @@ -21,7 +21,7 @@ from pymxs import runtime as rt class OxAbcLoader(load.LoaderPlugin): """Ornatrix Alembic loader.""" - product_types = ["camera", "animation", "pointcache"] + product_types = {"camera", "animation", "pointcache"} label = "Load Alembic with Ornatrix" representations = ["abc"] order = -10 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 6246979de1..c0000c7a79 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_pointcloud.py +++ b/client/ayon_core/hosts/max/plugins/load/load_pointcloud.py @@ -17,7 +17,7 @@ from ayon_core.pipeline import get_representation_path, load class PointCloudLoader(load.LoaderPlugin): """Point Cloud Loader.""" - product_types = ["pointcloud"] + product_types = {"pointcloud"} representations = ["prt"] order = -8 icon = "code-fork" 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 34732a9ef0..ff6811bd1b 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 @@ -23,7 +23,7 @@ class RedshiftProxyLoader(load.LoaderPlugin): """Load rs files with Redshift Proxy""" label = "Load Redshift Proxy" - product_types = ["redshiftproxy"] + product_types = {"redshiftproxy"} representations = ["rs"] order = -9 icon = "code-fork" 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 3460b913ed..0244e4e6fc 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_tycache.py +++ b/client/ayon_core/hosts/max/plugins/load/load_tycache.py @@ -16,7 +16,7 @@ from ayon_core.pipeline import get_representation_path, load class TyCacheLoader(load.LoaderPlugin): """TyCache Loader.""" - product_types = ["tycache"] + product_types = {"tycache"} representations = ["tyc"] order = -8 icon = "code-fork" diff --git a/client/ayon_core/hosts/maya/plugins/load/_load_animation.py b/client/ayon_core/hosts/maya/plugins/load/_load_animation.py index b9033bf2e4..884bdd7538 100644 --- a/client/ayon_core/hosts/maya/plugins/load/_load_animation.py +++ b/client/ayon_core/hosts/maya/plugins/load/_load_animation.py @@ -46,10 +46,11 @@ def _process_reference(file_url, name, namespace, options): class AbcLoader(ayon_core.hosts.maya.api.plugin.ReferenceLoader): """Loader to reference an Alembic file""" - product_types = [ + product_types = { "animation", "camera", - "pointcache"] + "pointcache", + } representations = ["abc"] label = "Reference animation" @@ -76,9 +77,10 @@ class AbcLoader(ayon_core.hosts.maya.api.plugin.ReferenceLoader): class FbxLoader(ayon_core.hosts.maya.api.plugin.ReferenceLoader): """Loader to reference an Fbx files""" - product_types = [ + product_types = { "animation", - "camera"] + "camera", + } representations = ["fbx"] label = "Reference animation" diff --git a/client/ayon_core/hosts/maya/plugins/load/actions.py b/client/ayon_core/hosts/maya/plugins/load/actions.py index 0cf031d012..a98fe97692 100644 --- a/client/ayon_core/hosts/maya/plugins/load/actions.py +++ b/client/ayon_core/hosts/maya/plugins/load/actions.py @@ -13,11 +13,12 @@ import ayon_core.hosts.maya.api.plugin class SetFrameRangeLoader(load.LoaderPlugin): """Set frame range excluding pre- and post-handles""" - product_types = [ + product_types = { "animation", "camera", "proxyAbc", - "pointcache"] + "pointcache", + } representations = ["abc"] label = "Set frame range" @@ -47,11 +48,12 @@ class SetFrameRangeLoader(load.LoaderPlugin): class SetFrameRangeWithHandlesLoader(load.LoaderPlugin): """Set frame range including pre- and post-handles""" - product_types = [ + product_types = { "animation", "camera", "proxyAbc", - "pointcache"] + "pointcache", + } representations = ["abc"] label = "Set frame range (with handles)" @@ -93,7 +95,7 @@ class ImportMayaLoader(ayon_core.hosts.maya.api.plugin.Loader): """ representations = ["ma", "mb", "obj"] - product_types = [ + product_types = { "model", "pointcache", "proxyAbc", @@ -106,8 +108,8 @@ class ImportMayaLoader(ayon_core.hosts.maya.api.plugin.Loader): "rig", "camerarig", "staticMesh", - "workfile" - ] + "workfile", + } label = "Import" order = 10 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 04445e68cf..920ad762b3 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 @@ -29,9 +29,9 @@ def is_sequence(files): class ArnoldStandinLoader(load.LoaderPlugin): """Load as Arnold standin""" - product_types = [ + product_types = { "ass", "animation", "model", "proxyAbc", "pointcache", "usd" - ] + } representations = ["ass", "abc", "usda", "usdc", "usd"] label = "Load as Arnold standin" 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 1db253822e..a0cbf91905 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_assembly.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_assembly.py @@ -12,7 +12,7 @@ from ayon_core.hosts.maya.api import setdress class AssemblyLoader(load.LoaderPlugin): - product_types = ["assembly"] + product_types = {"assembly"} representations = ["json"] label = "Load Set Dress" 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 2720a5ddfa..0a40993fcd 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_audio.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_audio.py @@ -11,7 +11,7 @@ from ayon_core.hosts.maya.api.lib import unique_namespace, get_container_members class AudioLoader(load.LoaderPlugin): """Specific loader of audio.""" - product_types = ["audio"] + product_types = {"audio"} label = "Load audio" representations = ["wav"] icon = "volume-up" 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 ca0eadc677..494bc7cfc6 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_gpucache.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_gpucache.py @@ -15,7 +15,7 @@ from ayon_core.hosts.maya.api.plugin import get_load_color_for_product_type class GpuCacheLoader(load.LoaderPlugin): """Load Alembic as gpuCache""" - product_types = ["model", "animation", "proxyAbc", "pointcache"] + product_types = {"model", "animation", "proxyAbc", "pointcache"} representations = ["abc", "gpu_cache"] label = "Load Gpu Cache" 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 7db68365ca..4976c46d7f 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_image.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_image.py @@ -93,7 +93,7 @@ def create_stencil(): class FileNodeLoader(load.LoaderPlugin): """File node loader.""" - product_types = ["image", "plate", "render"] + product_types = {"image", "plate", "render"} label = "Load file node" representations = ["exr", "tif", "png", "jpg"] icon = "image" 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 788971fcf8..7d6f7e26cf 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 @@ -87,7 +87,7 @@ class CameraWindow(QtWidgets.QDialog): class ImagePlaneLoader(load.LoaderPlugin): """Specific loader of plate for image planes on selected camera.""" - product_types = ["image", "plate", "render"] + product_types = {"image", "plate", "render"} label = "Load imagePlane" representations = ["mov", "exr", "preview", "png", "jpg"] icon = "image" 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 a2203b6e9b..f126a1df26 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_look.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_look.py @@ -17,7 +17,7 @@ from ayon_core.tools.utils import ScrollMessageBox class LookLoader(ayon_core.hosts.maya.api.plugin.ReferenceLoader): """Specific loader for lookdev""" - product_types = ["look"] + product_types = {"look"} representations = ["ma"] label = "Reference look" diff --git a/client/ayon_core/hosts/maya/plugins/load/load_matchmove.py b/client/ayon_core/hosts/maya/plugins/load/load_matchmove.py index 749369de87..05da173bb7 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_matchmove.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_matchmove.py @@ -8,7 +8,7 @@ class MatchmoveLoader(load.LoaderPlugin): Supported script types are .py and .mel """ - product_types = ["matchmove"] + product_types = {"matchmove"} representations = ["py", "mel"] defaults = ["Camera", "Object", "Mocap"] 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 885941ee59..cd73c26de1 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 @@ -16,7 +16,7 @@ from ayon_core.hosts.maya.api.pipeline import containerise class MayaUsdLoader(load.LoaderPlugin): """Read USD data in a Maya USD Proxy""" - product_types = ["model", "usd", "pointcache", "animation"] + product_types = {"model", "usd", "pointcache", "animation"} representations = ["usd", "usda", "usdc", "usdz", "abc"] label = "Load USD to Maya Proxy" 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 9d78c63205..984d14dff3 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 @@ -20,14 +20,14 @@ from ayon_core.hosts.maya.api.pipeline import containerise class MultiverseUsdLoader(load.LoaderPlugin): """Read USD data in a Multiverse Compound""" - product_types = [ + product_types = { "model", "usd", "mvUsdComposition", "mvUsdOverride", "pointcache", - "animation" - ] + "animation", + } representations = ["usd", "usda", "usdc", "usdz", "abc"] label = "Load USD to Multiverse" 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 10cf4918a4..dc5bc6ec1c 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 @@ -19,7 +19,7 @@ from ayon_core.hosts.maya.api.pipeline import containerise class MultiverseUsdOverLoader(load.LoaderPlugin): """Reference file""" - product_types = ["mvUsdOverride"] + product_types = {"mvUsdOverride"} representations = ["usda", "usd", "udsz"] label = "Load Usd Override into Compound" 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 a6f0a0878e..63dae87243 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 @@ -22,7 +22,7 @@ from ayon_core.hosts.maya.api.plugin import get_load_color_for_product_type class RedshiftProxyLoader(load.LoaderPlugin): """Load Redshift proxy""" - product_types = ["redshiftproxy"] + product_types = {"redshiftproxy"} representations = ["rs"] label = "Import Redshift Proxy" 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 a3914f2efe..fdd85eda43 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_reference.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_reference.py @@ -89,7 +89,7 @@ def preserve_modelpanel_cameras(container, log=None): class ReferenceLoader(plugin.ReferenceLoader): """Reference file""" - product_types = [ + product_types = { "model", "pointcache", "proxyAbc", @@ -104,8 +104,8 @@ class ReferenceLoader(plugin.ReferenceLoader): "staticMesh", "skeletalMesh", "mvLook", - "matchmove" - ] + "matchmove", + } representations = ["ma", "abc", "fbx", "mb"] @@ -272,7 +272,7 @@ class MayaUSDReferenceLoader(ReferenceLoader): """Reference USD file to native Maya nodes using MayaUSDImport reference""" label = "Reference Maya USD" - product_types = ["usd"] + product_types = {"usd"} representations = ["usd"] extensions = {"usd", "usda", "usdc"} 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 905be00ae7..6f20e677f0 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_rendersetup.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_rendersetup.py @@ -24,7 +24,7 @@ import maya.app.renderSetup.model.renderSetup as renderSetup class RenderSetupLoader(load.LoaderPlugin): """Load json preset for RenderSetup overwriting current one.""" - product_types = ["rendersetup"] + product_types = {"rendersetup"} representations = ["json"] defaults = ['Main'] 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 d8cf3774c8..eaa7ff1ae3 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 @@ -12,7 +12,7 @@ from ayon_core.hosts.maya.api.plugin import get_load_color_for_product_type class LoadVDBtoArnold(load.LoaderPlugin): """Load OpenVDB for Arnold in aiVolume""" - product_types = ["vdbcache"] + product_types = {"vdbcache"} representations = ["vdb"] label = "Load VDB to Arnold" 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 f5ed2dfdc2..1707008b67 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 @@ -18,7 +18,7 @@ class LoadVDBtoRedShift(load.LoaderPlugin): """ - product_types = ["vdbcache"] + product_types = {"vdbcache"} representations = ["vdb"] label = "Load VDB to RedShift" 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 e5f1674ef3..42d4583d76 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 @@ -77,7 +77,7 @@ def _fix_duplicate_vvg_callbacks(): class LoadVDBtoVRay(load.LoaderPlugin): """Load OpenVDB in a V-Ray Volume Grid""" - product_types = ["vdbcache"] + product_types = {"vdbcache"} representations = ["vdb"] label = "Load VDB to VRay" 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 b9f144f5da..651197627e 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_vrayproxy.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_vrayproxy.py @@ -27,7 +27,7 @@ from ayon_core.hosts.maya.api.plugin import get_load_color_for_product_type class VRayProxyLoader(load.LoaderPlugin): """Load VRay Proxy with Alembic or VrayMesh.""" - product_types = ["vrayproxy", "model", "pointcache", "animation"] + product_types = {"vrayproxy", "model", "pointcache", "animation"} representations = ["vrmesh", "abc"] label = "Import VRay Proxy" 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 046a94775f..2f4ab1d080 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_vrayscene.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_vrayscene.py @@ -17,7 +17,7 @@ from ayon_core.hosts.maya.api.plugin import get_load_color_for_product_type class VRaySceneLoader(load.LoaderPlugin): """Load Vray scene""" - product_types = ["vrayscene_layer"] + product_types = {"vrayscene_layer"} representations = ["vrscene"] label = "Import VRay Scene" 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 5b7d630dc3..880efd82e1 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_xgen.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_xgen.py @@ -20,7 +20,7 @@ from ayon_core.pipeline import get_representation_path class XgenLoader(ayon_core.hosts.maya.api.plugin.ReferenceLoader): """Load Xgen as reference""" - product_types = ["xgen"] + product_types = {"xgen"} representations = ["ma", "mb"] label = "Reference Xgen" 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 0ae0f95580..8933c4d8a6 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 @@ -36,7 +36,7 @@ def set_attribute(node, attr, value): class YetiCacheLoader(load.LoaderPlugin): """Load Yeti Cache with one or more Yeti nodes""" - product_types = ["yeticache", "yetiRig"] + product_types = {"yeticache", "yetiRig"} representations = ["fur"] label = "Load Yeti Cache" diff --git a/client/ayon_core/hosts/maya/plugins/load/load_yeti_rig.py b/client/ayon_core/hosts/maya/plugins/load/load_yeti_rig.py index 609231900d..74e33c5866 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_yeti_rig.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_yeti_rig.py @@ -7,7 +7,7 @@ from ayon_core.hosts.maya.api import lib class YetiRigLoader(plugin.ReferenceLoader): """This loader will load Yeti rig.""" - product_types = ["yetiRig"] + product_types = {"yetiRig"} representations = ["ma"] label = "Load Yeti Rig" diff --git a/client/ayon_core/hosts/nuke/plugins/load/actions.py b/client/ayon_core/hosts/nuke/plugins/load/actions.py index a9086e2ebc..a1b3697ef1 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/actions.py +++ b/client/ayon_core/hosts/nuke/plugins/load/actions.py @@ -12,12 +12,13 @@ log = Logger.get_logger(__name__) class SetFrameRangeLoader(load.LoaderPlugin): """Set frame range excluding pre- and post-handles""" - product_types = [ + product_types = { "animation", "camera", "write", "yeticache", - "pointcache"] + "pointcache", + } representations = ["*"] extensions = {"*"} @@ -45,12 +46,13 @@ class SetFrameRangeLoader(load.LoaderPlugin): class SetFrameRangeWithHandlesLoader(load.LoaderPlugin): """Set frame range including pre- and post-handles""" - product_types = [ + product_types = { "animation", "camera", "write", "yeticache", - "pointcache"] + "pointcache", + } representations = ["*"] label = "Set frame range (with handles)" 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 321d327fc5..e48dbf5e2f 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_backdrop.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_backdrop.py @@ -21,7 +21,7 @@ from ayon_core.hosts.nuke.api import containerise, update_container class LoadBackdropNodes(load.LoaderPlugin): """Loading Published Backdrop nodes (workfile, nukenodes)""" - product_types = ["workfile", "nukenodes"] + product_types = {"workfile", "nukenodes"} representations = ["*"] extensions = {"nk"} 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 574b74e7a1..70b736b1c8 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 @@ -20,7 +20,7 @@ class AlembicCameraLoader(load.LoaderPlugin): This will load alembic camera into script. """ - product_types = ["camera"] + product_types = {"camera"} representations = ["*"] extensions = {"abc"} 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 9094414080..8a41d854d9 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_clip.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_clip.py @@ -35,13 +35,13 @@ class LoadClip(plugin.NukeLoader): """ log = Logger.get_logger(__name__) - product_types = [ + product_types = { "source", "plate", "render", "prerender", - "review" - ] + "review", + } representations = ["*"] extensions = set( ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS) 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 a0e935f480..f17b179d1b 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_effects.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_effects.py @@ -18,7 +18,7 @@ from ayon_core.hosts.nuke.api import ( class LoadEffects(load.LoaderPlugin): """Loading colorspace soft effect exported from nukestudio""" - product_types = ["effect"] + product_types = {"effect"} representations = ["*"] extensions = {"json"} 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 b5f6509fbe..6b58977a95 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 @@ -19,7 +19,7 @@ from ayon_core.hosts.nuke.api import ( class LoadEffectsInputProcess(load.LoaderPlugin): """Loading colorspace soft effect exported from nukestudio""" - product_types = ["effect"] + product_types = {"effect"} representations = ["*"] extensions = {"json"} 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 3113d7194c..6709648ffb 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_gizmo.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_gizmo.py @@ -21,7 +21,7 @@ from ayon_core.hosts.nuke.api import ( class LoadGizmo(load.LoaderPlugin): """Loading nuke Gizmo""" - product_types = ["gizmo"] + product_types = {"gizmo"} representations = ["*"] extensions = {"nk"} 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 27e7c17756..3017fa5fc9 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 @@ -23,7 +23,7 @@ from ayon_core.hosts.nuke.api import ( class LoadGizmoInputProcess(load.LoaderPlugin): """Loading colorspace soft effect exported from nukestudio""" - product_types = ["gizmo"] + product_types = {"gizmo"} representations = ["*"] extensions = {"nk"} 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 c530d234f5..9d3f9ceea0 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_image.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_image.py @@ -23,15 +23,15 @@ from ayon_core.lib.transcoding import ( class LoadImage(load.LoaderPlugin): """Load still image into Nuke""" - product_types = [ + product_types = { "render2d", "source", "plate", "render", "prerender", "review", - "image" - ] + "image", + } representations = ["*"] extensions = set( ext.lstrip(".") for ext in IMAGE_EXTENSIONS diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_matchmove.py b/client/ayon_core/hosts/nuke/plugins/load/load_matchmove.py index a524a6961d..73c21376b4 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_matchmove.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_matchmove.py @@ -7,7 +7,7 @@ class MatchmoveLoader(load.LoaderPlugin): This will run matchmove script to create track in script. """ - product_types = ["matchmove"] + product_types = {"matchmove"} representations = ["*"] extensions = {"py"} 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 4ef2721915..971c36b6cf 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_model.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_model.py @@ -18,7 +18,7 @@ class AlembicModelLoader(load.LoaderPlugin): This will load alembic model or anim into script. """ - product_types = ["model", "pointcache", "animation"] + product_types = {"model", "pointcache", "animation"} representations = ["*"] extensions = {"abc"} 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 586fbc3209..82addcdfc0 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_ociolook.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_ociolook.py @@ -20,7 +20,7 @@ from ayon_core.hosts.nuke.api import ( class LoadOcioLookNodes(load.LoaderPlugin): """Loading Ocio look to the nuke.Node graph""" - product_types = ["ociolook"] + product_types = {"ociolook"} representations = ["*"] extensions = {"json"} 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 36e1f09c0a..ccc4164355 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 @@ -16,7 +16,7 @@ from ayon_core.hosts.nuke.api import ( class LinkAsGroup(load.LoaderPlugin): """Copy the published file to be pasted at the desired location""" - product_types = ["workfile", "nukenodes"] + product_types = {"workfile", "nukenodes"} representations = ["*"] extensions = {"nk"} 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 0cfa9400f3..72df2706b5 100644 --- a/client/ayon_core/hosts/photoshop/plugins/load/load_image.py +++ b/client/ayon_core/hosts/photoshop/plugins/load/load_image.py @@ -11,7 +11,7 @@ class ImageLoader(photoshop.PhotoshopLoader): Stores the imported asset in a container named after the asset. """ - product_types = ["image", "render"] + product_types = {"image", "render"} representations = ["*"] def load(self, context, name=None, namespace=None, data=None): 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 bfedc510e8..25b22f53a4 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 @@ -24,7 +24,7 @@ class ImageFromSequenceLoader(photoshop.PhotoshopLoader): difficult. """ - product_types = ["render"] + product_types = {"render"} representations = ["*"] options = [] 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 6ae34503d0..7cd34690f7 100644 --- a/client/ayon_core/hosts/photoshop/plugins/load/load_reference.py +++ b/client/ayon_core/hosts/photoshop/plugins/load/load_reference.py @@ -14,7 +14,7 @@ class ReferenceLoader(photoshop.PhotoshopLoader): "Cannot write to closing transport", possible refactor. """ - product_types = ["image", "render"] + product_types = {"image", "render"} representations = ["*"] def load(self, context, name=None, namespace=None, data=None): 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 848e6a4a54..c7bced5e8e 100644 --- a/client/ayon_core/hosts/resolve/plugins/load/load_clip.py +++ b/client/ayon_core/hosts/resolve/plugins/load/load_clip.py @@ -18,7 +18,7 @@ class LoadClip(plugin.TimelineItemLoader): during conforming to project """ - product_types = ["render2d", "source", "plate", "render", "review"] + product_types = {"render2d", "source", "plate", "render", "review"} representations = ["*"] extensions = set( 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 cf95f4506c..f2254c0907 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -17,7 +17,7 @@ import qargparse class SubstanceLoadProjectMesh(load.LoaderPlugin): """Load mesh for project""" - product_types = ["*"] + product_types = {"*"} representations = ["abc", "fbx", "obj", "gltf"] label = "Load mesh" diff --git a/client/ayon_core/hosts/tvpaint/plugins/load/load_image.py b/client/ayon_core/hosts/tvpaint/plugins/load/load_image.py index 3576dcea15..e954b72f12 100644 --- a/client/ayon_core/hosts/tvpaint/plugins/load/load_image.py +++ b/client/ayon_core/hosts/tvpaint/plugins/load/load_image.py @@ -6,7 +6,7 @@ from ayon_core.hosts.tvpaint.api.lib import execute_george_through_file class ImportImage(plugin.Loader): """Load image or image sequence to TVPaint as new layer.""" - product_types = ["render", "image", "background", "plate", "review"] + product_types = {"render", "image", "background", "plate", "review"} representations = ["*"] label = "Import Image" 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 e7539583be..a3bcf4c144 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 @@ -20,7 +20,7 @@ from ayon_core.hosts.tvpaint.api.pipeline import ( class LoadImage(plugin.Loader): """Load image or image sequence to TVPaint as new layer.""" - product_types = ["render", "image", "background", "plate", "review"] + product_types = {"render", "image", "background", "plate", "review"} representations = ["*"] label = "Load Image" diff --git a/client/ayon_core/hosts/tvpaint/plugins/load/load_sound.py b/client/ayon_core/hosts/tvpaint/plugins/load/load_sound.py index d8f22b3008..182d95d6db 100644 --- a/client/ayon_core/hosts/tvpaint/plugins/load/load_sound.py +++ b/client/ayon_core/hosts/tvpaint/plugins/load/load_sound.py @@ -22,7 +22,7 @@ class ImportSound(plugin.Loader): file contain any audio. """ - product_types = ["audio", "review", "plate"] + product_types = {"audio", "review", "plate"} representations = ["*"] label = "Import Sound" diff --git a/client/ayon_core/hosts/tvpaint/plugins/load/load_workfile.py b/client/ayon_core/hosts/tvpaint/plugins/load/load_workfile.py index 3d1941d266..56aa67708d 100644 --- a/client/ayon_core/hosts/tvpaint/plugins/load/load_workfile.py +++ b/client/ayon_core/hosts/tvpaint/plugins/load/load_workfile.py @@ -24,7 +24,7 @@ from ayon_core.pipeline.version_start import get_versioning_start class LoadWorkfile(plugin.Loader): """Load workfile.""" - product_types = ["workfile"] + product_types = {"workfile"} representations = ["tvpp"] label = "Load Workfile" 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 fbf19832b4..02259b706c 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 @@ -14,7 +14,7 @@ import unreal # noqa class AnimationAlembicLoader(plugin.Loader): """Load Unreal SkeletalMesh from Alembic""" - product_types = ["animation"] + product_types = {"animation"} label = "Import Alembic Animation" representations = ["abc"] icon = "cube" 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 3d0a260e4e..0f51ac39e0 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_animation.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_animation.py @@ -20,7 +20,7 @@ from ayon_core.hosts.unreal.api import pipeline as unreal_pipeline class AnimationFBXLoader(plugin.Loader): """Load Unreal SkeletalMesh from FBX.""" - product_types = ["animation"] + product_types = {"animation"} label = "Import FBX Animation" representations = ["fbx"] icon = "cube" 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 b9b67f5f98..285834c911 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_camera.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_camera.py @@ -28,7 +28,7 @@ from ayon_core.hosts.unreal.api.pipeline import ( class CameraLoader(plugin.Loader): """Load Unreal StaticMesh from FBX""" - product_types = ["camera"] + product_types = {"camera"} label = "Load Camera" representations = ["fbx"] icon = "cube" 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 f1cedc575b..44c308069b 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 @@ -19,7 +19,7 @@ import unreal # noqa class PointCacheAlembicLoader(plugin.Loader): """Load Point Cache from Alembic""" - product_types = ["model", "pointcache"] + product_types = {"model", "pointcache"} label = "Import Alembic Point Cache" representations = ["abc"] icon = "cube" 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 dcdd51480a..6c667d3d2f 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_layout.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_layout.py @@ -40,7 +40,7 @@ from ayon_core.hosts.unreal.api.pipeline import ( class LayoutLoader(plugin.Loader): """Load Layout from a JSON file""" - product_types = ["layout"] + product_types = {"layout"} representations = ["json"] label = "Load Layout" 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 a50019773f..700b6957a2 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 @@ -21,7 +21,7 @@ class ExistingLayoutLoader(plugin.Loader): Load Layout for an existing scene, and match the existing assets. """ - product_types = ["layout"] + product_types = {"layout"} representations = ["json"] label = "Load Layout on Existing Scene" 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 ba26bbac59..64b1810080 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 @@ -18,7 +18,7 @@ import unreal # noqa class SkeletalMeshAlembicLoader(plugin.Loader): """Load Unreal SkeletalMesh from Alembic""" - product_types = ["pointcache", "skeletalMesh"] + product_types = {"pointcache", "skeletalMesh"} label = "Import Alembic Skeletal Mesh" representations = ["abc"] icon = "cube" 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 7fc0420583..f61f0dbc3f 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 @@ -18,7 +18,7 @@ import unreal # noqa class SkeletalMeshFBXLoader(plugin.Loader): """Load Unreal SkeletalMesh from FBX.""" - product_types = ["rig", "skeletalMesh"] + product_types = {"rig", "skeletalMesh"} label = "Import FBX Skeletal Mesh" representations = ["fbx"] icon = "cube" 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 9301aacf78..256cb9e8bc 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 @@ -18,7 +18,7 @@ import unreal # noqa class StaticMeshAlembicLoader(plugin.Loader): """Load Unreal StaticMesh from Alembic""" - product_types = ["model", "staticMesh"] + product_types = {"model", "staticMesh"} label = "Import Alembic Static Mesh" representations = ["abc"] icon = "cube" 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 a7df3d1045..0ec4b1b4f8 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 @@ -18,7 +18,7 @@ import unreal # noqa class StaticMeshFBXLoader(plugin.Loader): """Load Unreal StaticMesh from FBX.""" - product_types = ["model", "staticMesh"] + product_types = {"model", "staticMesh"} label = "Import FBX Static Mesh" representations = ["fbx"] icon = "cube" 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 b8ecd40c32..89ef357c89 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_uasset.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_uasset.py @@ -15,7 +15,7 @@ import unreal # noqa class UAssetLoader(plugin.Loader): """Load UAsset.""" - product_types = ["uasset"] + product_types = {"uasset"} label = "Load UAsset" representations = ["uasset"] icon = "cube" @@ -164,7 +164,7 @@ class UAssetLoader(plugin.Loader): class UMapLoader(UAssetLoader): """Load Level.""" - product_types = ["uasset"] + product_types = {"uasset"} label = "Load Level" representations = ["umap"] 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 a3c11544f0..21715a24c6 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_yeticache.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_yeticache.py @@ -15,7 +15,7 @@ import unreal # noqa class YetiLoader(plugin.Loader): """Load Yeti Cache""" - product_types = ["yeticacheUE"] + product_types = {"yeticacheUE"} label = "Import Yeti" representations = ["abc"] icon = "pagelines" diff --git a/client/ayon_core/pipeline/load/plugins.py b/client/ayon_core/pipeline/load/plugins.py index 8c015f19fe..91f839ebf3 100644 --- a/client/ayon_core/pipeline/load/plugins.py +++ b/client/ayon_core/pipeline/load/plugins.py @@ -24,7 +24,7 @@ class LoaderPlugin(list): """ - product_types = [] + product_types = set() representations = [] extensions = {"*"} order = 0 diff --git a/client/ayon_core/plugins/load/copy_file.py b/client/ayon_core/plugins/load/copy_file.py index b6fe10e170..5e6daa866b 100644 --- a/client/ayon_core/plugins/load/copy_file.py +++ b/client/ayon_core/plugins/load/copy_file.py @@ -6,7 +6,7 @@ class CopyFile(load.LoaderPlugin): """Copy the published file to be pasted at the desired location""" representations = ["*"] - product_types = ["*"] + product_types = {"*"} label = "Copy File" order = 10 diff --git a/client/ayon_core/plugins/load/copy_file_path.py b/client/ayon_core/plugins/load/copy_file_path.py index 3b0e0fc291..ecde3bc55f 100644 --- a/client/ayon_core/plugins/load/copy_file_path.py +++ b/client/ayon_core/plugins/load/copy_file_path.py @@ -6,7 +6,7 @@ from ayon_core.pipeline import load class CopyFilePath(load.LoaderPlugin): """Copy published file path to clipboard""" representations = ["*"] - product_types = ["*"] + product_types = {"*"} label = "Copy File Path" order = 20 diff --git a/client/ayon_core/plugins/load/delete_old_versions.py b/client/ayon_core/plugins/load/delete_old_versions.py index 9124278696..04873d8b5c 100644 --- a/client/ayon_core/plugins/load/delete_old_versions.py +++ b/client/ayon_core/plugins/load/delete_old_versions.py @@ -27,7 +27,7 @@ # sequence_splitter = "__sequence_splitter__" # # representations = ["*"] -# product_types = ["*"] +# product_types = {"*"} # tool_names = ["library_loader"] # # label = "Delete Old Versions" diff --git a/client/ayon_core/plugins/load/delivery.py b/client/ayon_core/plugins/load/delivery.py index 3529458e70..cfa245d0d2 100644 --- a/client/ayon_core/plugins/load/delivery.py +++ b/client/ayon_core/plugins/load/delivery.py @@ -29,7 +29,7 @@ class Delivery(load.ProductLoaderPlugin): sequence_splitter = "__sequence_splitter__" representations = ["*"] - product_types = ["*"] + product_types = {"*"} tool_names = ["library_loader"] label = "Deliver Versions" diff --git a/client/ayon_core/plugins/load/open_djv.py b/client/ayon_core/plugins/load/open_djv.py index efe19161a0..30023ac1f5 100644 --- a/client/ayon_core/plugins/load/open_djv.py +++ b/client/ayon_core/plugins/load/open_djv.py @@ -18,7 +18,7 @@ class OpenInDJV(load.LoaderPlugin): """Open Image Sequence with system default""" djv_list = existing_djv_path() - product_types = ["*"] if djv_list else [] + product_types = {"*"} if djv_list else [] representations = ["*"] extensions = { "cin", "dpx", "avi", "dv", "gif", "flv", "mkv", "mov", "mpg", "mpeg", diff --git a/client/ayon_core/plugins/load/open_file.py b/client/ayon_core/plugins/load/open_file.py index b26b261f10..fc57708cd6 100644 --- a/client/ayon_core/plugins/load/open_file.py +++ b/client/ayon_core/plugins/load/open_file.py @@ -18,7 +18,7 @@ def open(filepath): class OpenFile(load.LoaderPlugin): """Open Image Sequence or Video with system default""" - product_types = ["render2d"] + product_types = {"render2d"} representations = ["*"] label = "Open" diff --git a/client/ayon_core/plugins/load/push_to_library.py b/client/ayon_core/plugins/load/push_to_library.py index cd3a5e449d..02d834bc95 100644 --- a/client/ayon_core/plugins/load/push_to_library.py +++ b/client/ayon_core/plugins/load/push_to_library.py @@ -12,7 +12,7 @@ class PushToLibraryProject(load.ProductLoaderPlugin): is_multiple_contexts_compatible = True representations = ["*"] - product_types = ["*"] + product_types = {"*"} label = "Push to Library project" order = 35 From b80541064ca93480a11caa3ee10061d162d6cabd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 18 Mar 2024 14:47:15 +0100 Subject: [PATCH 045/150] fix publish plugin attributes back to 'families' --- .../hosts/max/plugins/publish/collect_frame_range.py | 11 +++-------- .../hosts/maya/plugins/publish/collect_animation.py | 2 +- .../hosts/nuke/plugins/publish/collect_backdrop.py | 2 +- .../hosts/nuke/plugins/publish/collect_gizmo.py | 2 +- .../hosts/resolve/plugins/publish/extract_workfile.py | 2 +- 5 files changed, 7 insertions(+), 12 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/collect_frame_range.py b/client/ayon_core/hosts/max/plugins/publish/collect_frame_range.py index a70665ce3c..6fc8de90d1 100644 --- a/client/ayon_core/hosts/max/plugins/publish/collect_frame_range.py +++ b/client/ayon_core/hosts/max/plugins/publish/collect_frame_range.py @@ -9,14 +9,9 @@ class CollectFrameRange(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.01 label = "Collect Frame Range" hosts = ['max'] - product_types = [ - "camera", - "maxrender", - "pointcache", - "pointcloud", - "review", - "redshiftproxy" - ] + families = ["camera", "maxrender", + "pointcache", "pointcloud", + "review", "redshiftproxy"] def process(self, instance): if instance.data["productType"] == "maxrender": diff --git a/client/ayon_core/hosts/maya/plugins/publish/collect_animation.py b/client/ayon_core/hosts/maya/plugins/publish/collect_animation.py index 1ba750a6e0..2ab6511ece 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/collect_animation.py +++ b/client/ayon_core/hosts/maya/plugins/publish/collect_animation.py @@ -16,7 +16,7 @@ class CollectAnimationOutputGeometry(pyblish.api.InstancePlugin): """ order = pyblish.api.CollectorOrder + 0.4 - product_types = ["animation"] + families = ["animation"] label = "Collect Animation Output Geometry" hosts = ["maya"] diff --git a/client/ayon_core/hosts/nuke/plugins/publish/collect_backdrop.py b/client/ayon_core/hosts/nuke/plugins/publish/collect_backdrop.py index c0fdfa6fae..fc17de95b4 100644 --- a/client/ayon_core/hosts/nuke/plugins/publish/collect_backdrop.py +++ b/client/ayon_core/hosts/nuke/plugins/publish/collect_backdrop.py @@ -11,7 +11,7 @@ class CollectBackdrops(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.22 label = "Collect Backdrop" hosts = ["nuke"] - product_types = ["nukenodes"] + families = ["nukenodes"] def process(self, instance): self.log.debug(pformat(instance.data)) diff --git a/client/ayon_core/hosts/nuke/plugins/publish/collect_gizmo.py b/client/ayon_core/hosts/nuke/plugins/publish/collect_gizmo.py index 9d40b2db0f..fda1c7ac31 100644 --- a/client/ayon_core/hosts/nuke/plugins/publish/collect_gizmo.py +++ b/client/ayon_core/hosts/nuke/plugins/publish/collect_gizmo.py @@ -9,7 +9,7 @@ class CollectGizmo(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.22 label = "Collect Gizmo (group)" hosts = ["nuke"] - product_types = ["gizmo"] + families = ["gizmo"] def process(self, instance): diff --git a/client/ayon_core/hosts/resolve/plugins/publish/extract_workfile.py b/client/ayon_core/hosts/resolve/plugins/publish/extract_workfile.py index 0874685bb6..48ebdee7e3 100644 --- a/client/ayon_core/hosts/resolve/plugins/publish/extract_workfile.py +++ b/client/ayon_core/hosts/resolve/plugins/publish/extract_workfile.py @@ -12,7 +12,7 @@ class ExtractWorkfile(publish.Extractor): label = "Extract Workfile" order = pyblish.api.ExtractorOrder - product_types = ["workfile"] + families = ["workfile"] hosts = ["resolve"] def process(self, instance): From f6e8465dca6081b3deecda9d3d28fceabad2e169 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Mon, 18 Mar 2024 14:57:05 +0100 Subject: [PATCH 046/150] :alembic: add ruff workflow and code-spell pre-commit --- .github/workflows/pr_linting.yml | 24 ++++++ .pre-commit-config.yaml | 33 +++++--- poetry.toml | 0 pyproject.toml | 105 ++++++++++++++++++++++++++ scripts/setup_env.ps1 | 67 +++++++++++++++++ scripts/setup_env.sh | 124 +++++++++++++++++++++++++++++++ 6 files changed, 344 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/pr_linting.yml create mode 100644 poetry.toml create mode 100644 pyproject.toml create mode 100644 scripts/setup_env.ps1 create mode 100644 scripts/setup_env.sh diff --git a/.github/workflows/pr_linting.yml b/.github/workflows/pr_linting.yml new file mode 100644 index 0000000000..3d2431b69a --- /dev/null +++ b/.github/workflows/pr_linting.yml @@ -0,0 +1,24 @@ +name: 📇 Code Linting + +on: + push: + branches: [ develop ] + pull_request: + branches: [ develop ] + + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number}} + cancel-in-progress: true + +permissions: + contents: read + pull-requests: write + +jobs: + linting: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: chartboost/ruff-action@v1 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index eec388924e..8aa3e1b81b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,12 +1,27 @@ # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks repos: -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 - hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - - id: check-yaml - - id: check-added-large-files - - id: no-commit-to-branch - args: [ '--pattern', '^(?!((release|enhancement|feature|bugfix|documentation|tests|local|chore)\/[a-zA-Z0-9\-_]+)$).*' ] + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + - id: no-commit-to-branch + args: [ '--pattern', '^(?!((release|enhancement|feature|bugfix|documentation|tests|local|chore)\/[a-zA-Z0-9\-_]+)$).*' ] + - repo: https://github.com/codespell-project/codespell + rev: v2.2.6 + hooks: + - id: codespell + additional_dependencies: + - tomli + + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.3.3 + hooks: + # Run the linter. + - id: ruff + # Run the formatter. + # - id: ruff-format diff --git a/poetry.toml b/poetry.toml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000000..c50c0bad5b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,105 @@ +[tool.poetry] +name = "ayon-core" +version = "0.3.0" +description = "" +authors = ["Ynput Team "] +readme = "README.md" + +[tool.poetry.dependencies] +python = ">=3.9.1,<3.10" +aiohttp_json_rpc = "*" # TVPaint server +aiohttp-middlewares = "^2.0.0" +wsrpc_aiohttp = "^3.1.1" # websocket server +Click = "^8" +clique = "1.6.*" +jsonschema = "^4" +pyblish-base = "^1.8.11" +pynput = "^1.7.2" # Timers manager - TODO remove +speedcopy = "^2.1" +six = "^1.15" +qtawesome = "0.7.3" + + +[tool.poetry.dev-dependencies] +pytest = "^8.0" +pytest-print = "^1.0" +ayon-python-api = "^1.0" +arrow = "^1.3.0" +ruff = "^0.2.2" + + +[tool.ruff] +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv", + "vendor", + "generated", +] + +# Same as Black. +line-length = 79 +indent-width = 4 + +# Assume Python 3.8 +target-version = "py39" + +[tool.ruff.lint] +# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. +select = ["E4", "E7", "E9", "F"] +ignore = [] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[tool.ruff.format] +# Like Black, use double quotes for strings. +quote-style = "double" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" + +[tool.codespell] +# Ignore words that are not in the dictionary. +ignore-words-list = "ayon,ynput" +skip = "./.*,./package/*" +count = true +quiet-level = 3 + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/scripts/setup_env.ps1 b/scripts/setup_env.ps1 new file mode 100644 index 0000000000..82ff515bc6 --- /dev/null +++ b/scripts/setup_env.ps1 @@ -0,0 +1,67 @@ +$current_dir = Get-Location +$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent +$repo_root = (Get-Item $script_dir).parent.FullName +& git submodule update --init --recursive + + +function Exit-WithCode($exitcode) { + # Only exit this host process if it's a child of another PowerShell parent process... + $parentPID = (Get-CimInstance -ClassName Win32_Process -Filter "ProcessId=$PID" | Select-Object -Property ParentProcessId).ParentProcessId + $parentProcName = (Get-CimInstance -ClassName Win32_Process -Filter "ProcessId=$parentPID" | Select-Object -Property Name).Name + if ('powershell.exe' -eq $parentProcName) { $host.SetShouldExit($exitcode) } + + exit $exitcode + } + + +function Install-Poetry() { + Write-Host ">>> Installing Poetry ... " + $python = "python" + if (Get-Command "pyenv" -ErrorAction SilentlyContinue) { + if (-not (Test-Path -PathType Leaf -Path "$($repo_root)\.python-version")) { + $result = & pyenv global + if ($result -eq "no global version configured") { + Write-Host "!!! Using pyenv but having no local or global version of Python set." -Color Red, Yellow + Exit-WithCode 1 + } + } + $python = & pyenv which python + + } + + $env:POETRY_HOME="$repo_root\.poetry" + (Invoke-WebRequest -Uri https://install.python-poetry.org/ -UseBasicParsing).Content | & $($python) - +} + +Write-Host ">>> Reading Poetry ... " -NoNewline +if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) { + Write-Host "NOT FOUND" + Install-Poetry + Write-Host "INSTALLED" +} else { + Write-Host "OK" +} + +if (-not (Test-Path -PathType Leaf -Path "$($repo_root)\poetry.lock")) { + Write-Host ">>> Installing virtual environment and creating lock." +} else { + Write-Host ">>> Installing virtual environment from lock." +} +$startTime = [int][double]::Parse((Get-Date -UFormat %s)) +& "$env:POETRY_HOME\bin\poetry" install --no-root $poetry_verbosity --ansi +if ($LASTEXITCODE -ne 0) { + Write-Host "!!! ", "Poetry command failed." + Set-Location -Path $current_dir + Exit-WithCode 1 +} +Write-Host ">>> Installing pre-commit hooks ..." +& "$env:POETRY_HOME\bin\poetry" run pre-commit install +if ($LASTEXITCODE -ne 0) { + Write-Host "!!! Installation of pre-commit hooks failed." + Set-Location -Path $current_dir + Exit-WithCode 1 +} + +$endTime = [int][double]::Parse((Get-Date -UFormat %s)) +Set-Location -Path $current_dir +Write-Host ">>> Done in $( $endTime - $startTime ) secs." diff --git a/scripts/setup_env.sh b/scripts/setup_env.sh new file mode 100644 index 0000000000..16298cb8bc --- /dev/null +++ b/scripts/setup_env.sh @@ -0,0 +1,124 @@ +# Colors for terminal + +RST='\033[0m' # Text Reset + +# Regular Colors +Black='\033[0;30m' # Black +Red='\033[0;31m' # Red +Green='\033[0;32m' # Green +Yellow='\033[0;33m' # Yellow +Blue='\033[0;34m' # Blue +Purple='\033[0;35m' # Purple +Cyan='\033[0;36m' # Cyan +White='\033[0;37m' # White + +# Bold +BBlack='\033[1;30m' # Black +BRed='\033[1;31m' # Red +BGreen='\033[1;32m' # Green +BYellow='\033[1;33m' # Yellow +BBlue='\033[1;34m' # Blue +BPurple='\033[1;35m' # Purple +BCyan='\033[1;36m' # Cyan +BWhite='\033[1;37m' # White + +# Bold High Intensity +BIBlack='\033[1;90m' # Black +BIRed='\033[1;91m' # Red +BIGreen='\033[1;92m' # Green +BIYellow='\033[1;93m' # Yellow +BIBlue='\033[1;94m' # Blue +BIPurple='\033[1;95m' # Purple +BICyan='\033[1;96m' # Cyan +BIWhite='\033[1;97m' # White + + +############################################################################## +# Detect required version of python +# Globals: +# colors +# PYTHON +# Arguments: +# None +# Returns: +# None +############################################################################### +detect_python () { + echo -e "${BIGreen}>>>${RST} Using python \c" + command -v python >/dev/null 2>&1 || { echo -e "${BIRed}- NOT FOUND${RST} ${BIYellow}You need Python 3.9 installed to continue.${RST}"; return 1; } + local version_command="import sys;print('{0}.{1}'.format(sys.version_info[0], sys.version_info[1]))" + local python_version="$(python <<< ${version_command})" + oIFS="$IFS" + IFS=. + set -- $python_version + IFS="$oIFS" + if [ "$1" -ge "3" ] && [ "$2" -ge "9" ] ; then + if [ "$2" -gt "9" ] ; then + echo -e "${BIWhite}[${RST} ${BIRed}$1.$2 ${BIWhite}]${RST} - ${BIRed}FAILED${RST} ${BIYellow}Version is new and unsupported, use${RST} ${BIPurple}3.9.x${RST}"; return 1; + else + echo -e "${BIWhite}[${RST} ${BIGreen}$1.$2${RST} ${BIWhite}]${RST}" + fi + else + command -v python >/dev/null 2>&1 || { echo -e "${BIRed}$1.$2$ - ${BIRed}FAILED${RST} ${BIYellow}Version is old and unsupported${RST}"; return 1; } + fi +} + +install_poetry () { + echo -e "${BIGreen}>>>${RST} Installing Poetry ..." + export POETRY_HOME="$repo_root/.poetry" + command -v curl >/dev/null 2>&1 || { echo -e "${BIRed}!!!${RST}${BIYellow} Missing ${RST}${BIBlue}curl${BIYellow} command.${RST}"; return 1; } + curl -sSL https://install.python-poetry.org/ | python - +} + +############################################################################## +# Return absolute path +# Globals: +# None +# Arguments: +# Path to resolve +# Returns: +# None +############################################################################### +realpath () { + echo $(cd $(dirname "$1"); pwd)/$(basename "$1") +} + +main () { + detect_python || return 1 + + # Directories + repo_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}"))) + + if [[ -z $POETRY_HOME ]]; then + export POETRY_HOME="$repo_root/.poetry" + fi + + pushd "$repo_root" > /dev/null || return > /dev/null + + echo -e "${BIGreen}>>>${RST} Reading Poetry ... \c" + if [ -f "$POETRY_HOME/bin/poetry" ]; then + echo -e "${BIGreen}OK${RST}" + else + echo -e "${BIYellow}NOT FOUND${RST}" + install_poetry || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return 1; } + fi + + if [ -f "$repo_root/poetry.lock" ]; then + echo -e "${BIGreen}>>>${RST} Updating dependencies ..." + else + echo -e "${BIGreen}>>>${RST} Installing dependencies ..." + fi + + "$POETRY_HOME/bin/poetry" install --no-root $poetry_verbosity || { echo -e "${BIRed}!!!${RST} Poetry environment installation failed"; return 1; } + if [ $? -ne 0 ] ; then + echo -e "${BIRed}!!!${RST} Virtual environment creation failed." + return 1 + fi + + echo -e "${BIGreen}>>>${RST} Installing pre-commit hooks ..." + "$POETRY_HOME/bin/poetry" run pre-commit install +} + +return_code=0 +main || return_code=$? +exit $return_code From 55e8ab0c4d155487474c3bb5791e19001ef5416f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Mon, 18 Mar 2024 14:59:02 +0100 Subject: [PATCH 047/150] :recycle: update gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 502cf85b9f..acbc3e2572 100644 --- a/.gitignore +++ b/.gitignore @@ -77,6 +77,7 @@ dump.sql # Poetry ######## +.poetry/ .python-version .editorconfig .pre-commit-config.yaml From 84c42d060ed9ff97ec4f73f0858844b33052c7c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Mon, 18 Mar 2024 15:00:08 +0100 Subject: [PATCH 048/150] :heavy_plus_sign: add pre-commit --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index c50c0bad5b..14fe5fd1e3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ pynput = "^1.7.2" # Timers manager - TODO remove speedcopy = "^2.1" six = "^1.15" qtawesome = "0.7.3" +pre-commit = "^3.6.2" [tool.poetry.dev-dependencies] From 775d53f4d29edb0d13f86c588aa36f3da111b837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Mon, 18 Mar 2024 15:06:36 +0100 Subject: [PATCH 049/150] :heavy_plus_sign: add codespell and config --- poetry.toml | 2 ++ pyproject.toml | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/poetry.toml b/poetry.toml index e69de29bb2..ab1033bd37 100644 --- a/poetry.toml +++ b/poetry.toml @@ -0,0 +1,2 @@ +[virtualenvs] +in-project = true diff --git a/pyproject.toml b/pyproject.toml index 14fe5fd1e3..f9c0f4602c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ speedcopy = "^2.1" six = "^1.15" qtawesome = "0.7.3" pre-commit = "^3.6.2" +codespell = "^2.2.6" [tool.poetry.dev-dependencies] @@ -97,7 +98,7 @@ line-ending = "auto" [tool.codespell] # Ignore words that are not in the dictionary. ignore-words-list = "ayon,ynput" -skip = "./.*,./package/*" +skip = "./.*,./package/*,*/vendor/*,*/unreal/integration/*,*/aftereffects/api/extension/js/libs/*" count = true quiet-level = 3 From 92fc2f230860477ae5d81d9467a9078b979416de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Mon, 18 Mar 2024 15:07:04 +0100 Subject: [PATCH 050/150] :heavy_plus_sign: add `poetry.lock` --- poetry.lock | 1468 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1468 insertions(+) create mode 100644 poetry.lock diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000000..adfa083e82 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,1468 @@ +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. + +[[package]] +name = "aiohttp" +version = "3.9.3" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:939677b61f9d72a4fa2a042a5eee2a99a24001a67c13da113b2e30396567db54"}, + {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f5cd333fcf7590a18334c90f8c9147c837a6ec8a178e88d90a9b96ea03194cc"}, + {file = "aiohttp-3.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:82e6aa28dd46374f72093eda8bcd142f7771ee1eb9d1e223ff0fa7177a96b4a5"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f56455b0c2c7cc3b0c584815264461d07b177f903a04481dfc33e08a89f0c26b"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bca77a198bb6e69795ef2f09a5f4c12758487f83f33d63acde5f0d4919815768"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e083c285857b78ee21a96ba1eb1b5339733c3563f72980728ca2b08b53826ca5"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab40e6251c3873d86ea9b30a1ac6d7478c09277b32e14745d0d3c6e76e3c7e29"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df822ee7feaaeffb99c1a9e5e608800bd8eda6e5f18f5cfb0dc7eeb2eaa6bbec"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:acef0899fea7492145d2bbaaaec7b345c87753168589cc7faf0afec9afe9b747"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cd73265a9e5ea618014802ab01babf1940cecb90c9762d8b9e7d2cc1e1969ec6"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a78ed8a53a1221393d9637c01870248a6f4ea5b214a59a92a36f18151739452c"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6b0e029353361f1746bac2e4cc19b32f972ec03f0f943b390c4ab3371840aabf"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7cf5c9458e1e90e3c390c2639f1017a0379a99a94fdfad3a1fd966a2874bba52"}, + {file = "aiohttp-3.9.3-cp310-cp310-win32.whl", hash = "sha256:3e59c23c52765951b69ec45ddbbc9403a8761ee6f57253250c6e1536cacc758b"}, + {file = "aiohttp-3.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:055ce4f74b82551678291473f66dc9fb9048a50d8324278751926ff0ae7715e5"}, + {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6b88f9386ff1ad91ace19d2a1c0225896e28815ee09fc6a8932fded8cda97c3d"}, + {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c46956ed82961e31557b6857a5ca153c67e5476972e5f7190015018760938da2"}, + {file = "aiohttp-3.9.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:07b837ef0d2f252f96009e9b8435ec1fef68ef8b1461933253d318748ec1acdc"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad46e6f620574b3b4801c68255492e0159d1712271cc99d8bdf35f2043ec266"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ed3e046ea7b14938112ccd53d91c1539af3e6679b222f9469981e3dac7ba1ce"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:039df344b45ae0b34ac885ab5b53940b174530d4dd8a14ed8b0e2155b9dddccb"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7943c414d3a8d9235f5f15c22ace69787c140c80b718dcd57caaade95f7cd93b"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84871a243359bb42c12728f04d181a389718710129b36b6aad0fc4655a7647d4"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5eafe2c065df5401ba06821b9a054d9cb2848867f3c59801b5d07a0be3a380ae"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9d3c9b50f19704552f23b4eaea1fc082fdd82c63429a6506446cbd8737823da3"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:f033d80bc6283092613882dfe40419c6a6a1527e04fc69350e87a9df02bbc283"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:2c895a656dd7e061b2fd6bb77d971cc38f2afc277229ce7dd3552de8313a483e"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1f5a71d25cd8106eab05f8704cd9167b6e5187bcdf8f090a66c6d88b634802b4"}, + {file = "aiohttp-3.9.3-cp311-cp311-win32.whl", hash = "sha256:50fca156d718f8ced687a373f9e140c1bb765ca16e3d6f4fe116e3df7c05b2c5"}, + {file = "aiohttp-3.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:5fe9ce6c09668063b8447f85d43b8d1c4e5d3d7e92c63173e6180b2ac5d46dd8"}, + {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:38a19bc3b686ad55804ae931012f78f7a534cce165d089a2059f658f6c91fa60"}, + {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:770d015888c2a598b377bd2f663adfd947d78c0124cfe7b959e1ef39f5b13869"}, + {file = "aiohttp-3.9.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee43080e75fc92bf36219926c8e6de497f9b247301bbf88c5c7593d931426679"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52df73f14ed99cee84865b95a3d9e044f226320a87af208f068ecc33e0c35b96"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc9b311743a78043b26ffaeeb9715dc360335e5517832f5a8e339f8a43581e4d"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b955ed993491f1a5da7f92e98d5dad3c1e14dc175f74517c4e610b1f2456fb11"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:504b6981675ace64c28bf4a05a508af5cde526e36492c98916127f5a02354d53"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6fe5571784af92b6bc2fda8d1925cccdf24642d49546d3144948a6a1ed58ca5"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ba39e9c8627edc56544c8628cc180d88605df3892beeb2b94c9bc857774848ca"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e5e46b578c0e9db71d04c4b506a2121c0cb371dd89af17a0586ff6769d4c58c1"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:938a9653e1e0c592053f815f7028e41a3062e902095e5a7dc84617c87267ebd5"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:c3452ea726c76e92f3b9fae4b34a151981a9ec0a4847a627c43d71a15ac32aa6"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ff30218887e62209942f91ac1be902cc80cddb86bf00fbc6783b7a43b2bea26f"}, + {file = "aiohttp-3.9.3-cp312-cp312-win32.whl", hash = "sha256:38f307b41e0bea3294a9a2a87833191e4bcf89bb0365e83a8be3a58b31fb7f38"}, + {file = "aiohttp-3.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:b791a3143681a520c0a17e26ae7465f1b6f99461a28019d1a2f425236e6eedb5"}, + {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ed621426d961df79aa3b963ac7af0d40392956ffa9be022024cd16297b30c8c"}, + {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7f46acd6a194287b7e41e87957bfe2ad1ad88318d447caf5b090012f2c5bb528"}, + {file = "aiohttp-3.9.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feeb18a801aacb098220e2c3eea59a512362eb408d4afd0c242044c33ad6d542"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f734e38fd8666f53da904c52a23ce517f1b07722118d750405af7e4123933511"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b40670ec7e2156d8e57f70aec34a7216407848dfe6c693ef131ddf6e76feb672"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fdd215b7b7fd4a53994f238d0f46b7ba4ac4c0adb12452beee724ddd0743ae5d"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:017a21b0df49039c8f46ca0971b3a7fdc1f56741ab1240cb90ca408049766168"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e99abf0bba688259a496f966211c49a514e65afa9b3073a1fcee08856e04425b"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:648056db9a9fa565d3fa851880f99f45e3f9a771dd3ff3bb0c048ea83fb28194"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8aacb477dc26797ee089721536a292a664846489c49d3ef9725f992449eda5a8"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:522a11c934ea660ff8953eda090dcd2154d367dec1ae3c540aff9f8a5c109ab4"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5bce0dc147ca85caa5d33debc4f4d65e8e8b5c97c7f9f660f215fa74fc49a321"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b4af9f25b49a7be47c0972139e59ec0e8285c371049df1a63b6ca81fdd216a2"}, + {file = "aiohttp-3.9.3-cp38-cp38-win32.whl", hash = "sha256:298abd678033b8571995650ccee753d9458dfa0377be4dba91e4491da3f2be63"}, + {file = "aiohttp-3.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:69361bfdca5468c0488d7017b9b1e5ce769d40b46a9f4a2eed26b78619e9396c"}, + {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0fa43c32d1643f518491d9d3a730f85f5bbaedcbd7fbcae27435bb8b7a061b29"}, + {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:835a55b7ca49468aaaac0b217092dfdff370e6c215c9224c52f30daaa735c1c1"}, + {file = "aiohttp-3.9.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06a9b2c8837d9a94fae16c6223acc14b4dfdff216ab9b7202e07a9a09541168f"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abf151955990d23f84205286938796c55ff11bbfb4ccfada8c9c83ae6b3c89a3"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59c26c95975f26e662ca78fdf543d4eeaef70e533a672b4113dd888bd2423caa"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f95511dd5d0e05fd9728bac4096319f80615aaef4acbecb35a990afebe953b0e"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:595f105710293e76b9dc09f52e0dd896bd064a79346234b521f6b968ffdd8e58"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7c8b816c2b5af5c8a436df44ca08258fc1a13b449393a91484225fcb7545533"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f1088fa100bf46e7b398ffd9904f4808a0612e1d966b4aa43baa535d1b6341eb"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f59dfe57bb1ec82ac0698ebfcdb7bcd0e99c255bd637ff613760d5f33e7c81b3"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:361a1026c9dd4aba0109e4040e2aecf9884f5cfe1b1b1bd3d09419c205e2e53d"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:363afe77cfcbe3a36353d8ea133e904b108feea505aa4792dad6585a8192c55a"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e2c45c208c62e955e8256949eb225bd8b66a4c9b6865729a786f2aa79b72e9d"}, + {file = "aiohttp-3.9.3-cp39-cp39-win32.whl", hash = "sha256:f7217af2e14da0856e082e96ff637f14ae45c10a5714b63c77f26d8884cf1051"}, + {file = "aiohttp-3.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:27468897f628c627230dba07ec65dc8d0db566923c48f29e084ce382119802bc"}, + {file = "aiohttp-3.9.3.tar.gz", hash = "sha256:90842933e5d1ff760fae6caca4b2b3edba53ba8f4b71e95dacf2818a2aca06f7"}, +] + +[package.dependencies] +aiosignal = ">=1.1.2" +async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""} +attrs = ">=17.3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["Brotli", "aiodns", "brotlicffi"] + +[[package]] +name = "aiohttp-json-rpc" +version = "0.13.3" +description = "Implementation JSON-RPC 2.0 server and client using aiohttp on top of websockets transport" +optional = false +python-versions = ">=3.5" +files = [ + {file = "aiohttp-json-rpc-0.13.3.tar.gz", hash = "sha256:6237a104478c22c6ef96c7227a01d6832597b414e4b79a52d85593356a169e99"}, + {file = "aiohttp_json_rpc-0.13.3-py3-none-any.whl", hash = "sha256:4fbd197aced61bd2df7ae3237ead7d3e08833c2ccf48b8581e1828c95ebee680"}, +] + +[package.dependencies] +aiohttp = ">=3,<4" + +[[package]] +name = "aiohttp-middlewares" +version = "2.3.0" +description = "Collection of useful middlewares for aiohttp applications." +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "aiohttp_middlewares-2.3.0-py3-none-any.whl", hash = "sha256:4424b136a351b67b4c93da7d7505b56a342addaa324be30793f6cba463d18ac8"}, + {file = "aiohttp_middlewares-2.3.0.tar.gz", hash = "sha256:b2564c1dfa8dbcf7d2e101a6a03dcaad45464744531c269e8e582cb2dc551d08"}, +] + +[package.dependencies] +aiohttp = ">=3.8.1,<4.0.0" +async-timeout = ">=4.0.2,<5.0.0" +yarl = ">=1.5.1,<2.0.0" + +[[package]] +name = "aiosignal" +version = "1.3.1" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = false +python-versions = ">=3.7" +files = [ + {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, + {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" + +[[package]] +name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = "*" +files = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] + +[[package]] +name = "arrow" +version = "1.3.0" +description = "Better dates & times for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80"}, + {file = "arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85"}, +] + +[package.dependencies] +python-dateutil = ">=2.7.0" +types-python-dateutil = ">=2.8.10" + +[package.extras] +doc = ["doc8", "sphinx (>=7.0.0)", "sphinx-autobuild", "sphinx-autodoc-typehints", "sphinx_rtd_theme (>=1.3.0)"] +test = ["dateparser (==1.*)", "pre-commit", "pytest", "pytest-cov", "pytest-mock", "pytz (==2021.1)", "simplejson (==3.*)"] + +[[package]] +name = "async-timeout" +version = "4.0.3" +description = "Timeout context manager for asyncio programs" +optional = false +python-versions = ">=3.7" +files = [ + {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, + {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, +] + +[[package]] +name = "attrs" +version = "23.2.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, + {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] + +[[package]] +name = "ayon-python-api" +version = "1.0.1" +description = "AYON Python API" +optional = false +python-versions = "*" +files = [ + {file = "ayon-python-api-1.0.1.tar.gz", hash = "sha256:6a53af84903317e2097f3c6bba0094e90d905d6670fb9c7d3ad3aa9de6552bc1"}, + {file = "ayon_python_api-1.0.1-py3-none-any.whl", hash = "sha256:d4b649ac39c9003cdbd60f172c0d35f05d310fba3a0649b6d16300fe67f967d6"}, +] + +[package.dependencies] +appdirs = ">=1,<2" +requests = ">=2.27.1" +six = ">=1.15" +Unidecode = ">=1.2.0" + +[[package]] +name = "certifi" +version = "2024.2.2" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, +] + +[[package]] +name = "cfgv" +version = "3.4.0" +description = "Validate configuration and produce human readable error messages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "clique" +version = "1.6.1" +description = "Manage collections with common numerical component" +optional = false +python-versions = ">=2.7, <4.0" +files = [ + {file = "clique-1.6.1-py2.py3-none-any.whl", hash = "sha256:8619774fa035661928dd8c93cd805acf2d42533ccea1b536c09815ed426c9858"}, + {file = "clique-1.6.1.tar.gz", hash = "sha256:90165c1cf162d4dd1baef83ceaa1afc886b453e379094fa5b60ea470d1733e66"}, +] + +[package.extras] +dev = ["lowdown (>=0.2.0,<1)", "pytest (>=2.3.5,<5)", "pytest-cov (>=2,<3)", "pytest-runner (>=2.7,<3)", "sphinx (>=2,<4)", "sphinx-rtd-theme (>=0.1.6,<1)"] +doc = ["lowdown (>=0.2.0,<1)", "sphinx (>=2,<4)", "sphinx-rtd-theme (>=0.1.6,<1)"] +test = ["pytest (>=2.3.5,<5)", "pytest-cov (>=2,<3)", "pytest-runner (>=2.7,<3)"] + +[[package]] +name = "codespell" +version = "2.2.6" +description = "Codespell" +optional = false +python-versions = ">=3.8" +files = [ + {file = "codespell-2.2.6-py3-none-any.whl", hash = "sha256:9ee9a3e5df0990604013ac2a9f22fa8e57669c827124a2e961fe8a1da4cacc07"}, + {file = "codespell-2.2.6.tar.gz", hash = "sha256:a8c65d8eb3faa03deabab6b3bbe798bea72e1799c7e9e955d57eca4096abcff9"}, +] + +[package.extras] +dev = ["Pygments", "build", "chardet", "pre-commit", "pytest", "pytest-cov", "pytest-dependency", "ruff", "tomli", "twine"] +hard-encoding-detection = ["chardet"] +toml = ["tomli"] +types = ["chardet (>=5.1.0)", "mypy", "pytest", "pytest-cov", "pytest-dependency"] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "distlib" +version = "0.3.8" +description = "Distribution utilities" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, + {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, +] + +[[package]] +name = "evdev" +version = "1.7.0" +description = "Bindings to the Linux input handling subsystem" +optional = false +python-versions = ">=3.6" +files = [ + {file = "evdev-1.7.0.tar.gz", hash = "sha256:95bd2a1e0c6ce2cd7a2ecc6e6cd9736ff794b3ad5cb54d81d8cbc2e414d0b870"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.0" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, + {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "filelock" +version = "3.13.1" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.8" +files = [ + {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, + {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] +typing = ["typing-extensions (>=4.8)"] + +[[package]] +name = "frozenlist" +version = "1.4.1" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.8" +files = [ + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, + {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, + {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, + {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, + {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, + {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, + {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, + {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, + {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, + {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, + {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, + {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, + {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, +] + +[[package]] +name = "identify" +version = "2.5.35" +description = "File identification library for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "identify-2.5.35-py2.py3-none-any.whl", hash = "sha256:c4de0081837b211594f8e877a6b4fad7ca32bbfc1a9307fdd61c28bfe923f13e"}, + {file = "identify-2.5.35.tar.gz", hash = "sha256:10a7ca245cfcd756a554a7288159f72ff105ad233c7c4b9c6f0f4d108f5f6791"}, +] + +[package.extras] +license = ["ukkonen"] + +[[package]] +name = "idna" +version = "3.6" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "jsonschema" +version = "4.21.1" +description = "An implementation of JSON Schema validation for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jsonschema-4.21.1-py3-none-any.whl", hash = "sha256:7996507afae316306f9e2290407761157c6f78002dcf7419acb99822143d1c6f"}, + {file = "jsonschema-4.21.1.tar.gz", hash = "sha256:85727c00279f5fa6bedbe6238d2aa6403bedd8b4864ab11207d07df3cc1b2ee5"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +jsonschema-specifications = ">=2023.03.6" +referencing = ">=0.28.4" +rpds-py = ">=0.7.1" + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"] + +[[package]] +name = "jsonschema-specifications" +version = "2023.12.1" +description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jsonschema_specifications-2023.12.1-py3-none-any.whl", hash = "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c"}, + {file = "jsonschema_specifications-2023.12.1.tar.gz", hash = "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc"}, +] + +[package.dependencies] +referencing = ">=0.31.0" + +[[package]] +name = "multidict" +version = "6.0.5" +description = "multidict implementation" +optional = false +python-versions = ">=3.7" +files = [ + {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc"}, + {file = "multidict-6.0.5-cp310-cp310-win32.whl", hash = "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319"}, + {file = "multidict-6.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"}, + {file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"}, + {file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"}, + {file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"}, + {file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"}, + {file = "multidict-6.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc"}, + {file = "multidict-6.0.5-cp37-cp37m-win32.whl", hash = "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee"}, + {file = "multidict-6.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44"}, + {file = "multidict-6.0.5-cp38-cp38-win32.whl", hash = "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241"}, + {file = "multidict-6.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c"}, + {file = "multidict-6.0.5-cp39-cp39-win32.whl", hash = "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b"}, + {file = "multidict-6.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755"}, + {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"}, + {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, +] + +[[package]] +name = "nodeenv" +version = "1.8.0" +description = "Node.js virtual environment builder" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +files = [ + {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, + {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, +] + +[package.dependencies] +setuptools = "*" + +[[package]] +name = "packaging" +version = "24.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, +] + +[[package]] +name = "platformdirs" +version = "4.2.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, + {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] + +[[package]] +name = "pluggy" +version = "1.4.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pre-commit" +version = "3.6.2" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +optional = false +python-versions = ">=3.9" +files = [ + {file = "pre_commit-3.6.2-py2.py3-none-any.whl", hash = "sha256:ba637c2d7a670c10daedc059f5c49b5bd0aadbccfcd7ec15592cf9665117532c"}, + {file = "pre_commit-3.6.2.tar.gz", hash = "sha256:c3ef34f463045c88658c5b99f38c1e297abdcc0ff13f98d3370055fbbfabc67e"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + +[[package]] +name = "pyblish-base" +version = "1.8.11" +description = "Plug-in driven automation framework for content" +optional = false +python-versions = "*" +files = [ + {file = "pyblish-base-1.8.11.tar.gz", hash = "sha256:86dfeec0567430eb7eb25f89a18312054147a729ec66f6ac8c7e421fd15b66e1"}, + {file = "pyblish_base-1.8.11-py2.py3-none-any.whl", hash = "sha256:c321be7020c946fe9dfa11941241bd985a572c5009198b4f9810e5afad1f0b4b"}, +] + +[[package]] +name = "pynput" +version = "1.7.6" +description = "Monitor and control user input devices" +optional = false +python-versions = "*" +files = [ + {file = "pynput-1.7.6-py2.py3-none-any.whl", hash = "sha256:19861b2a0c430d646489852f89500e0c9332e295f2c020e7c2775e7046aa2e2f"}, + {file = "pynput-1.7.6.tar.gz", hash = "sha256:3a5726546da54116b687785d38b1db56997ce1d28e53e8d22fc656d8b92e533c"}, +] + +[package.dependencies] +evdev = {version = ">=1.3", markers = "sys_platform in \"linux\""} +pyobjc-framework-ApplicationServices = {version = ">=8.0", markers = "sys_platform == \"darwin\""} +pyobjc-framework-Quartz = {version = ">=8.0", markers = "sys_platform == \"darwin\""} +python-xlib = {version = ">=0.17", markers = "sys_platform in \"linux\""} +six = "*" + +[[package]] +name = "pyobjc-core" +version = "10.2" +description = "Python<->ObjC Interoperability Module" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-core-10.2.tar.gz", hash = "sha256:0153206e15d0e0d7abd53ee8a7fbaf5606602a032e177a028fc8589516a8771c"}, + {file = "pyobjc_core-10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b8eab50ce7f17017a0f1d68c3b7e88bb1bb033415fdff62b8e0a9ee4ab72f242"}, + {file = "pyobjc_core-10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f2115971463073426ab926416e17e5c16de5b90d1a1f2a2d8724637eb1c21308"}, + {file = "pyobjc_core-10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a70546246177c23acb323c9324330e37638f1a0a3d13664abcba3bb75e43012c"}, + {file = "pyobjc_core-10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a9b5a215080d13bd7526031d21d5eb27a410780878d863f486053a0eba7ca9a5"}, + {file = "pyobjc_core-10.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:eb1ab700a44bcc4ceb125091dfaae0b998b767b49990df5fdc83eb58158d8e3f"}, + {file = "pyobjc_core-10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c9a7163aff9c47d654f835f80361c1b112886ec754800d34e75d1e02ff52c3d7"}, +] + +[[package]] +name = "pyobjc-framework-applicationservices" +version = "10.2" +description = "Wrappers for the framework ApplicationServices on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ApplicationServices-10.2.tar.gz", hash = "sha256:f83d6ed3320afb6648be6defafe0f05bac00d0281fc84ee4766ff977309b659f"}, + {file = "pyobjc_framework_ApplicationServices-10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2aebfed888f9bcb4f11d93f9ef9a76d561e92848dcb6011da5d5e9d3593371be"}, + {file = "pyobjc_framework_ApplicationServices-10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:edfd3153e64ee9573bcff7ccaa1fbbbd6964658f187464c461ad34f24552bc85"}, + {file = "pyobjc_framework_ApplicationServices-10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9d2c89b246c19a041221ff36e9121c92e86a4422016f809a40f5ce3d647882d9"}, + {file = "pyobjc_framework_ApplicationServices-10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ee1e69947f31aad5fdec44921ce37f7f921faf50a0ceb27ed40b6d54f4b15d0e"}, + {file = "pyobjc_framework_ApplicationServices-10.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:101f5b09d71e55bd39e6e91f0787433805d422622336b72fde969a7c54528045"}, + {file = "pyobjc_framework_ApplicationServices-10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a3ef00c9aea09c5ef5840b8749d0753249869bc30e124145b763cd0b4b81155"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-CoreText = ">=10.2" +pyobjc-framework-Quartz = ">=10.2" + +[[package]] +name = "pyobjc-framework-cocoa" +version = "10.2" +description = "Wrappers for the Cocoa frameworks on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Cocoa-10.2.tar.gz", hash = "sha256:6383141379636b13855dca1b39c032752862b829f93a49d7ddb35046abfdc035"}, + {file = "pyobjc_framework_Cocoa-10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9227b4f271fda2250f5a88cbc686ff30ae02c0f923bb7854bb47972397496b2"}, + {file = "pyobjc_framework_Cocoa-10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6a6042b7703bdc33b7491959c715c1e810a3f8c7a560c94b36e00ef321480797"}, + {file = "pyobjc_framework_Cocoa-10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:18886d5013cd7dc7ecd6e0df5134c767569b5247fc10a5e293c72ee3937b217b"}, + {file = "pyobjc_framework_Cocoa-10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ecf01400ee698d2e0ff4c907bcf9608d9d710e97203fbb97b37d208507a9362"}, + {file = "pyobjc_framework_Cocoa-10.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:0def036a7b24e3ae37a244c77bec96b7c9c8384bf6bb4d33369f0a0c8807a70d"}, + {file = "pyobjc_framework_Cocoa-10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5f47ecc393bc1019c4b47e8653207188df784ac006ad54d8c2eb528906ff7013"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" + +[[package]] +name = "pyobjc-framework-coretext" +version = "10.2" +description = "Wrappers for the framework CoreText on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreText-10.2.tar.gz", hash = "sha256:59ef8ca8d88bb53ce9980dda0b8094daa3e2dabe355847365ba965ff0b49f961"}, + {file = "pyobjc_framework_CoreText-10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:44052f752f42b62d342fa8aced5d1b8928831e70830eccddc594726d40500d5c"}, + {file = "pyobjc_framework_CoreText-10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0bc278f509a3fd3eea89124d81e77de11af10167c0df0d0cc15a369f060465a0"}, + {file = "pyobjc_framework_CoreText-10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7b819119dc859e49c0ce9040ae09d6a3bd66658003793f486ef5a21e46a2d34f"}, + {file = "pyobjc_framework_CoreText-10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2719c57ff08af6e4fdcddd0fa5eda56113808a1690c3325f1c6926740817f9a1"}, + {file = "pyobjc_framework_CoreText-10.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:8239ce92f9496587a60fc1bfd4994136832bad99405bb45572f92d960cbe746e"}, + {file = "pyobjc_framework_CoreText-10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:80a1d207fcdb2999841daa430c83d760ac1a3f2f65c605949fc5ff789425b1f6"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-Quartz = ">=10.2" + +[[package]] +name = "pyobjc-framework-quartz" +version = "10.2" +description = "Wrappers for the Quartz frameworks on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Quartz-10.2.tar.gz", hash = "sha256:9b947e081f5bd6cd01c99ab5d62c36500d2d6e8d3b87421c1cbb7f9c885555eb"}, + {file = "pyobjc_framework_Quartz-10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bc0ab739259a717d9d13a739434991b54eb8963ad7c27f9f6d04d68531fb479b"}, + {file = "pyobjc_framework_Quartz-10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2a74d00e933c1e1a1820839323dc5cf252bee8bb98e2a298d961f7ae7905ce71"}, + {file = "pyobjc_framework_Quartz-10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3e8e33246d966c2bd7f5ee2cf3b431582fa434a6ec2b6dbe580045ebf1f55be5"}, + {file = "pyobjc_framework_Quartz-10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c6ca490eff1be0dd8dc7726edde79c97e21ec1afcf55f75962a79e27b4eb2961"}, + {file = "pyobjc_framework_Quartz-10.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d3d54d9fa50de09ee8994248151def58f30b4738eb20755b0bdd5ee1e1f5883d"}, + {file = "pyobjc_framework_Quartz-10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:520c8031b2389110f80070b078dde1968caaecb10921f8070046c26132ac9286"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pytest" +version = "8.1.1" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.1.1-py3-none-any.whl", hash = "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7"}, + {file = "pytest-8.1.1.tar.gz", hash = "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.4,<2.0" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-print" +version = "1.0.0" +description = "pytest-print adds the printer fixture you can use to print messages to the user (directly to the pytest runner, not stdout)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest_print-1.0.0-py3-none-any.whl", hash = "sha256:23484f42b906b87e31abd564761efffeb0348a6f83109fb857ee6e8e5df42b69"}, + {file = "pytest_print-1.0.0.tar.gz", hash = "sha256:1fcde9945fba462227a8959271369b10bb7a193be8452162707e63cd60875ca0"}, +] + +[package.dependencies] +pytest = ">=7.4" + +[package.extras] +test = ["covdefaults (>=2.3)", "coverage (>=7.3)", "pytest-mock (>=3.11.1)"] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-xlib" +version = "0.33" +description = "Python X Library" +optional = false +python-versions = "*" +files = [ + {file = "python-xlib-0.33.tar.gz", hash = "sha256:55af7906a2c75ce6cb280a584776080602444f75815a7aff4d287bb2d7018b32"}, + {file = "python_xlib-0.33-py2.py3-none-any.whl", hash = "sha256:c3534038d42e0df2f1392a1b30a15a4ff5fdc2b86cfa94f072bf11b10a164398"}, +] + +[package.dependencies] +six = ">=1.10.0" + +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + +[[package]] +name = "qtawesome" +version = "0.7.3" +description = "FontAwesome icons in PyQt and PySide applications" +optional = false +python-versions = "*" +files = [ + {file = "QtAwesome-0.7.3-py2.py3-none-any.whl", hash = "sha256:ddf4530b4af71cec13b24b88a4cdb56ec85b1e44c43c42d0698804c7137b09b0"}, + {file = "QtAwesome-0.7.3.tar.gz", hash = "sha256:b98b9038d19190e83ab26d91c4d8fc3a36591ee2bc7f5016d4438b8240d097bd"}, +] + +[package.dependencies] +qtpy = "*" +six = "*" + +[[package]] +name = "qtpy" +version = "2.4.1" +description = "Provides an abstraction layer on top of the various Qt bindings (PyQt5/6 and PySide2/6)." +optional = false +python-versions = ">=3.7" +files = [ + {file = "QtPy-2.4.1-py3-none-any.whl", hash = "sha256:1c1d8c4fa2c884ae742b069151b0abe15b3f70491f3972698c683b8e38de839b"}, + {file = "QtPy-2.4.1.tar.gz", hash = "sha256:a5a15ffd519550a1361bdc56ffc07fda56a6af7292f17c7b395d4083af632987"}, +] + +[package.dependencies] +packaging = "*" + +[package.extras] +test = ["pytest (>=6,!=7.0.0,!=7.0.1)", "pytest-cov (>=3.0.0)", "pytest-qt"] + +[[package]] +name = "referencing" +version = "0.34.0" +description = "JSON Referencing + Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "referencing-0.34.0-py3-none-any.whl", hash = "sha256:d53ae300ceddd3169f1ffa9caf2cb7b769e92657e4fafb23d34b93679116dfd4"}, + {file = "referencing-0.34.0.tar.gz", hash = "sha256:5773bd84ef41799a5a8ca72dc34590c041eb01bf9aa02632b4a973fb0181a844"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +rpds-py = ">=0.7.0" + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "rpds-py" +version = "0.18.0" +description = "Python bindings to Rust's persistent data structures (rpds)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "rpds_py-0.18.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:5b4e7d8d6c9b2e8ee2d55c90b59c707ca59bc30058269b3db7b1f8df5763557e"}, + {file = "rpds_py-0.18.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c463ed05f9dfb9baebef68048aed8dcdc94411e4bf3d33a39ba97e271624f8f7"}, + {file = "rpds_py-0.18.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01e36a39af54a30f28b73096dd39b6802eddd04c90dbe161c1b8dbe22353189f"}, + {file = "rpds_py-0.18.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d62dec4976954a23d7f91f2f4530852b0c7608116c257833922a896101336c51"}, + {file = "rpds_py-0.18.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd18772815d5f008fa03d2b9a681ae38d5ae9f0e599f7dda233c439fcaa00d40"}, + {file = "rpds_py-0.18.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:923d39efa3cfb7279a0327e337a7958bff00cc447fd07a25cddb0a1cc9a6d2da"}, + {file = "rpds_py-0.18.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39514da80f971362f9267c600b6d459bfbbc549cffc2cef8e47474fddc9b45b1"}, + {file = "rpds_py-0.18.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a34d557a42aa28bd5c48a023c570219ba2593bcbbb8dc1b98d8cf5d529ab1434"}, + {file = "rpds_py-0.18.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:93df1de2f7f7239dc9cc5a4a12408ee1598725036bd2dedadc14d94525192fc3"}, + {file = "rpds_py-0.18.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:34b18ba135c687f4dac449aa5157d36e2cbb7c03cbea4ddbd88604e076aa836e"}, + {file = "rpds_py-0.18.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c0b5dcf9193625afd8ecc92312d6ed78781c46ecbf39af9ad4681fc9f464af88"}, + {file = "rpds_py-0.18.0-cp310-none-win32.whl", hash = "sha256:c4325ff0442a12113a6379af66978c3fe562f846763287ef66bdc1d57925d337"}, + {file = "rpds_py-0.18.0-cp310-none-win_amd64.whl", hash = "sha256:7223a2a5fe0d217e60a60cdae28d6949140dde9c3bcc714063c5b463065e3d66"}, + {file = "rpds_py-0.18.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3a96e0c6a41dcdba3a0a581bbf6c44bb863f27c541547fb4b9711fd8cf0ffad4"}, + {file = "rpds_py-0.18.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30f43887bbae0d49113cbaab729a112251a940e9b274536613097ab8b4899cf6"}, + {file = "rpds_py-0.18.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fcb25daa9219b4cf3a0ab24b0eb9a5cc8949ed4dc72acb8fa16b7e1681aa3c58"}, + {file = "rpds_py-0.18.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d68c93e381010662ab873fea609bf6c0f428b6d0bb00f2c6939782e0818d37bf"}, + {file = "rpds_py-0.18.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b34b7aa8b261c1dbf7720b5d6f01f38243e9b9daf7e6b8bc1fd4657000062f2c"}, + {file = "rpds_py-0.18.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e6d75ab12b0bbab7215e5d40f1e5b738aa539598db27ef83b2ec46747df90e1"}, + {file = "rpds_py-0.18.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b8612cd233543a3781bc659c731b9d607de65890085098986dfd573fc2befe5"}, + {file = "rpds_py-0.18.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:aec493917dd45e3c69d00a8874e7cbed844efd935595ef78a0f25f14312e33c6"}, + {file = "rpds_py-0.18.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:661d25cbffaf8cc42e971dd570d87cb29a665f49f4abe1f9e76be9a5182c4688"}, + {file = "rpds_py-0.18.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1df3659d26f539ac74fb3b0c481cdf9d725386e3552c6fa2974f4d33d78e544b"}, + {file = "rpds_py-0.18.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a1ce3ba137ed54f83e56fb983a5859a27d43a40188ba798993812fed73c70836"}, + {file = "rpds_py-0.18.0-cp311-none-win32.whl", hash = "sha256:69e64831e22a6b377772e7fb337533c365085b31619005802a79242fee620bc1"}, + {file = "rpds_py-0.18.0-cp311-none-win_amd64.whl", hash = "sha256:998e33ad22dc7ec7e030b3df701c43630b5bc0d8fbc2267653577e3fec279afa"}, + {file = "rpds_py-0.18.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7f2facbd386dd60cbbf1a794181e6aa0bd429bd78bfdf775436020172e2a23f0"}, + {file = "rpds_py-0.18.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1d9a5be316c15ffb2b3c405c4ff14448c36b4435be062a7f578ccd8b01f0c4d8"}, + {file = "rpds_py-0.18.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd5bf1af8efe569654bbef5a3e0a56eca45f87cfcffab31dd8dde70da5982475"}, + {file = "rpds_py-0.18.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5417558f6887e9b6b65b4527232553c139b57ec42c64570569b155262ac0754f"}, + {file = "rpds_py-0.18.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:56a737287efecafc16f6d067c2ea0117abadcd078d58721f967952db329a3e5c"}, + {file = "rpds_py-0.18.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8f03bccbd8586e9dd37219bce4d4e0d3ab492e6b3b533e973fa08a112cb2ffc9"}, + {file = "rpds_py-0.18.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4457a94da0d5c53dc4b3e4de1158bdab077db23c53232f37a3cb7afdb053a4e3"}, + {file = "rpds_py-0.18.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0ab39c1ba9023914297dd88ec3b3b3c3f33671baeb6acf82ad7ce883f6e8e157"}, + {file = "rpds_py-0.18.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9d54553c1136b50fd12cc17e5b11ad07374c316df307e4cfd6441bea5fb68496"}, + {file = "rpds_py-0.18.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0af039631b6de0397ab2ba16eaf2872e9f8fca391b44d3d8cac317860a700a3f"}, + {file = "rpds_py-0.18.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:84ffab12db93b5f6bad84c712c92060a2d321b35c3c9960b43d08d0f639d60d7"}, + {file = "rpds_py-0.18.0-cp312-none-win32.whl", hash = "sha256:685537e07897f173abcf67258bee3c05c374fa6fff89d4c7e42fb391b0605e98"}, + {file = "rpds_py-0.18.0-cp312-none-win_amd64.whl", hash = "sha256:e003b002ec72c8d5a3e3da2989c7d6065b47d9eaa70cd8808b5384fbb970f4ec"}, + {file = "rpds_py-0.18.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:08f9ad53c3f31dfb4baa00da22f1e862900f45908383c062c27628754af2e88e"}, + {file = "rpds_py-0.18.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c0013fe6b46aa496a6749c77e00a3eb07952832ad6166bd481c74bda0dcb6d58"}, + {file = "rpds_py-0.18.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e32a92116d4f2a80b629778280103d2a510a5b3f6314ceccd6e38006b5e92dcb"}, + {file = "rpds_py-0.18.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e541ec6f2ec456934fd279a3120f856cd0aedd209fc3852eca563f81738f6861"}, + {file = "rpds_py-0.18.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bed88b9a458e354014d662d47e7a5baafd7ff81c780fd91584a10d6ec842cb73"}, + {file = "rpds_py-0.18.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2644e47de560eb7bd55c20fc59f6daa04682655c58d08185a9b95c1970fa1e07"}, + {file = "rpds_py-0.18.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e8916ae4c720529e18afa0b879473049e95949bf97042e938530e072fde061d"}, + {file = "rpds_py-0.18.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:465a3eb5659338cf2a9243e50ad9b2296fa15061736d6e26240e713522b6235c"}, + {file = "rpds_py-0.18.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ea7d4a99f3b38c37eac212dbd6ec42b7a5ec51e2c74b5d3223e43c811609e65f"}, + {file = "rpds_py-0.18.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:67071a6171e92b6da534b8ae326505f7c18022c6f19072a81dcf40db2638767c"}, + {file = "rpds_py-0.18.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:41ef53e7c58aa4ef281da975f62c258950f54b76ec8e45941e93a3d1d8580594"}, + {file = "rpds_py-0.18.0-cp38-none-win32.whl", hash = "sha256:fdea4952db2793c4ad0bdccd27c1d8fdd1423a92f04598bc39425bcc2b8ee46e"}, + {file = "rpds_py-0.18.0-cp38-none-win_amd64.whl", hash = "sha256:7cd863afe7336c62ec78d7d1349a2f34c007a3cc6c2369d667c65aeec412a5b1"}, + {file = "rpds_py-0.18.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:5307def11a35f5ae4581a0b658b0af8178c65c530e94893345bebf41cc139d33"}, + {file = "rpds_py-0.18.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:77f195baa60a54ef9d2de16fbbfd3ff8b04edc0c0140a761b56c267ac11aa467"}, + {file = "rpds_py-0.18.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39f5441553f1c2aed4de4377178ad8ff8f9d733723d6c66d983d75341de265ab"}, + {file = "rpds_py-0.18.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a00312dea9310d4cb7dbd7787e722d2e86a95c2db92fbd7d0155f97127bcb40"}, + {file = "rpds_py-0.18.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f2fc11e8fe034ee3c34d316d0ad8808f45bc3b9ce5857ff29d513f3ff2923a1"}, + {file = "rpds_py-0.18.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:586f8204935b9ec884500498ccc91aa869fc652c40c093bd9e1471fbcc25c022"}, + {file = "rpds_py-0.18.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddc2f4dfd396c7bfa18e6ce371cba60e4cf9d2e5cdb71376aa2da264605b60b9"}, + {file = "rpds_py-0.18.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ddcba87675b6d509139d1b521e0c8250e967e63b5909a7e8f8944d0f90ff36f"}, + {file = "rpds_py-0.18.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7bd339195d84439cbe5771546fe8a4e8a7a045417d8f9de9a368c434e42a721e"}, + {file = "rpds_py-0.18.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:d7c36232a90d4755b720fbd76739d8891732b18cf240a9c645d75f00639a9024"}, + {file = "rpds_py-0.18.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6b0817e34942b2ca527b0e9298373e7cc75f429e8da2055607f4931fded23e20"}, + {file = "rpds_py-0.18.0-cp39-none-win32.whl", hash = "sha256:99f70b740dc04d09e6b2699b675874367885217a2e9f782bdf5395632ac663b7"}, + {file = "rpds_py-0.18.0-cp39-none-win_amd64.whl", hash = "sha256:6ef687afab047554a2d366e112dd187b62d261d49eb79b77e386f94644363294"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ad36cfb355e24f1bd37cac88c112cd7730873f20fb0bdaf8ba59eedf8216079f"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:36b3ee798c58ace201289024b52788161e1ea133e4ac93fba7d49da5fec0ef9e"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8a2f084546cc59ea99fda8e070be2fd140c3092dc11524a71aa8f0f3d5a55ca"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e4461d0f003a0aa9be2bdd1b798a041f177189c1a0f7619fe8c95ad08d9a45d7"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8db715ebe3bb7d86d77ac1826f7d67ec11a70dbd2376b7cc214199360517b641"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:793968759cd0d96cac1e367afd70c235867831983f876a53389ad869b043c948"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66e6a3af5a75363d2c9a48b07cb27c4ea542938b1a2e93b15a503cdfa8490795"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6ef0befbb5d79cf32d0266f5cff01545602344eda89480e1dd88aca964260b18"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:1d4acf42190d449d5e89654d5c1ed3a4f17925eec71f05e2a41414689cda02d1"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:a5f446dd5055667aabaee78487f2b5ab72e244f9bc0b2ffebfeec79051679984"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:9dbbeb27f4e70bfd9eec1be5477517365afe05a9b2c441a0b21929ee61048124"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:22806714311a69fd0af9b35b7be97c18a0fc2826e6827dbb3a8c94eac6cf7eeb"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:b34ae4636dfc4e76a438ab826a0d1eed2589ca7d9a1b2d5bb546978ac6485461"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c8370641f1a7f0e0669ddccca22f1da893cef7628396431eb445d46d893e5cd"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c8362467a0fdeccd47935f22c256bec5e6abe543bf0d66e3d3d57a8fb5731863"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11a8c85ef4a07a7638180bf04fe189d12757c696eb41f310d2426895356dcf05"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b316144e85316da2723f9d8dc75bada12fa58489a527091fa1d5a612643d1a0e"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf1ea2e34868f6fbf070e1af291c8180480310173de0b0c43fc38a02929fc0e3"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e546e768d08ad55b20b11dbb78a745151acbd938f8f00d0cfbabe8b0199b9880"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:4901165d170a5fde6f589acb90a6b33629ad1ec976d4529e769c6f3d885e3e80"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:618a3d6cae6ef8ec88bb76dd80b83cfe415ad4f1d942ca2a903bf6b6ff97a2da"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ed4eb745efbff0a8e9587d22a84be94a5eb7d2d99c02dacf7bd0911713ed14dd"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:6c81e5f372cd0dc5dc4809553d34f832f60a46034a5f187756d9b90586c2c307"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:43fbac5f22e25bee1d482c97474f930a353542855f05c1161fd804c9dc74a09d"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d7faa6f14017c0b1e69f5e2c357b998731ea75a442ab3841c0dbbbfe902d2c4"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:08231ac30a842bd04daabc4d71fddd7e6d26189406d5a69535638e4dcb88fe76"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:044a3e61a7c2dafacae99d1e722cc2d4c05280790ec5a05031b3876809d89a5c"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3f26b5bd1079acdb0c7a5645e350fe54d16b17bfc5e71f371c449383d3342e17"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:482103aed1dfe2f3b71a58eff35ba105289b8d862551ea576bd15479aba01f66"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1374f4129f9bcca53a1bba0bb86bf78325a0374577cf7e9e4cd046b1e6f20e24"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:635dc434ff724b178cb192c70016cc0ad25a275228f749ee0daf0eddbc8183b1"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:bc362ee4e314870a70f4ae88772d72d877246537d9f8cb8f7eacf10884862432"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:4832d7d380477521a8c1644bbab6588dfedea5e30a7d967b5fb75977c45fd77f"}, + {file = "rpds_py-0.18.0.tar.gz", hash = "sha256:42821446ee7a76f5d9f71f9e33a4fb2ffd724bb3e7f93386150b61a43115788d"}, +] + +[[package]] +name = "ruff" +version = "0.2.2" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0a9efb032855ffb3c21f6405751d5e147b0c6b631e3ca3f6b20f917572b97eb6"}, + {file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d450b7fbff85913f866a5384d8912710936e2b96da74541c82c1b458472ddb39"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecd46e3106850a5c26aee114e562c329f9a1fbe9e4821b008c4404f64ff9ce73"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e22676a5b875bd72acd3d11d5fa9075d3a5f53b877fe7b4793e4673499318ba"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1695700d1e25a99d28f7a1636d85bafcc5030bba9d0578c0781ba1790dbcf51c"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b0c232af3d0bd8f521806223723456ffebf8e323bd1e4e82b0befb20ba18388e"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f63d96494eeec2fc70d909393bcd76c69f35334cdbd9e20d089fb3f0640216ca"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a61ea0ff048e06de273b2e45bd72629f470f5da8f71daf09fe481278b175001"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1439c8f407e4f356470e54cdecdca1bd5439a0673792dbe34a2b0a551a2fe3"}, + {file = "ruff-0.2.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:940de32dc8853eba0f67f7198b3e79bc6ba95c2edbfdfac2144c8235114d6726"}, + {file = "ruff-0.2.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0c126da55c38dd917621552ab430213bdb3273bb10ddb67bc4b761989210eb6e"}, + {file = "ruff-0.2.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3b65494f7e4bed2e74110dac1f0d17dc8e1f42faaa784e7c58a98e335ec83d7e"}, + {file = "ruff-0.2.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1ec49be4fe6ddac0503833f3ed8930528e26d1e60ad35c2446da372d16651ce9"}, + {file = "ruff-0.2.2-py3-none-win32.whl", hash = "sha256:d920499b576f6c68295bc04e7b17b6544d9d05f196bb3aac4358792ef6f34325"}, + {file = "ruff-0.2.2-py3-none-win_amd64.whl", hash = "sha256:cc9a91ae137d687f43a44c900e5d95e9617cb37d4c989e462980ba27039d239d"}, + {file = "ruff-0.2.2-py3-none-win_arm64.whl", hash = "sha256:c9d15fc41e6054bfc7200478720570078f0b41c9ae4f010bcc16bd6f4d1aacdd"}, + {file = "ruff-0.2.2.tar.gz", hash = "sha256:e62ed7f36b3068a30ba39193a14274cd706bc486fad521276458022f7bccb31d"}, +] + +[[package]] +name = "setuptools" +version = "69.2.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-69.2.0-py3-none-any.whl", hash = "sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c"}, + {file = "setuptools-69.2.0.tar.gz", hash = "sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "speedcopy" +version = "2.1.5" +description = "Replacement or alternative for python copyfile() utilizing server side copy on network shares for faster copying." +optional = false +python-versions = "*" +files = [ + {file = "speedcopy-2.1.5-py3-none-any.whl", hash = "sha256:903d0b466c2bef7c07dfac17493cdfbc09aadd70e947199c81caa6c6da2c095f"}, + {file = "speedcopy-2.1.5.tar.gz", hash = "sha256:9d6c482300791f02462ad451730ae247901978eae9e25290ca352de964698c82"}, +] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "types-python-dateutil" +version = "2.9.0.20240316" +description = "Typing stubs for python-dateutil" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-python-dateutil-2.9.0.20240316.tar.gz", hash = "sha256:5d2f2e240b86905e40944dd787db6da9263f0deabef1076ddaed797351ec0202"}, + {file = "types_python_dateutil-2.9.0.20240316-py3-none-any.whl", hash = "sha256:6b8cb66d960771ce5ff974e9dd45e38facb81718cc1e208b10b1baccbfdbee3b"}, +] + +[[package]] +name = "unidecode" +version = "1.3.8" +description = "ASCII transliterations of Unicode text" +optional = false +python-versions = ">=3.5" +files = [ + {file = "Unidecode-1.3.8-py3-none-any.whl", hash = "sha256:d130a61ce6696f8148a3bd8fe779c99adeb4b870584eeb9526584e9aa091fd39"}, + {file = "Unidecode-1.3.8.tar.gz", hash = "sha256:cfdb349d46ed3873ece4586b96aa75258726e2fa8ec21d6f00a591d98806c2f4"}, +] + +[[package]] +name = "urllib3" +version = "2.2.1" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, + {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "virtualenv" +version = "20.25.1" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.7" +files = [ + {file = "virtualenv-20.25.1-py3-none-any.whl", hash = "sha256:961c026ac520bac5f69acb8ea063e8a4f071bcc9457b9c1f28f6b085c511583a"}, + {file = "virtualenv-20.25.1.tar.gz", hash = "sha256:e08e13ecdca7a0bd53798f356d5831434afa5b07b93f0abdf0797b7a06ffe197"}, +] + +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<5" + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] + +[[package]] +name = "wsrpc-aiohttp" +version = "3.2.0" +description = "WSRPC is the RPC over WebSocket for aiohttp" +optional = false +python-versions = ">3.5.*, <4" +files = [ + {file = "wsrpc-aiohttp-3.2.0.tar.gz", hash = "sha256:f467abc51bcdc760fc5aeb7041abdeef46eeca3928dc43dd6e7fa7a533563818"}, + {file = "wsrpc_aiohttp-3.2.0-py3-none-any.whl", hash = "sha256:fa9b0bf5cb056898cb5c9f64cbc5eacb8a5dd18ab1b7f0cd4a2208b4a7fde282"}, +] + +[package.dependencies] +aiohttp = "<4" +yarl = "*" + +[package.extras] +develop = ["Sphinx", "async-timeout", "coverage (!=4.3)", "coveralls", "pytest", "pytest-aiohttp", "pytest-cov", "sphinxcontrib-plantuml", "tox (>=2.4)"] +testing = ["async-timeout", "coverage (!=4.3)", "coveralls", "pytest", "pytest-aiohttp", "pytest-cov"] +ujson = ["ujson"] + +[[package]] +name = "yarl" +version = "1.9.4" +description = "Yet another URL library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, + {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, + {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, + {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, + {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, + {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, + {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, + {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, + {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, + {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, + {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, + {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, + {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, + {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, + {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, + {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + +[metadata] +lock-version = "2.0" +python-versions = ">=3.9.1,<3.10" +content-hash = "9904c684f7885d871c560a189ff074527231d44f7bf4b221290ae646102f00f1" From 0e6c050618322244f37fea0ccb0cc688f9dd003b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Mon, 18 Mar 2024 15:28:33 +0100 Subject: [PATCH 051/150] :recycle: update ruff and fix python version --- poetry.lock | 38 +++++++++++++++++++------------------- pyproject.toml | 4 ++-- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/poetry.lock b/poetry.lock index adfa083e82..d2476400f7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1207,28 +1207,28 @@ files = [ [[package]] name = "ruff" -version = "0.2.2" +version = "0.3.3" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0a9efb032855ffb3c21f6405751d5e147b0c6b631e3ca3f6b20f917572b97eb6"}, - {file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d450b7fbff85913f866a5384d8912710936e2b96da74541c82c1b458472ddb39"}, - {file = "ruff-0.2.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecd46e3106850a5c26aee114e562c329f9a1fbe9e4821b008c4404f64ff9ce73"}, - {file = "ruff-0.2.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e22676a5b875bd72acd3d11d5fa9075d3a5f53b877fe7b4793e4673499318ba"}, - {file = "ruff-0.2.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1695700d1e25a99d28f7a1636d85bafcc5030bba9d0578c0781ba1790dbcf51c"}, - {file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b0c232af3d0bd8f521806223723456ffebf8e323bd1e4e82b0befb20ba18388e"}, - {file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f63d96494eeec2fc70d909393bcd76c69f35334cdbd9e20d089fb3f0640216ca"}, - {file = "ruff-0.2.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a61ea0ff048e06de273b2e45bd72629f470f5da8f71daf09fe481278b175001"}, - {file = "ruff-0.2.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1439c8f407e4f356470e54cdecdca1bd5439a0673792dbe34a2b0a551a2fe3"}, - {file = "ruff-0.2.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:940de32dc8853eba0f67f7198b3e79bc6ba95c2edbfdfac2144c8235114d6726"}, - {file = "ruff-0.2.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0c126da55c38dd917621552ab430213bdb3273bb10ddb67bc4b761989210eb6e"}, - {file = "ruff-0.2.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3b65494f7e4bed2e74110dac1f0d17dc8e1f42faaa784e7c58a98e335ec83d7e"}, - {file = "ruff-0.2.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1ec49be4fe6ddac0503833f3ed8930528e26d1e60ad35c2446da372d16651ce9"}, - {file = "ruff-0.2.2-py3-none-win32.whl", hash = "sha256:d920499b576f6c68295bc04e7b17b6544d9d05f196bb3aac4358792ef6f34325"}, - {file = "ruff-0.2.2-py3-none-win_amd64.whl", hash = "sha256:cc9a91ae137d687f43a44c900e5d95e9617cb37d4c989e462980ba27039d239d"}, - {file = "ruff-0.2.2-py3-none-win_arm64.whl", hash = "sha256:c9d15fc41e6054bfc7200478720570078f0b41c9ae4f010bcc16bd6f4d1aacdd"}, - {file = "ruff-0.2.2.tar.gz", hash = "sha256:e62ed7f36b3068a30ba39193a14274cd706bc486fad521276458022f7bccb31d"}, + {file = "ruff-0.3.3-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:973a0e388b7bc2e9148c7f9be8b8c6ae7471b9be37e1cc732f8f44a6f6d7720d"}, + {file = "ruff-0.3.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cfa60d23269d6e2031129b053fdb4e5a7b0637fc6c9c0586737b962b2f834493"}, + {file = "ruff-0.3.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1eca7ff7a47043cf6ce5c7f45f603b09121a7cc047447744b029d1b719278eb5"}, + {file = "ruff-0.3.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7d3f6762217c1da954de24b4a1a70515630d29f71e268ec5000afe81377642d"}, + {file = "ruff-0.3.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b24c19e8598916d9c6f5a5437671f55ee93c212a2c4c569605dc3842b6820386"}, + {file = "ruff-0.3.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5a6cbf216b69c7090f0fe4669501a27326c34e119068c1494f35aaf4cc683778"}, + {file = "ruff-0.3.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:352e95ead6964974b234e16ba8a66dad102ec7bf8ac064a23f95371d8b198aab"}, + {file = "ruff-0.3.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d6ab88c81c4040a817aa432484e838aaddf8bfd7ca70e4e615482757acb64f8"}, + {file = "ruff-0.3.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79bca3a03a759cc773fca69e0bdeac8abd1c13c31b798d5bb3c9da4a03144a9f"}, + {file = "ruff-0.3.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2700a804d5336bcffe063fd789ca2c7b02b552d2e323a336700abb8ae9e6a3f8"}, + {file = "ruff-0.3.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fd66469f1a18fdb9d32e22b79f486223052ddf057dc56dea0caaf1a47bdfaf4e"}, + {file = "ruff-0.3.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:45817af234605525cdf6317005923bf532514e1ea3d9270acf61ca2440691376"}, + {file = "ruff-0.3.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0da458989ce0159555ef224d5b7c24d3d2e4bf4c300b85467b08c3261c6bc6a8"}, + {file = "ruff-0.3.3-py3-none-win32.whl", hash = "sha256:f2831ec6a580a97f1ea82ea1eda0401c3cdf512cf2045fa3c85e8ef109e87de0"}, + {file = "ruff-0.3.3-py3-none-win_amd64.whl", hash = "sha256:be90bcae57c24d9f9d023b12d627e958eb55f595428bafcb7fec0791ad25ddfc"}, + {file = "ruff-0.3.3-py3-none-win_arm64.whl", hash = "sha256:0171aab5fecdc54383993389710a3d1227f2da124d76a2784a7098e818f92d61"}, + {file = "ruff-0.3.3.tar.gz", hash = "sha256:38671be06f57a2f8aba957d9f701ea889aa5736be806f18c0cd03d6ff0cbca8d"}, ] [[package]] @@ -1465,4 +1465,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = ">=3.9.1,<3.10" -content-hash = "9904c684f7885d871c560a189ff074527231d44f7bf4b221290ae646102f00f1" +content-hash = "8a62cc31c960aff7e5df7bfdc5b65790e57bf0e7a87fedd16a68ababa49268c8" diff --git a/pyproject.toml b/pyproject.toml index f9c0f4602c..29213281af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ pytest = "^8.0" pytest-print = "^1.0" ayon-python-api = "^1.0" arrow = "^1.3.0" -ruff = "^0.2.2" +ruff = "^0.3.3" [tool.ruff] @@ -67,7 +67,7 @@ exclude = [ line-length = 79 indent-width = 4 -# Assume Python 3.8 +# Assume Python 3.9 target-version = "py39" [tool.ruff.lint] From 65af945b7d1baf9d8f2fad2aa46023d2e417b322 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 18 Mar 2024 15:29:01 +0100 Subject: [PATCH 052/150] remove forgotten line --- client/ayon_core/pipeline/anatomy/templates.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_core/pipeline/anatomy/templates.py b/client/ayon_core/pipeline/anatomy/templates.py index 63dfd9b1b0..7ea2c8e004 100644 --- a/client/ayon_core/pipeline/anatomy/templates.py +++ b/client/ayon_core/pipeline/anatomy/templates.py @@ -379,7 +379,6 @@ class TemplateCategory: return key -class AnatomyTemplates(TemplatesDict): class AnatomyTemplates: inner_key_pattern = re.compile(r"(\{@.*?[^{}0]*\})") inner_key_name_pattern = re.compile(r"\{@(.*?[^{}0]*)\}") From bf60b264def3f45162016b255715aec64152a31b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 18 Mar 2024 15:42:14 +0100 Subject: [PATCH 053/150] 'get_template' raises 'KeyError' but have option to pass in 'default' --- .../ayon_core/pipeline/anatomy/templates.py | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/pipeline/anatomy/templates.py b/client/ayon_core/pipeline/anatomy/templates.py index 7ea2c8e004..e9401c1501 100644 --- a/client/ayon_core/pipeline/anatomy/templates.py +++ b/client/ayon_core/pipeline/anatomy/templates.py @@ -560,31 +560,56 @@ class AnatomyTemplates: """ return self.format(in_data, strict=False) - def get_template(self, category_name, template_name, subkey=None): + def get_template( + self, category_name, template_name, subkey=None, default=_PLACEHOLDER + ): """Get template item from category. Args: category_name (str): Category name. template_name (str): Template name. subkey (Optional[str]): Subkey name. + default (Any): Default value if template is not found. Returns: Any: Template item or subkey value. + Raises: + KeyError: When any passed key is not available. Raise of error + does not happen if 'default' is filled. + """ self._validate_discovery() category = self.get(category_name) if category is None: - return None + if default is not _PLACEHOLDER: + return default + raise KeyError("Category '{}' not found.".format(category_name)) template_item = category.get(template_name) if template_item is None: - return template_item + if default is not _PLACEHOLDER: + return default + raise KeyError( + "Template '{}' not found in category '{}'.".format( + template_name, category_name + ) + ) if subkey is None: return template_item - return template_item.get(subkey) + item = template_item.get(subkey) + if item is not None: + return item + + if default is not _PLACEHOLDER: + return default + raise KeyError( + "Subkey '{}' not found in '{}/{}'.".format( + subkey, category_name, template_name + ) + ) def _solve_dict(self, data, strict): """ Solves templates with entered data. From eee55221a2ec5d2da55477cc786bad155fd4770e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 18 Mar 2024 15:43:33 +0100 Subject: [PATCH 054/150] fix cases when template key is name of the category --- client/ayon_core/pipeline/anatomy/templates.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/ayon_core/pipeline/anatomy/templates.py b/client/ayon_core/pipeline/anatomy/templates.py index e9401c1501..7becb5026e 100644 --- a/client/ayon_core/pipeline/anatomy/templates.py +++ b/client/ayon_core/pipeline/anatomy/templates.py @@ -372,6 +372,12 @@ class TemplateCategory: """ if key in self._category_data: return key + + # Use default when the key is the category name + if key == self._name: + return "default" + + # Remove prefix if is key prefixed if key.startswith(self._name_prefix): new_key = key[len(self._name_prefix):] if new_key in self._category_data: From e70786ce15c27f78e1b629133d65c769c4959a52 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 18 Mar 2024 15:45:26 +0100 Subject: [PATCH 055/150] modify default settings --- server/settings/tools.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/server/settings/tools.py b/server/settings/tools.py index b45f9b49d4..488d27e8f1 100644 --- a/server/settings/tools.py +++ b/server/settings/tools.py @@ -410,14 +410,14 @@ DEFAULT_TOOLS_VALUES = { { "task_types": [], "hosts": [], - "workfile_template": "work" + "workfile_template": "default" }, { "task_types": [], "hosts": [ "unreal" ], - "workfile_template": "work_unreal" + "workfile_template": "unreal" } ], "last_workfile_on_startup": [ @@ -457,7 +457,7 @@ DEFAULT_TOOLS_VALUES = { "hosts": [], "task_types": [], "task_names": [], - "template_name": "publish" + "template_name": "default" }, { "product_types": [ @@ -468,7 +468,7 @@ DEFAULT_TOOLS_VALUES = { "hosts": [], "task_types": [], "task_names": [], - "template_name": "publish_render" + "template_name": "render" }, { "product_types": [ @@ -479,7 +479,7 @@ DEFAULT_TOOLS_VALUES = { ], "task_types": [], "task_names": [], - "template_name": "publish_simpleUnrealTexture" + "template_name": "simpleUnrealTexture" }, { "product_types": [ @@ -491,7 +491,7 @@ DEFAULT_TOOLS_VALUES = { ], "task_types": [], "task_names": [], - "template_name": "publish_maya2unreal" + "template_name": "maya2unreal" }, { "product_types": [ @@ -502,7 +502,7 @@ DEFAULT_TOOLS_VALUES = { ], "task_types": [], "task_names": [], - "template_name": "publish_online" + "template_name": "online" }, { "product_types": [ @@ -513,7 +513,7 @@ DEFAULT_TOOLS_VALUES = { ], "task_types": [], "task_names": [], - "template_name": "publish_tycache" + "template_name": "tycache" } ], "hero_template_name_profiles": [ @@ -526,7 +526,7 @@ DEFAULT_TOOLS_VALUES = { ], "task_types": [], "task_names": [], - "template_name": "hero_simpleUnrealTextureHero" + "template_name": "simpleUnrealTextureHero" } ] } From 77b3cd1c534e2fdf7fa26e16bfc123cb537791a7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 18 Mar 2024 15:47:54 +0100 Subject: [PATCH 056/150] add missing placeholder object --- client/ayon_core/pipeline/anatomy/templates.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/ayon_core/pipeline/anatomy/templates.py b/client/ayon_core/pipeline/anatomy/templates.py index 7becb5026e..7d9a1255a3 100644 --- a/client/ayon_core/pipeline/anatomy/templates.py +++ b/client/ayon_core/pipeline/anatomy/templates.py @@ -16,6 +16,8 @@ from .exceptions import ( ) from .roots import RootItem +_PLACEHOLDER = object() + class AnatomyTemplateResult(TemplateResult): rootless = None From b66dded41125ea5fe8eed4629afd6fa329680ae9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 18 Mar 2024 15:55:37 +0100 Subject: [PATCH 057/150] fix file template conversion --- client/ayon_core/hosts/photoshop/api/launch_logic.py | 5 +++-- .../ayon_core/hosts/tvpaint/plugins/load/load_workfile.py | 4 ++-- client/ayon_core/lib/applications.py | 4 +++- client/ayon_core/tools/workfiles/models/workfiles.py | 6 ++++-- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/hosts/photoshop/api/launch_logic.py b/client/ayon_core/hosts/photoshop/api/launch_logic.py index ccb7b0048b..9c6a1e8a01 100644 --- a/client/ayon_core/hosts/photoshop/api/launch_logic.py +++ b/client/ayon_core/hosts/photoshop/api/launch_logic.py @@ -397,9 +397,10 @@ class PhotoshopRoute(WebSocketRoute): # Define saving file extension extensions = host.get_workfile_extensions() - work_root = work_template["directory"].format_strict(data) + work_root = str(work_template["directory"].format_strict(data)) + file_template = str(work_template["file"]) last_workfile_path = get_last_workfile( - work_root, work_template["file"], data, extensions, True + work_root, file_template, data, extensions, True ) return last_workfile_path diff --git a/client/ayon_core/hosts/tvpaint/plugins/load/load_workfile.py b/client/ayon_core/hosts/tvpaint/plugins/load/load_workfile.py index 16cb54f4a3..0b6d67a840 100644 --- a/client/ayon_core/hosts/tvpaint/plugins/load/load_workfile.py +++ b/client/ayon_core/hosts/tvpaint/plugins/load/load_workfile.py @@ -93,9 +93,9 @@ class LoadWorkfile(plugin.Loader): data["ext"] = extension.lstrip(".") - work_root = work_template["directory"].format_strict(data) + work_root = str(work_template["directory"].format_strict(data)) version = get_last_workfile_with_version( - work_root, work_template["file"], data, extensions + work_root, str(work_template["file"]), data, extensions )[1] if version is None: diff --git a/client/ayon_core/lib/applications.py b/client/ayon_core/lib/applications.py index c4f1d168b5..123396f5f6 100644 --- a/client/ayon_core/lib/applications.py +++ b/client/ayon_core/lib/applications.py @@ -1862,7 +1862,9 @@ def _prepare_last_workfile(data, workdir, addons_manager): project_settings=project_settings ) # Find last workfile - file_template = anatomy.get_template("work", template_key, "file") + file_template = str( + anatomy.get_template("work", template_key, "file") + ) workdir_data.update({ "version": 1, diff --git a/client/ayon_core/tools/workfiles/models/workfiles.py b/client/ayon_core/tools/workfiles/models/workfiles.py index f4ca08a45a..ef502e3d6f 100644 --- a/client/ayon_core/tools/workfiles/models/workfiles.py +++ b/client/ayon_core/tools/workfiles/models/workfiles.py @@ -200,7 +200,7 @@ class WorkareaModel: self, workdir, file_template, fill_data, extensions ): version = get_last_workfile_with_version( - workdir, str(file_template), fill_data, extensions + workdir, file_template, fill_data, extensions )[1] if version is None: @@ -300,7 +300,9 @@ class WorkareaModel: workdir = self._get_workdir(anatomy, template_key, fill_data) - file_template = anatomy.get_template("work", template_key, "file") + file_template = str( + anatomy.get_template("work", template_key, "file") + ) comment_hints, comment = self._get_comments_from_root( file_template, From 0ba8d7e6456b1b8f29333acc16f8b1d71333cb2e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 18 Mar 2024 16:47:40 +0100 Subject: [PATCH 058/150] fix super call in class method --- client/ayon_core/hosts/maya/api/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/api/plugin.py b/client/ayon_core/hosts/maya/api/plugin.py index eaf93725f4..c25e99e92e 100644 --- a/client/ayon_core/hosts/maya/api/plugin.py +++ b/client/ayon_core/hosts/maya/api/plugin.py @@ -657,7 +657,7 @@ class Loader(LoaderPlugin): @classmethod def apply_settings(cls, project_settings): - super(Loader, cls).apply_settings(project_settings) + LoaderPlugin.apply_settings(cls, project_settings) cls.load_settings = project_settings['maya']['load'] def get_custom_namespace_and_group(self, context, options, loader_key): From c62993a394308fcf1ac45f6e25e7c66a62bdd023 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 18 Mar 2024 16:59:07 +0100 Subject: [PATCH 059/150] remove cls passed to class method --- client/ayon_core/hosts/maya/api/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/api/plugin.py b/client/ayon_core/hosts/maya/api/plugin.py index c25e99e92e..2de029c516 100644 --- a/client/ayon_core/hosts/maya/api/plugin.py +++ b/client/ayon_core/hosts/maya/api/plugin.py @@ -657,7 +657,7 @@ class Loader(LoaderPlugin): @classmethod def apply_settings(cls, project_settings): - LoaderPlugin.apply_settings(cls, project_settings) + LoaderPlugin.apply_settings(project_settings) cls.load_settings = project_settings['maya']['load'] def get_custom_namespace_and_group(self, context, options, loader_key): From e913eefa0796cd71a404dc3ca283a9837aef1b0c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 18 Mar 2024 16:59:15 +0100 Subject: [PATCH 060/150] fix get_product_name call --- client/ayon_core/hosts/maya/api/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/api/plugin.py b/client/ayon_core/hosts/maya/api/plugin.py index 2de029c516..f086295b57 100644 --- a/client/ayon_core/hosts/maya/api/plugin.py +++ b/client/ayon_core/hosts/maya/api/plugin.py @@ -608,7 +608,7 @@ class RenderlayerCreator(NewCreator, MayaCreatorBase): return get_product_name( project_name, task_name, - task_type + task_type, host_name, self.layer_instance_prefix or self.product_type, variant, From 24a00cd96a723ad5ad88af85238596b7bd3c5cb2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 18 Mar 2024 17:03:37 +0100 Subject: [PATCH 061/150] keep super class call --- client/ayon_core/hosts/maya/api/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/api/plugin.py b/client/ayon_core/hosts/maya/api/plugin.py index f086295b57..bdb0cb1c99 100644 --- a/client/ayon_core/hosts/maya/api/plugin.py +++ b/client/ayon_core/hosts/maya/api/plugin.py @@ -657,7 +657,7 @@ class Loader(LoaderPlugin): @classmethod def apply_settings(cls, project_settings): - LoaderPlugin.apply_settings(project_settings) + super(Loader, cls).apply_settings(project_settings) cls.load_settings = project_settings['maya']['load'] def get_custom_namespace_and_group(self, context, options, loader_key): From ed268ccec679504101cce2bd60cb3fe09293cd76 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 18 Mar 2024 17:58:16 +0100 Subject: [PATCH 062/150] move ayon_utils utils to tools utils --- .../tools/ayon_utils/widgets/utils.py | 109 ---------- client/ayon_core/tools/utils/__init__.py | 2 + client/ayon_core/tools/utils/lib.py | 199 ++++++++++++++---- 3 files changed, 156 insertions(+), 154 deletions(-) delete mode 100644 client/ayon_core/tools/ayon_utils/widgets/utils.py diff --git a/client/ayon_core/tools/ayon_utils/widgets/utils.py b/client/ayon_core/tools/ayon_utils/widgets/utils.py deleted file mode 100644 index ead8f4edb2..0000000000 --- a/client/ayon_core/tools/ayon_utils/widgets/utils.py +++ /dev/null @@ -1,109 +0,0 @@ -import os -from functools import partial - -from qtpy import QtCore, QtGui - -from ayon_core.tools.utils.lib import get_qta_icon_by_name_and_color - - -class RefreshThread(QtCore.QThread): - refresh_finished = QtCore.Signal(str) - - def __init__(self, thread_id, func, *args, **kwargs): - super(RefreshThread, self).__init__() - self._id = thread_id - self._callback = partial(func, *args, **kwargs) - self._exception = None - self._result = None - self.finished.connect(self._on_finish_callback) - - @property - def id(self): - return self._id - - @property - def failed(self): - return self._exception is not None - - def run(self): - try: - self._result = self._callback() - except Exception as exc: - self._exception = exc - - def get_result(self): - return self._result - - def _on_finish_callback(self): - """Trigger custom signal with thread id. - - Listening for 'finished' signal we make sure that execution of thread - finished and QThread object can be safely deleted. - """ - - self.refresh_finished.emit(self.id) - - -class _IconsCache: - """Cache for icons.""" - - _cache = {} - _default = None - - @classmethod - def _get_cache_key(cls, icon_def): - parts = [] - icon_type = icon_def["type"] - if icon_type == "path": - parts = [icon_type, icon_def["path"]] - - elif icon_type == "awesome-font": - parts = [icon_type, icon_def["name"], icon_def["color"]] - return "|".join(parts) - - @classmethod - def get_icon(cls, icon_def): - if not icon_def: - return None - icon_type = icon_def["type"] - cache_key = cls._get_cache_key(icon_def) - cache = cls._cache.get(cache_key) - if cache is not None: - return cache - - icon = None - if icon_type == "path": - path = icon_def["path"] - if os.path.exists(path): - icon = QtGui.QIcon(path) - - elif icon_type == "awesome-font": - icon_name = icon_def["name"] - icon_color = icon_def["color"] - icon = get_qta_icon_by_name_and_color(icon_name, icon_color) - if icon is None: - icon = get_qta_icon_by_name_and_color( - "fa.{}".format(icon_name), icon_color) - if icon is None: - icon = cls.get_default() - cls._cache[cache_key] = icon - return icon - - @classmethod - def get_default(cls): - pix = QtGui.QPixmap(1, 1) - pix.fill(QtCore.Qt.transparent) - return QtGui.QIcon(pix) - - -def get_qt_icon(icon_def): - """Returns icon from cache or creates new one. - - Args: - icon_def (dict[str, Any]): Icon definition. - - Returns: - QtGui.QIcon: Icon. - """ - - return _IconsCache.get_icon(icon_def) diff --git a/client/ayon_core/tools/utils/__init__.py b/client/ayon_core/tools/utils/__init__.py index 445b4d9b97..39570a4813 100644 --- a/client/ayon_core/tools/utils/__init__.py +++ b/client/ayon_core/tools/utils/__init__.py @@ -37,6 +37,7 @@ from .lib import ( get_qt_app, get_ayon_qt_app, get_openpype_qt_app, + get_qt_icon, ) from .models import ( @@ -96,6 +97,7 @@ __all__ = ( "get_qt_app", "get_ayon_qt_app", "get_openpype_qt_app", + "get_qt_icon", "RecursiveSortFilterProxyModel", diff --git a/client/ayon_core/tools/utils/lib.py b/client/ayon_core/tools/utils/lib.py index 741fc1f335..8bcfe8b985 100644 --- a/client/ayon_core/tools/utils/lib.py +++ b/client/ayon_core/tools/utils/lib.py @@ -1,6 +1,7 @@ import os import sys import contextlib +from functools import partial from qtpy import QtWidgets, QtCore, QtGui import qtawesome @@ -195,51 +196,6 @@ def get_openpype_qt_app(): return get_ayon_qt_app() -class _Cache: - icons = {} - - -def get_qta_icon_by_name_and_color(icon_name, icon_color): - if not icon_name or not icon_color: - return None - - full_icon_name = "{0}-{1}".format(icon_name, icon_color) - if full_icon_name in _Cache.icons: - return _Cache.icons[full_icon_name] - - variants = [icon_name] - qta_instance = qtawesome._instance() - for key in qta_instance.charmap.keys(): - variants.append("{0}.{1}".format(key, icon_name)) - - icon = None - used_variant = None - for variant in variants: - try: - icon = qtawesome.icon(variant, color=icon_color) - used_variant = variant - break - except Exception: - pass - - if used_variant is None: - log.info("Didn't find icon \"{}\"".format(icon_name)) - - elif used_variant != icon_name: - log.debug("Icon \"{}\" was not found \"{}\" is used instead".format( - icon_name, used_variant - )) - - _Cache.icons[full_icon_name] = icon - return icon - - -def get_default_task_icon(color=None): - if color is None: - color = get_default_entity_icon_color() - return get_qta_icon_by_name_and_color("fa.male", color) - - def iter_model_rows(model, column, include_root=False): """Iterate over all row indices in a model""" indices = [QtCore.QModelIndex()] # start iteration at root @@ -457,3 +413,156 @@ def get_warning_pixmap(color=None): color = get_objected_colors("delete-btn-bg").get_qcolor() return paint_image_with_color(src_image, color) + + +class RefreshThread(QtCore.QThread): + refresh_finished = QtCore.Signal(str) + + def __init__(self, thread_id, func, *args, **kwargs): + super(RefreshThread, self).__init__() + self._id = thread_id + self._callback = partial(func, *args, **kwargs) + self._exception = None + self._result = None + self.finished.connect(self._on_finish_callback) + + @property + def id(self): + return self._id + + @property + def failed(self): + return self._exception is not None + + def run(self): + try: + self._result = self._callback() + except Exception as exc: + self._exception = exc + + def get_result(self): + return self._result + + def _on_finish_callback(self): + """Trigger custom signal with thread id. + + Listening for 'finished' signal we make sure that execution of thread + finished and QThread object can be safely deleted. + """ + + self.refresh_finished.emit(self.id) + + +class _IconsCache: + """Cache for icons.""" + + _cache = {} + _default = None + _qtawesome_cache = {} + + @classmethod + def _get_cache_key(cls, icon_def): + parts = [] + icon_type = icon_def["type"] + if icon_type == "path": + parts = [icon_type, icon_def["path"]] + + elif icon_type == "awesome-font": + parts = [icon_type, icon_def["name"], icon_def["color"]] + return "|".join(parts) + + @classmethod + def get_icon(cls, icon_def): + if not icon_def: + return None + icon_type = icon_def["type"] + cache_key = cls._get_cache_key(icon_def) + cache = cls._cache.get(cache_key) + if cache is not None: + return cache + + icon = None + if icon_type == "path": + path = icon_def["path"] + if os.path.exists(path): + icon = QtGui.QIcon(path) + + elif icon_type == "awesome-font": + icon_name = icon_def["name"] + icon_color = icon_def["color"] + icon = cls.get_qta_icon_by_name_and_color(icon_name, icon_color) + if icon is None: + icon = cls.get_qta_icon_by_name_and_color( + "fa.{}".format(icon_name), icon_color) + if icon is None: + icon = cls.get_default() + cls._cache[cache_key] = icon + return icon + + @classmethod + def get_default(cls): + pix = QtGui.QPixmap(1, 1) + pix.fill(QtCore.Qt.transparent) + return QtGui.QIcon(pix) + + @classmethod + def get_qta_icon_by_name_and_color(cls, icon_name, icon_color): + if not icon_name or not icon_color: + return None + + full_icon_name = "{0}-{1}".format(icon_name, icon_color) + if full_icon_name in cls._qtawesome_cache: + return cls._qtawesome_cache[full_icon_name] + + variants = [icon_name] + qta_instance = qtawesome._instance() + for key in qta_instance.charmap.keys(): + variants.append("{0}.{1}".format(key, icon_name)) + + icon = None + used_variant = None + for variant in variants: + try: + icon = qtawesome.icon(variant, color=icon_color) + used_variant = variant + break + except Exception: + pass + + if used_variant is None: + log.info("Didn't find icon \"{}\"".format(icon_name)) + + elif used_variant != icon_name: + log.debug("Icon \"{}\" was not found \"{}\" is used instead".format( + icon_name, used_variant + )) + + cls._qtawesome_cache[full_icon_name] = icon + return icon + + +def get_qt_icon(icon_def): + """Returns icon from cache or creates new one. + + Args: + icon_def (dict[str, Any]): Icon definition. + + Returns: + QtGui.QIcon: Icon. + + """ + return _IconsCache.get_icon(icon_def) + + +def get_qta_icon_by_name_and_color(icon_name, icon_color): + """Returns icon from cache or creates new one. + + Args: + icon_name (str): Icon name. + icon_color (str): Icon color. + + Returns: + QtGui.QIcon: Icon. + + """ + return _IconsCache.get_qta_icon_by_name_and_color(icon_name, icon_color) From fb3df8c8067e66479815dbc8635f88ee5e663be2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 18 Mar 2024 17:59:09 +0100 Subject: [PATCH 063/150] move ayon utils widgets to utils --- .../tools/ayon_utils/widgets/__init__.py | 51 ------------------- client/ayon_core/tools/utils/__init__.py | 39 ++++++++++++++ .../widgets => utils}/folders_widget.py | 9 ++-- .../widgets => utils}/projects_widget.py | 3 +- .../widgets => utils}/tasks_widget.py | 4 +- 5 files changed, 47 insertions(+), 59 deletions(-) delete mode 100644 client/ayon_core/tools/ayon_utils/widgets/__init__.py rename client/ayon_core/tools/{ayon_utils/widgets => utils}/folders_widget.py (99%) rename client/ayon_core/tools/{ayon_utils/widgets => utils}/projects_widget.py (99%) rename client/ayon_core/tools/{ayon_utils/widgets => utils}/tasks_widget.py (99%) diff --git a/client/ayon_core/tools/ayon_utils/widgets/__init__.py b/client/ayon_core/tools/ayon_utils/widgets/__init__.py deleted file mode 100644 index a62bab6751..0000000000 --- a/client/ayon_core/tools/ayon_utils/widgets/__init__.py +++ /dev/null @@ -1,51 +0,0 @@ -from .projects_widget import ( - # ProjectsWidget, - ProjectsCombobox, - ProjectsQtModel, - ProjectSortFilterProxy, - PROJECT_NAME_ROLE, - PROJECT_IS_CURRENT_ROLE, - PROJECT_IS_ACTIVE_ROLE, - PROJECT_IS_LIBRARY_ROLE, -) - -from .folders_widget import ( - FoldersWidget, - FoldersQtModel, - FOLDERS_MODEL_SENDER_NAME, - SimpleFoldersWidget, -) - -from .tasks_widget import ( - TasksWidget, - TasksQtModel, - TASKS_MODEL_SENDER_NAME, -) -from .utils import ( - get_qt_icon, - RefreshThread, -) - - -__all__ = ( - # "ProjectsWidget", - "ProjectsCombobox", - "ProjectsQtModel", - "ProjectSortFilterProxy", - "PROJECT_NAME_ROLE", - "PROJECT_IS_CURRENT_ROLE", - "PROJECT_IS_ACTIVE_ROLE", - "PROJECT_IS_LIBRARY_ROLE", - - "FoldersWidget", - "FoldersQtModel", - "FOLDERS_MODEL_SENDER_NAME", - "SimpleFoldersWidget", - - "TasksWidget", - "TasksQtModel", - "TASKS_MODEL_SENDER_NAME", - - "get_qt_icon", - "RefreshThread", -) diff --git a/client/ayon_core/tools/utils/__init__.py b/client/ayon_core/tools/utils/__init__.py index 39570a4813..4b5fbeaf67 100644 --- a/client/ayon_core/tools/utils/__init__.py +++ b/client/ayon_core/tools/utils/__init__.py @@ -56,6 +56,28 @@ from .dialogs import ( SimplePopup, PopupUpdateKeys, ) +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 ( + FoldersWidget, + FoldersQtModel, + FOLDERS_MODEL_SENDER_NAME, + SimpleFoldersWidget, +) + +from .tasks_widget import ( + TasksWidget, + TasksQtModel, + TASKS_MODEL_SENDER_NAME, +) __all__ = ( @@ -115,4 +137,21 @@ __all__ = ( "ScrollMessageBox", "SimplePopup", "PopupUpdateKeys", + + "ProjectsCombobox", + "ProjectsQtModel", + "ProjectSortFilterProxy", + "PROJECT_NAME_ROLE", + "PROJECT_IS_CURRENT_ROLE", + "PROJECT_IS_ACTIVE_ROLE", + "PROJECT_IS_LIBRARY_ROLE", + + "FoldersWidget", + "FoldersQtModel", + "FOLDERS_MODEL_SENDER_NAME", + "SimpleFoldersWidget", + + "TasksWidget", + "TasksQtModel", + "TASKS_MODEL_SENDER_NAME", ) diff --git a/client/ayon_core/tools/ayon_utils/widgets/folders_widget.py b/client/ayon_core/tools/utils/folders_widget.py similarity index 99% rename from client/ayon_core/tools/ayon_utils/widgets/folders_widget.py rename to client/ayon_core/tools/utils/folders_widget.py index e42a5b635c..a2519f90c2 100644 --- a/client/ayon_core/tools/ayon_utils/widgets/folders_widget.py +++ b/client/ayon_core/tools/utils/folders_widget.py @@ -7,12 +7,11 @@ from ayon_core.tools.ayon_utils.models import ( HierarchyModel, HierarchyExpectedSelection, ) -from ayon_core.tools.utils import ( - RecursiveSortFilterProxyModel, - TreeView, -) -from .utils import RefreshThread, get_qt_icon +from .models import RecursiveSortFilterProxyModel +from .views import TreeView +from .lib import RefreshThread, get_qt_icon + FOLDERS_MODEL_SENDER_NAME = "qt_folders_model" FOLDER_ID_ROLE = QtCore.Qt.UserRole + 1 diff --git a/client/ayon_core/tools/ayon_utils/widgets/projects_widget.py b/client/ayon_core/tools/utils/projects_widget.py similarity index 99% rename from client/ayon_core/tools/ayon_utils/widgets/projects_widget.py rename to client/ayon_core/tools/utils/projects_widget.py index 79ffc77640..e4ac69198a 100644 --- a/client/ayon_core/tools/ayon_utils/widgets/projects_widget.py +++ b/client/ayon_core/tools/utils/projects_widget.py @@ -1,7 +1,8 @@ from qtpy import QtWidgets, QtCore, QtGui from ayon_core.tools.ayon_utils.models import PROJECTS_MODEL_SENDER -from .utils import RefreshThread, get_qt_icon + +from .lib import RefreshThread, get_qt_icon PROJECT_NAME_ROLE = QtCore.Qt.UserRole + 1 PROJECT_IS_ACTIVE_ROLE = QtCore.Qt.UserRole + 2 diff --git a/client/ayon_core/tools/ayon_utils/widgets/tasks_widget.py b/client/ayon_core/tools/utils/tasks_widget.py similarity index 99% rename from client/ayon_core/tools/ayon_utils/widgets/tasks_widget.py rename to client/ayon_core/tools/utils/tasks_widget.py index cfe901c492..0ff8e8a5c1 100644 --- a/client/ayon_core/tools/ayon_utils/widgets/tasks_widget.py +++ b/client/ayon_core/tools/utils/tasks_widget.py @@ -1,9 +1,9 @@ from qtpy import QtWidgets, QtGui, QtCore from ayon_core.style import get_disabled_entity_icon_color -from ayon_core.tools.utils import DeselectableTreeView -from .utils import RefreshThread, get_qt_icon +from .views import DeselectableTreeView +from .lib import RefreshThread, get_qt_icon TASKS_MODEL_SENDER_NAME = "qt_tasks_model" ITEM_ID_ROLE = QtCore.Qt.UserRole + 1 From 95a67b666fb9e5c043d87c03762ec1fabb07ab47 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 18 Mar 2024 17:59:41 +0100 Subject: [PATCH 064/150] import widgets from new locations --- client/ayon_core/hosts/houdini/api/usd.py | 7 +++++-- client/ayon_core/tools/context_dialog/window.py | 6 ++---- client/ayon_core/tools/launcher/ui/actions_widget.py | 2 +- client/ayon_core/tools/launcher/ui/hierarchy_page.py | 2 +- client/ayon_core/tools/launcher/ui/projects_widget.py | 5 +++-- client/ayon_core/tools/loader/ui/actions_utils.py | 2 +- client/ayon_core/tools/loader/ui/folders_widget.py | 4 ++-- .../ayon_core/tools/loader/ui/product_types_widget.py | 2 +- client/ayon_core/tools/loader/ui/products_model.py | 2 +- client/ayon_core/tools/loader/ui/repres_widget.py | 2 +- client/ayon_core/tools/loader/ui/window.py | 2 +- .../tools/publisher/widgets/create_context_widgets.py | 2 +- .../tools/publisher/widgets/folders_dialog.py | 3 +-- .../ayon_core/tools/publisher/widgets/tasks_model.py | 10 ++++++++-- client/ayon_core/tools/push_to_project/ui/window.py | 2 -- client/ayon_core/tools/sceneinventory/model.py | 2 +- .../sceneinventory/switch_dialog/folders_input.py | 2 +- client/ayon_core/tools/traypublisher/window.py | 5 +++-- client/ayon_core/tools/workfiles/widgets/window.py | 8 ++++++-- 19 files changed, 40 insertions(+), 30 deletions(-) diff --git a/client/ayon_core/hosts/houdini/api/usd.py b/client/ayon_core/hosts/houdini/api/usd.py index 443a52bc37..ed33fbf590 100644 --- a/client/ayon_core/hosts/houdini/api/usd.py +++ b/client/ayon_core/hosts/houdini/api/usd.py @@ -8,8 +8,11 @@ from qtpy import QtWidgets, QtCore, QtGui from ayon_core import style from ayon_core.pipeline import get_current_project_name -from ayon_core.tools.utils import PlaceholderLineEdit, RefreshButton -from ayon_core.tools.ayon_utils.widgets import SimpleFoldersWidget +from ayon_core.tools.utils import ( + PlaceholderLineEdit, + RefreshButton, + SimpleFoldersWidget, +) from pxr import Sdf diff --git a/client/ayon_core/tools/context_dialog/window.py b/client/ayon_core/tools/context_dialog/window.py index b145e77515..df5dd868e8 100644 --- a/client/ayon_core/tools/context_dialog/window.py +++ b/client/ayon_core/tools/context_dialog/window.py @@ -10,15 +10,13 @@ from ayon_core.tools.ayon_utils.models import ( ProjectsModel, HierarchyModel, ) -from ayon_core.tools.ayon_utils.widgets import ( +from ayon_core.tools.utils import ( ProjectsCombobox, FoldersWidget, TasksWidget, -) -from ayon_core.tools.utils.lib import ( - center_window, get_ayon_qt_app, ) +from ayon_core.tools.utils.lib import center_window class SelectionModel(object): diff --git a/client/ayon_core/tools/launcher/ui/actions_widget.py b/client/ayon_core/tools/launcher/ui/actions_widget.py index 617f3b0c91..a225827418 100644 --- a/client/ayon_core/tools/launcher/ui/actions_widget.py +++ b/client/ayon_core/tools/launcher/ui/actions_widget.py @@ -4,7 +4,7 @@ import collections from qtpy import QtWidgets, QtCore, QtGui from ayon_core.tools.flickcharm import FlickCharm -from ayon_core.tools.ayon_utils.widgets import get_qt_icon +from ayon_core.tools.utils import get_qt_icon from .resources import get_options_image_path diff --git a/client/ayon_core/tools/launcher/ui/hierarchy_page.py b/client/ayon_core/tools/launcher/ui/hierarchy_page.py index 5b5f88a802..226a57930b 100644 --- a/client/ayon_core/tools/launcher/ui/hierarchy_page.py +++ b/client/ayon_core/tools/launcher/ui/hierarchy_page.py @@ -6,7 +6,7 @@ from ayon_core.tools.utils import ( SquareButton, RefreshButton, ) -from ayon_core.tools.ayon_utils.widgets import ( +from ayon_core.tools.utils import ( ProjectsCombobox, FoldersWidget, TasksWidget, diff --git a/client/ayon_core/tools/launcher/ui/projects_widget.py b/client/ayon_core/tools/launcher/ui/projects_widget.py index 729caf3232..39fc67fc0f 100644 --- a/client/ayon_core/tools/launcher/ui/projects_widget.py +++ b/client/ayon_core/tools/launcher/ui/projects_widget.py @@ -1,8 +1,9 @@ from qtpy import QtWidgets, QtCore from ayon_core.tools.flickcharm import FlickCharm -from ayon_core.tools.utils import PlaceholderLineEdit, RefreshButton -from ayon_core.tools.ayon_utils.widgets import ( +from ayon_core.tools.utils import ( + PlaceholderLineEdit, + RefreshButton, ProjectsQtModel, ProjectSortFilterProxy, ) diff --git a/client/ayon_core/tools/loader/ui/actions_utils.py b/client/ayon_core/tools/loader/ui/actions_utils.py index bf6ab6eeb5..5a988ef4c2 100644 --- a/client/ayon_core/tools/loader/ui/actions_utils.py +++ b/client/ayon_core/tools/loader/ui/actions_utils.py @@ -10,7 +10,7 @@ from ayon_core.tools.utils.widgets import ( OptionalAction, OptionDialog, ) -from ayon_core.tools.ayon_utils.widgets import get_qt_icon +from ayon_core.tools.utils import get_qt_icon def show_actions_menu(action_items, global_point, one_item_selected, parent): diff --git a/client/ayon_core/tools/loader/ui/folders_widget.py b/client/ayon_core/tools/loader/ui/folders_widget.py index 34881ab49d..7b146456da 100644 --- a/client/ayon_core/tools/loader/ui/folders_widget.py +++ b/client/ayon_core/tools/loader/ui/folders_widget.py @@ -7,11 +7,11 @@ from ayon_core.tools.utils import ( ) from ayon_core.style import get_objected_colors -from ayon_core.tools.ayon_utils.widgets import ( +from ayon_core.tools.utils import ( FoldersQtModel, FOLDERS_MODEL_SENDER_NAME, ) -from ayon_core.tools.ayon_utils.widgets.folders_widget import FOLDER_ID_ROLE +from ayon_core.tools.utils.folders_widget import FOLDER_ID_ROLE if qtpy.API == "pyside": from PySide.QtGui import QStyleOptionViewItemV4 diff --git a/client/ayon_core/tools/loader/ui/product_types_widget.py b/client/ayon_core/tools/loader/ui/product_types_widget.py index 26244517ec..180994fd7f 100644 --- a/client/ayon_core/tools/loader/ui/product_types_widget.py +++ b/client/ayon_core/tools/loader/ui/product_types_widget.py @@ -1,6 +1,6 @@ from qtpy import QtWidgets, QtGui, QtCore -from ayon_core.tools.ayon_utils.widgets import get_qt_icon +from ayon_core.tools.utils import get_qt_icon PRODUCT_TYPE_ROLE = QtCore.Qt.UserRole + 1 diff --git a/client/ayon_core/tools/loader/ui/products_model.py b/client/ayon_core/tools/loader/ui/products_model.py index 331efad68a..c51172849a 100644 --- a/client/ayon_core/tools/loader/ui/products_model.py +++ b/client/ayon_core/tools/loader/ui/products_model.py @@ -4,7 +4,7 @@ import qtawesome from qtpy import QtGui, QtCore from ayon_core.style import get_default_entity_icon_color -from ayon_core.tools.ayon_utils.widgets import get_qt_icon +from ayon_core.tools.utils import get_qt_icon PRODUCTS_MODEL_SENDER_NAME = "qt_products_model" diff --git a/client/ayon_core/tools/loader/ui/repres_widget.py b/client/ayon_core/tools/loader/ui/repres_widget.py index 27db8dda40..3b6b8f94bf 100644 --- a/client/ayon_core/tools/loader/ui/repres_widget.py +++ b/client/ayon_core/tools/loader/ui/repres_widget.py @@ -4,7 +4,7 @@ from qtpy import QtWidgets, QtGui, QtCore import qtawesome from ayon_core.style import get_default_entity_icon_color -from ayon_core.tools.ayon_utils.widgets import get_qt_icon +from ayon_core.tools.utils import get_qt_icon from ayon_core.tools.utils import DeselectableTreeView from .actions_utils import show_actions_menu diff --git a/client/ayon_core/tools/loader/ui/window.py b/client/ayon_core/tools/loader/ui/window.py index 104b64d81c..3a6f4679fa 100644 --- a/client/ayon_core/tools/loader/ui/window.py +++ b/client/ayon_core/tools/loader/ui/window.py @@ -10,7 +10,7 @@ from ayon_core.tools.utils import ( GoToCurrentButton, ) from ayon_core.tools.utils.lib import center_window -from ayon_core.tools.ayon_utils.widgets import ProjectsCombobox +from ayon_core.tools.utils import ProjectsCombobox from ayon_core.tools.loader.control import LoaderController from .folders_widget import LoaderFoldersWidget diff --git a/client/ayon_core/tools/publisher/widgets/create_context_widgets.py b/client/ayon_core/tools/publisher/widgets/create_context_widgets.py index d65a2ace8d..3ec1c491e8 100644 --- a/client/ayon_core/tools/publisher/widgets/create_context_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/create_context_widgets.py @@ -4,7 +4,7 @@ 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 +from ayon_core.tools.utils import FoldersWidget, TasksWidget class CreateSelectionModel(object): diff --git a/client/ayon_core/tools/publisher/widgets/folders_dialog.py b/client/ayon_core/tools/publisher/widgets/folders_dialog.py index 8f93264b2e..03336e10a6 100644 --- a/client/ayon_core/tools/publisher/widgets/folders_dialog.py +++ b/client/ayon_core/tools/publisher/widgets/folders_dialog.py @@ -1,8 +1,7 @@ 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 +from ayon_core.tools.utils import PlaceholderLineEdit, FoldersWidget class FoldersDialogController: diff --git a/client/ayon_core/tools/publisher/widgets/tasks_model.py b/client/ayon_core/tools/publisher/widgets/tasks_model.py index 8f00dc37a2..e36de80fcf 100644 --- a/client/ayon_core/tools/publisher/widgets/tasks_model.py +++ b/client/ayon_core/tools/publisher/widgets/tasks_model.py @@ -1,6 +1,7 @@ from qtpy import QtWidgets, QtCore, QtGui -from ayon_core.tools.utils.lib import get_default_task_icon +from ayon_core.style import get_default_entity_icon_color +from ayon_core.tools.utils import get_qt_icon TASK_NAME_ROLE = QtCore.Qt.UserRole + 1 TASK_TYPE_ROLE = QtCore.Qt.UserRole + 2 @@ -121,6 +122,11 @@ class TasksModel(QtGui.QStandardItemModel): item = self._items_by_name.pop(task_name) root_item.removeRow(item.row()) + icon = get_qt_icon({ + "type": "awesome-font", + "name": "fa.male", + "color": get_default_entity_icon_color(), + }) new_items = [] for task_name in new_task_names: if task_name in self._items_by_name: @@ -129,7 +135,7 @@ class TasksModel(QtGui.QStandardItemModel): item = QtGui.QStandardItem(task_name) item.setData(task_name, TASK_NAME_ROLE) if task_name: - item.setData(get_default_task_icon(), QtCore.Qt.DecorationRole) + item.setData(icon, QtCore.Qt.DecorationRole) self._items_by_name[task_name] = item new_items.append(item) diff --git a/client/ayon_core/tools/push_to_project/ui/window.py b/client/ayon_core/tools/push_to_project/ui/window.py index bc2fc6bf96..4d64509afd 100644 --- a/client/ayon_core/tools/push_to_project/ui/window.py +++ b/client/ayon_core/tools/push_to_project/ui/window.py @@ -5,8 +5,6 @@ from ayon_core.tools.utils import ( PlaceholderLineEdit, SeparatorWidget, set_style_property, -) -from ayon_core.tools.ayon_utils.widgets import ( ProjectsCombobox, FoldersWidget, TasksWidget, diff --git a/client/ayon_core/tools/sceneinventory/model.py b/client/ayon_core/tools/sceneinventory/model.py index e53b6aa4c3..df0dea7a3d 100644 --- a/client/ayon_core/tools/sceneinventory/model.py +++ b/client/ayon_core/tools/sceneinventory/model.py @@ -13,8 +13,8 @@ from ayon_core.pipeline import ( HeroVersionType, ) from ayon_core.style import get_default_entity_icon_color +from ayon_core.tools.utils import get_qt_icon from ayon_core.tools.utils.models import TreeModel, Item -from ayon_core.tools.ayon_utils.widgets import get_qt_icon def walk_hierarchy(node): 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 e46c28474f..3137e70214 100644 --- a/client/ayon_core/tools/sceneinventory/switch_dialog/folders_input.py +++ b/client/ayon_core/tools/sceneinventory/switch_dialog/folders_input.py @@ -5,8 +5,8 @@ from ayon_core.tools.utils import ( PlaceholderLineEdit, BaseClickableFrame, set_style_property, + FoldersWidget, ) -from ayon_core.tools.ayon_utils.widgets import FoldersWidget NOT_SET = object() diff --git a/client/ayon_core/tools/traypublisher/window.py b/client/ayon_core/tools/traypublisher/window.py index 988c22819a..ffa5d5b606 100644 --- a/client/ayon_core/tools/traypublisher/window.py +++ b/client/ayon_core/tools/traypublisher/window.py @@ -16,9 +16,10 @@ 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.ayon_utils.models import ProjectsModel -from ayon_core.tools.ayon_utils.widgets import ( +from ayon_core.tools.utils import ( + PlaceholderLineEdit, + get_ayon_qt_app, ProjectsQtModel, ProjectSortFilterProxy, PROJECT_NAME_ROLE, diff --git a/client/ayon_core/tools/workfiles/widgets/window.py b/client/ayon_core/tools/workfiles/widgets/window.py index 86a84b6195..8a2617d270 100644 --- a/client/ayon_core/tools/workfiles/widgets/window.py +++ b/client/ayon_core/tools/workfiles/widgets/window.py @@ -6,9 +6,13 @@ from ayon_core.tools.utils import ( MessageOverlayObject, ) -from ayon_core.tools.ayon_utils.widgets import FoldersWidget, TasksWidget from ayon_core.tools.workfiles.control import BaseWorkfileController -from ayon_core.tools.utils import GoToCurrentButton, RefreshButton +from ayon_core.tools.utils import ( + GoToCurrentButton, + RefreshButton, + FoldersWidget, + TasksWidget, +) from .side_panel import SidePanelWidget from .files_widget import FilesWidget From 20a9d4f9724c1f0a1ab79a43482741fc236e347c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 18 Mar 2024 18:01:45 +0100 Subject: [PATCH 065/150] move models to 'common_models' in tools --- .../tools/{ayon_utils/models => common_models}/__init__.py | 0 .../ayon_core/tools/{ayon_utils/models => common_models}/cache.py | 0 .../tools/{ayon_utils/models => common_models}/hierarchy.py | 0 .../tools/{ayon_utils/models => common_models}/projects.py | 0 .../tools/{ayon_utils/models => common_models}/selection.py | 0 .../tools/{ayon_utils/models => common_models}/thumbnails.py | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename client/ayon_core/tools/{ayon_utils/models => common_models}/__init__.py (100%) rename client/ayon_core/tools/{ayon_utils/models => common_models}/cache.py (100%) rename client/ayon_core/tools/{ayon_utils/models => common_models}/hierarchy.py (100%) rename client/ayon_core/tools/{ayon_utils/models => common_models}/projects.py (100%) rename client/ayon_core/tools/{ayon_utils/models => common_models}/selection.py (100%) rename client/ayon_core/tools/{ayon_utils/models => common_models}/thumbnails.py (100%) diff --git a/client/ayon_core/tools/ayon_utils/models/__init__.py b/client/ayon_core/tools/common_models/__init__.py similarity index 100% rename from client/ayon_core/tools/ayon_utils/models/__init__.py rename to client/ayon_core/tools/common_models/__init__.py diff --git a/client/ayon_core/tools/ayon_utils/models/cache.py b/client/ayon_core/tools/common_models/cache.py similarity index 100% rename from client/ayon_core/tools/ayon_utils/models/cache.py rename to client/ayon_core/tools/common_models/cache.py diff --git a/client/ayon_core/tools/ayon_utils/models/hierarchy.py b/client/ayon_core/tools/common_models/hierarchy.py similarity index 100% rename from client/ayon_core/tools/ayon_utils/models/hierarchy.py rename to client/ayon_core/tools/common_models/hierarchy.py diff --git a/client/ayon_core/tools/ayon_utils/models/projects.py b/client/ayon_core/tools/common_models/projects.py similarity index 100% rename from client/ayon_core/tools/ayon_utils/models/projects.py rename to client/ayon_core/tools/common_models/projects.py diff --git a/client/ayon_core/tools/ayon_utils/models/selection.py b/client/ayon_core/tools/common_models/selection.py similarity index 100% rename from client/ayon_core/tools/ayon_utils/models/selection.py rename to client/ayon_core/tools/common_models/selection.py diff --git a/client/ayon_core/tools/ayon_utils/models/thumbnails.py b/client/ayon_core/tools/common_models/thumbnails.py similarity index 100% rename from client/ayon_core/tools/ayon_utils/models/thumbnails.py rename to client/ayon_core/tools/common_models/thumbnails.py From 9770d8ffa9263b515fc89f1d110472b6dac4ebe5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 18 Mar 2024 18:02:13 +0100 Subject: [PATCH 066/150] use new location of tools models --- client/ayon_core/tools/context_dialog/window.py | 2 +- client/ayon_core/tools/launcher/control.py | 2 +- client/ayon_core/tools/launcher/ui/projects_widget.py | 2 +- client/ayon_core/tools/loader/control.py | 2 +- client/ayon_core/tools/loader/models/actions.py | 2 +- client/ayon_core/tools/loader/models/products.py | 2 +- client/ayon_core/tools/loader/models/site_sync.py | 2 +- client/ayon_core/tools/publisher/control.py | 2 +- .../ayon_core/tools/publisher/widgets/create_context_widgets.py | 2 +- client/ayon_core/tools/push_to_project/control.py | 2 +- client/ayon_core/tools/sceneinventory/control.py | 2 +- client/ayon_core/tools/traypublisher/window.py | 2 +- client/ayon_core/tools/utils/folders_widget.py | 2 +- client/ayon_core/tools/utils/projects_widget.py | 2 +- client/ayon_core/tools/workfiles/control.py | 2 +- 15 files changed, 15 insertions(+), 15 deletions(-) diff --git a/client/ayon_core/tools/context_dialog/window.py b/client/ayon_core/tools/context_dialog/window.py index df5dd868e8..828d771142 100644 --- a/client/ayon_core/tools/context_dialog/window.py +++ b/client/ayon_core/tools/context_dialog/window.py @@ -6,7 +6,7 @@ from qtpy import QtWidgets, QtCore, QtGui from ayon_core import style from ayon_core.lib.events import QueuedEventSystem -from ayon_core.tools.ayon_utils.models import ( +from ayon_core.tools.common_models import ( ProjectsModel, HierarchyModel, ) diff --git a/client/ayon_core/tools/launcher/control.py b/client/ayon_core/tools/launcher/control.py index 8780b211f1..abd0cd78d8 100644 --- a/client/ayon_core/tools/launcher/control.py +++ b/client/ayon_core/tools/launcher/control.py @@ -1,7 +1,7 @@ from ayon_core.lib import Logger from ayon_core.lib.events import QueuedEventSystem from ayon_core.settings import get_project_settings -from ayon_core.tools.ayon_utils.models import ProjectsModel, HierarchyModel +from ayon_core.tools.common_models import ProjectsModel, HierarchyModel from .abstract import AbstractLauncherFrontEnd, AbstractLauncherBackend from .models import LauncherSelectionModel, ActionsModel diff --git a/client/ayon_core/tools/launcher/ui/projects_widget.py b/client/ayon_core/tools/launcher/ui/projects_widget.py index 39fc67fc0f..e2af54b55d 100644 --- a/client/ayon_core/tools/launcher/ui/projects_widget.py +++ b/client/ayon_core/tools/launcher/ui/projects_widget.py @@ -7,7 +7,7 @@ from ayon_core.tools.utils import ( ProjectsQtModel, ProjectSortFilterProxy, ) -from ayon_core.tools.ayon_utils.models import PROJECTS_MODEL_SENDER +from ayon_core.tools.common_models import PROJECTS_MODEL_SENDER class ProjectIconView(QtWidgets.QListView): diff --git a/client/ayon_core/tools/loader/control.py b/client/ayon_core/tools/loader/control.py index 5995bd2cae..d8562f50ca 100644 --- a/client/ayon_core/tools/loader/control.py +++ b/client/ayon_core/tools/loader/control.py @@ -6,7 +6,7 @@ import ayon_api from ayon_core.lib.events import QueuedEventSystem from ayon_core.pipeline import Anatomy, get_current_context from ayon_core.host import ILoadHost -from ayon_core.tools.ayon_utils.models import ( +from ayon_core.tools.common_models import ( ProjectsModel, HierarchyModel, NestedCacheItem, diff --git a/client/ayon_core/tools/loader/models/actions.py b/client/ayon_core/tools/loader/models/actions.py index aab5ba49d1..ad2993af50 100644 --- a/client/ayon_core/tools/loader/models/actions.py +++ b/client/ayon_core/tools/loader/models/actions.py @@ -17,7 +17,7 @@ from ayon_core.pipeline.load import ( LoadError, IncompatibleLoaderError, ) -from ayon_core.tools.ayon_utils.models import NestedCacheItem +from ayon_core.tools.common_models import NestedCacheItem from ayon_core.tools.loader.abstract import ActionItem ACTIONS_MODEL_SENDER = "actions.model" diff --git a/client/ayon_core/tools/loader/models/products.py b/client/ayon_core/tools/loader/models/products.py index 63547bef8b..812446a012 100644 --- a/client/ayon_core/tools/loader/models/products.py +++ b/client/ayon_core/tools/loader/models/products.py @@ -6,7 +6,7 @@ import ayon_api from ayon_api.operations import OperationsSession from ayon_core.style import get_default_entity_icon_color -from ayon_core.tools.ayon_utils.models import NestedCacheItem +from ayon_core.tools.common_models import NestedCacheItem from ayon_core.tools.loader.abstract import ( ProductTypeItem, ProductItem, diff --git a/client/ayon_core/tools/loader/models/site_sync.py b/client/ayon_core/tools/loader/models/site_sync.py index daa9f7ba50..a589cf7fbe 100644 --- a/client/ayon_core/tools/loader/models/site_sync.py +++ b/client/ayon_core/tools/loader/models/site_sync.py @@ -4,7 +4,7 @@ from ayon_api import get_representations, get_versions_links from ayon_core.lib import Logger from ayon_core.addon import AddonsManager -from ayon_core.tools.ayon_utils.models import NestedCacheItem +from ayon_core.tools.common_models import NestedCacheItem from ayon_core.tools.loader.abstract import ActionItem DOWNLOAD_IDENTIFIER = "sitesync.download" diff --git a/client/ayon_core/tools/publisher/control.py b/client/ayon_core/tools/publisher/control.py index aaca0fea10..ede772b917 100644 --- a/client/ayon_core/tools/publisher/control.py +++ b/client/ayon_core/tools/publisher/control.py @@ -38,7 +38,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 +from ayon_core.tools.common_models import HierarchyModel # Define constant for plugin orders offset PLUGIN_ORDER_OFFSET = 0.5 diff --git a/client/ayon_core/tools/publisher/widgets/create_context_widgets.py b/client/ayon_core/tools/publisher/widgets/create_context_widgets.py index 3ec1c491e8..61223bbe75 100644 --- a/client/ayon_core/tools/publisher/widgets/create_context_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/create_context_widgets.py @@ -3,7 +3,7 @@ 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.common_models import HierarchyExpectedSelection from ayon_core.tools.utils import FoldersWidget, TasksWidget diff --git a/client/ayon_core/tools/push_to_project/control.py b/client/ayon_core/tools/push_to_project/control.py index d5acaadc2a..58447a8389 100644 --- a/client/ayon_core/tools/push_to_project/control.py +++ b/client/ayon_core/tools/push_to_project/control.py @@ -6,7 +6,7 @@ from ayon_core.settings import get_project_settings from ayon_core.lib import prepare_template_data from ayon_core.lib.events import QueuedEventSystem from ayon_core.pipeline.create import get_product_name_template -from ayon_core.tools.ayon_utils.models import ProjectsModel, HierarchyModel +from ayon_core.tools.common_models import ProjectsModel, HierarchyModel from .models import ( PushToProjectSelectionModel, diff --git a/client/ayon_core/tools/sceneinventory/control.py b/client/ayon_core/tools/sceneinventory/control.py index 16b889e855..77f4d60b22 100644 --- a/client/ayon_core/tools/sceneinventory/control.py +++ b/client/ayon_core/tools/sceneinventory/control.py @@ -6,7 +6,7 @@ from ayon_core.pipeline import ( registered_host, get_current_context, ) -from ayon_core.tools.ayon_utils.models import HierarchyModel +from ayon_core.tools.common_models import HierarchyModel from .models import SiteSyncModel diff --git a/client/ayon_core/tools/traypublisher/window.py b/client/ayon_core/tools/traypublisher/window.py index ffa5d5b606..4700e20531 100644 --- a/client/ayon_core/tools/traypublisher/window.py +++ b/client/ayon_core/tools/traypublisher/window.py @@ -16,7 +16,7 @@ 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.ayon_utils.models import ProjectsModel +from ayon_core.tools.common_models import ProjectsModel from ayon_core.tools.utils import ( PlaceholderLineEdit, get_ayon_qt_app, diff --git a/client/ayon_core/tools/utils/folders_widget.py b/client/ayon_core/tools/utils/folders_widget.py index a2519f90c2..2ad640de37 100644 --- a/client/ayon_core/tools/utils/folders_widget.py +++ b/client/ayon_core/tools/utils/folders_widget.py @@ -3,7 +3,7 @@ import collections from qtpy import QtWidgets, QtGui, QtCore from ayon_core.lib.events import QueuedEventSystem -from ayon_core.tools.ayon_utils.models import ( +from ayon_core.tools.common_models import ( HierarchyModel, HierarchyExpectedSelection, ) diff --git a/client/ayon_core/tools/utils/projects_widget.py b/client/ayon_core/tools/utils/projects_widget.py index e4ac69198a..fd361493ab 100644 --- a/client/ayon_core/tools/utils/projects_widget.py +++ b/client/ayon_core/tools/utils/projects_widget.py @@ -1,6 +1,6 @@ from qtpy import QtWidgets, QtCore, QtGui -from ayon_core.tools.ayon_utils.models import PROJECTS_MODEL_SENDER +from ayon_core.tools.common_models import PROJECTS_MODEL_SENDER from .lib import RefreshThread, get_qt_icon diff --git a/client/ayon_core/tools/workfiles/control.py b/client/ayon_core/tools/workfiles/control.py index 3111c4d443..7fa7af1662 100644 --- a/client/ayon_core/tools/workfiles/control.py +++ b/client/ayon_core/tools/workfiles/control.py @@ -15,7 +15,7 @@ from ayon_core.pipeline.context_tools import ( ) from ayon_core.pipeline.workfile import create_workdir_extra_folders -from ayon_core.tools.ayon_utils.models import ( +from ayon_core.tools.common_models import ( HierarchyModel, HierarchyExpectedSelection, ProjectsModel, From ab71411d122220ecd2edf4d5bde4ebe04f1a889e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 19 Mar 2024 11:37:15 +0100 Subject: [PATCH 067/150] :recycle: remove unneeded dependencies --- poetry.lock | 906 +------------------------------------------------ pyproject.toml | 18 +- 2 files changed, 5 insertions(+), 919 deletions(-) diff --git a/poetry.lock b/poetry.lock index d2476400f7..be5a3b2c2c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,145 +1,5 @@ # This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. -[[package]] -name = "aiohttp" -version = "3.9.3" -description = "Async http client/server framework (asyncio)" -optional = false -python-versions = ">=3.8" -files = [ - {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:939677b61f9d72a4fa2a042a5eee2a99a24001a67c13da113b2e30396567db54"}, - {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f5cd333fcf7590a18334c90f8c9147c837a6ec8a178e88d90a9b96ea03194cc"}, - {file = "aiohttp-3.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:82e6aa28dd46374f72093eda8bcd142f7771ee1eb9d1e223ff0fa7177a96b4a5"}, - {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f56455b0c2c7cc3b0c584815264461d07b177f903a04481dfc33e08a89f0c26b"}, - {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bca77a198bb6e69795ef2f09a5f4c12758487f83f33d63acde5f0d4919815768"}, - {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e083c285857b78ee21a96ba1eb1b5339733c3563f72980728ca2b08b53826ca5"}, - {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab40e6251c3873d86ea9b30a1ac6d7478c09277b32e14745d0d3c6e76e3c7e29"}, - {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df822ee7feaaeffb99c1a9e5e608800bd8eda6e5f18f5cfb0dc7eeb2eaa6bbec"}, - {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:acef0899fea7492145d2bbaaaec7b345c87753168589cc7faf0afec9afe9b747"}, - {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cd73265a9e5ea618014802ab01babf1940cecb90c9762d8b9e7d2cc1e1969ec6"}, - {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a78ed8a53a1221393d9637c01870248a6f4ea5b214a59a92a36f18151739452c"}, - {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6b0e029353361f1746bac2e4cc19b32f972ec03f0f943b390c4ab3371840aabf"}, - {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7cf5c9458e1e90e3c390c2639f1017a0379a99a94fdfad3a1fd966a2874bba52"}, - {file = "aiohttp-3.9.3-cp310-cp310-win32.whl", hash = "sha256:3e59c23c52765951b69ec45ddbbc9403a8761ee6f57253250c6e1536cacc758b"}, - {file = "aiohttp-3.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:055ce4f74b82551678291473f66dc9fb9048a50d8324278751926ff0ae7715e5"}, - {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6b88f9386ff1ad91ace19d2a1c0225896e28815ee09fc6a8932fded8cda97c3d"}, - {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c46956ed82961e31557b6857a5ca153c67e5476972e5f7190015018760938da2"}, - {file = "aiohttp-3.9.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:07b837ef0d2f252f96009e9b8435ec1fef68ef8b1461933253d318748ec1acdc"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad46e6f620574b3b4801c68255492e0159d1712271cc99d8bdf35f2043ec266"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ed3e046ea7b14938112ccd53d91c1539af3e6679b222f9469981e3dac7ba1ce"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:039df344b45ae0b34ac885ab5b53940b174530d4dd8a14ed8b0e2155b9dddccb"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7943c414d3a8d9235f5f15c22ace69787c140c80b718dcd57caaade95f7cd93b"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84871a243359bb42c12728f04d181a389718710129b36b6aad0fc4655a7647d4"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5eafe2c065df5401ba06821b9a054d9cb2848867f3c59801b5d07a0be3a380ae"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9d3c9b50f19704552f23b4eaea1fc082fdd82c63429a6506446cbd8737823da3"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:f033d80bc6283092613882dfe40419c6a6a1527e04fc69350e87a9df02bbc283"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:2c895a656dd7e061b2fd6bb77d971cc38f2afc277229ce7dd3552de8313a483e"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1f5a71d25cd8106eab05f8704cd9167b6e5187bcdf8f090a66c6d88b634802b4"}, - {file = "aiohttp-3.9.3-cp311-cp311-win32.whl", hash = "sha256:50fca156d718f8ced687a373f9e140c1bb765ca16e3d6f4fe116e3df7c05b2c5"}, - {file = "aiohttp-3.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:5fe9ce6c09668063b8447f85d43b8d1c4e5d3d7e92c63173e6180b2ac5d46dd8"}, - {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:38a19bc3b686ad55804ae931012f78f7a534cce165d089a2059f658f6c91fa60"}, - {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:770d015888c2a598b377bd2f663adfd947d78c0124cfe7b959e1ef39f5b13869"}, - {file = "aiohttp-3.9.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee43080e75fc92bf36219926c8e6de497f9b247301bbf88c5c7593d931426679"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52df73f14ed99cee84865b95a3d9e044f226320a87af208f068ecc33e0c35b96"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc9b311743a78043b26ffaeeb9715dc360335e5517832f5a8e339f8a43581e4d"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b955ed993491f1a5da7f92e98d5dad3c1e14dc175f74517c4e610b1f2456fb11"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:504b6981675ace64c28bf4a05a508af5cde526e36492c98916127f5a02354d53"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6fe5571784af92b6bc2fda8d1925cccdf24642d49546d3144948a6a1ed58ca5"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ba39e9c8627edc56544c8628cc180d88605df3892beeb2b94c9bc857774848ca"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e5e46b578c0e9db71d04c4b506a2121c0cb371dd89af17a0586ff6769d4c58c1"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:938a9653e1e0c592053f815f7028e41a3062e902095e5a7dc84617c87267ebd5"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:c3452ea726c76e92f3b9fae4b34a151981a9ec0a4847a627c43d71a15ac32aa6"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ff30218887e62209942f91ac1be902cc80cddb86bf00fbc6783b7a43b2bea26f"}, - {file = "aiohttp-3.9.3-cp312-cp312-win32.whl", hash = "sha256:38f307b41e0bea3294a9a2a87833191e4bcf89bb0365e83a8be3a58b31fb7f38"}, - {file = "aiohttp-3.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:b791a3143681a520c0a17e26ae7465f1b6f99461a28019d1a2f425236e6eedb5"}, - {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ed621426d961df79aa3b963ac7af0d40392956ffa9be022024cd16297b30c8c"}, - {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7f46acd6a194287b7e41e87957bfe2ad1ad88318d447caf5b090012f2c5bb528"}, - {file = "aiohttp-3.9.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feeb18a801aacb098220e2c3eea59a512362eb408d4afd0c242044c33ad6d542"}, - {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f734e38fd8666f53da904c52a23ce517f1b07722118d750405af7e4123933511"}, - {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b40670ec7e2156d8e57f70aec34a7216407848dfe6c693ef131ddf6e76feb672"}, - {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fdd215b7b7fd4a53994f238d0f46b7ba4ac4c0adb12452beee724ddd0743ae5d"}, - {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:017a21b0df49039c8f46ca0971b3a7fdc1f56741ab1240cb90ca408049766168"}, - {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e99abf0bba688259a496f966211c49a514e65afa9b3073a1fcee08856e04425b"}, - {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:648056db9a9fa565d3fa851880f99f45e3f9a771dd3ff3bb0c048ea83fb28194"}, - {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8aacb477dc26797ee089721536a292a664846489c49d3ef9725f992449eda5a8"}, - {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:522a11c934ea660ff8953eda090dcd2154d367dec1ae3c540aff9f8a5c109ab4"}, - {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5bce0dc147ca85caa5d33debc4f4d65e8e8b5c97c7f9f660f215fa74fc49a321"}, - {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b4af9f25b49a7be47c0972139e59ec0e8285c371049df1a63b6ca81fdd216a2"}, - {file = "aiohttp-3.9.3-cp38-cp38-win32.whl", hash = "sha256:298abd678033b8571995650ccee753d9458dfa0377be4dba91e4491da3f2be63"}, - {file = "aiohttp-3.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:69361bfdca5468c0488d7017b9b1e5ce769d40b46a9f4a2eed26b78619e9396c"}, - {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0fa43c32d1643f518491d9d3a730f85f5bbaedcbd7fbcae27435bb8b7a061b29"}, - {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:835a55b7ca49468aaaac0b217092dfdff370e6c215c9224c52f30daaa735c1c1"}, - {file = "aiohttp-3.9.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06a9b2c8837d9a94fae16c6223acc14b4dfdff216ab9b7202e07a9a09541168f"}, - {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abf151955990d23f84205286938796c55ff11bbfb4ccfada8c9c83ae6b3c89a3"}, - {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59c26c95975f26e662ca78fdf543d4eeaef70e533a672b4113dd888bd2423caa"}, - {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f95511dd5d0e05fd9728bac4096319f80615aaef4acbecb35a990afebe953b0e"}, - {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:595f105710293e76b9dc09f52e0dd896bd064a79346234b521f6b968ffdd8e58"}, - {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7c8b816c2b5af5c8a436df44ca08258fc1a13b449393a91484225fcb7545533"}, - {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f1088fa100bf46e7b398ffd9904f4808a0612e1d966b4aa43baa535d1b6341eb"}, - {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f59dfe57bb1ec82ac0698ebfcdb7bcd0e99c255bd637ff613760d5f33e7c81b3"}, - {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:361a1026c9dd4aba0109e4040e2aecf9884f5cfe1b1b1bd3d09419c205e2e53d"}, - {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:363afe77cfcbe3a36353d8ea133e904b108feea505aa4792dad6585a8192c55a"}, - {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e2c45c208c62e955e8256949eb225bd8b66a4c9b6865729a786f2aa79b72e9d"}, - {file = "aiohttp-3.9.3-cp39-cp39-win32.whl", hash = "sha256:f7217af2e14da0856e082e96ff637f14ae45c10a5714b63c77f26d8884cf1051"}, - {file = "aiohttp-3.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:27468897f628c627230dba07ec65dc8d0db566923c48f29e084ce382119802bc"}, - {file = "aiohttp-3.9.3.tar.gz", hash = "sha256:90842933e5d1ff760fae6caca4b2b3edba53ba8f4b71e95dacf2818a2aca06f7"}, -] - -[package.dependencies] -aiosignal = ">=1.1.2" -async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""} -attrs = ">=17.3.0" -frozenlist = ">=1.1.1" -multidict = ">=4.5,<7.0" -yarl = ">=1.0,<2.0" - -[package.extras] -speedups = ["Brotli", "aiodns", "brotlicffi"] - -[[package]] -name = "aiohttp-json-rpc" -version = "0.13.3" -description = "Implementation JSON-RPC 2.0 server and client using aiohttp on top of websockets transport" -optional = false -python-versions = ">=3.5" -files = [ - {file = "aiohttp-json-rpc-0.13.3.tar.gz", hash = "sha256:6237a104478c22c6ef96c7227a01d6832597b414e4b79a52d85593356a169e99"}, - {file = "aiohttp_json_rpc-0.13.3-py3-none-any.whl", hash = "sha256:4fbd197aced61bd2df7ae3237ead7d3e08833c2ccf48b8581e1828c95ebee680"}, -] - -[package.dependencies] -aiohttp = ">=3,<4" - -[[package]] -name = "aiohttp-middlewares" -version = "2.3.0" -description = "Collection of useful middlewares for aiohttp applications." -optional = false -python-versions = ">=3.8,<4.0" -files = [ - {file = "aiohttp_middlewares-2.3.0-py3-none-any.whl", hash = "sha256:4424b136a351b67b4c93da7d7505b56a342addaa324be30793f6cba463d18ac8"}, - {file = "aiohttp_middlewares-2.3.0.tar.gz", hash = "sha256:b2564c1dfa8dbcf7d2e101a6a03dcaad45464744531c269e8e582cb2dc551d08"}, -] - -[package.dependencies] -aiohttp = ">=3.8.1,<4.0.0" -async-timeout = ">=4.0.2,<5.0.0" -yarl = ">=1.5.1,<2.0.0" - -[[package]] -name = "aiosignal" -version = "1.3.1" -description = "aiosignal: a list of registered asynchronous callbacks" -optional = false -python-versions = ">=3.7" -files = [ - {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, - {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, -] - -[package.dependencies] -frozenlist = ">=1.1.0" - [[package]] name = "appdirs" version = "1.4.4" @@ -151,55 +11,6 @@ files = [ {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, ] -[[package]] -name = "arrow" -version = "1.3.0" -description = "Better dates & times for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80"}, - {file = "arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85"}, -] - -[package.dependencies] -python-dateutil = ">=2.7.0" -types-python-dateutil = ">=2.8.10" - -[package.extras] -doc = ["doc8", "sphinx (>=7.0.0)", "sphinx-autobuild", "sphinx-autodoc-typehints", "sphinx_rtd_theme (>=1.3.0)"] -test = ["dateparser (==1.*)", "pre-commit", "pytest", "pytest-cov", "pytest-mock", "pytz (==2021.1)", "simplejson (==3.*)"] - -[[package]] -name = "async-timeout" -version = "4.0.3" -description = "Timeout context manager for asyncio programs" -optional = false -python-versions = ">=3.7" -files = [ - {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, - {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, -] - -[[package]] -name = "attrs" -version = "23.2.0" -description = "Classes Without Boilerplate" -optional = false -python-versions = ">=3.7" -files = [ - {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, - {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, -] - -[package.extras] -cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[tests]", "pre-commit"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] -tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] -tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] - [[package]] name = "ayon-python-api" version = "1.0.1" @@ -338,36 +149,6 @@ files = [ {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] -[[package]] -name = "click" -version = "8.1.7" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.7" -files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "clique" -version = "1.6.1" -description = "Manage collections with common numerical component" -optional = false -python-versions = ">=2.7, <4.0" -files = [ - {file = "clique-1.6.1-py2.py3-none-any.whl", hash = "sha256:8619774fa035661928dd8c93cd805acf2d42533ccea1b536c09815ed426c9858"}, - {file = "clique-1.6.1.tar.gz", hash = "sha256:90165c1cf162d4dd1baef83ceaa1afc886b453e379094fa5b60ea470d1733e66"}, -] - -[package.extras] -dev = ["lowdown (>=0.2.0,<1)", "pytest (>=2.3.5,<5)", "pytest-cov (>=2,<3)", "pytest-runner (>=2.7,<3)", "sphinx (>=2,<4)", "sphinx-rtd-theme (>=0.1.6,<1)"] -doc = ["lowdown (>=0.2.0,<1)", "sphinx (>=2,<4)", "sphinx-rtd-theme (>=0.1.6,<1)"] -test = ["pytest (>=2.3.5,<5)", "pytest-cov (>=2,<3)", "pytest-runner (>=2.7,<3)"] - [[package]] name = "codespell" version = "2.2.6" @@ -407,16 +188,6 @@ files = [ {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, ] -[[package]] -name = "evdev" -version = "1.7.0" -description = "Bindings to the Linux input handling subsystem" -optional = false -python-versions = ">=3.6" -files = [ - {file = "evdev-1.7.0.tar.gz", hash = "sha256:95bd2a1e0c6ce2cd7a2ecc6e6cd9736ff794b3ad5cb54d81d8cbc2e414d0b870"}, -] - [[package]] name = "exceptiongroup" version = "1.2.0" @@ -447,92 +218,6 @@ docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1 testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] typing = ["typing-extensions (>=4.8)"] -[[package]] -name = "frozenlist" -version = "1.4.1" -description = "A list-like structure which implements collections.abc.MutableSequence" -optional = false -python-versions = ">=3.8" -files = [ - {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, - {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, - {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, - {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, - {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, - {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, - {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, - {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, - {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, - {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, - {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, - {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, - {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, - {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, - {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, -] - [[package]] name = "identify" version = "2.5.35" @@ -569,140 +254,6 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] -[[package]] -name = "jsonschema" -version = "4.21.1" -description = "An implementation of JSON Schema validation for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "jsonschema-4.21.1-py3-none-any.whl", hash = "sha256:7996507afae316306f9e2290407761157c6f78002dcf7419acb99822143d1c6f"}, - {file = "jsonschema-4.21.1.tar.gz", hash = "sha256:85727c00279f5fa6bedbe6238d2aa6403bedd8b4864ab11207d07df3cc1b2ee5"}, -] - -[package.dependencies] -attrs = ">=22.2.0" -jsonschema-specifications = ">=2023.03.6" -referencing = ">=0.28.4" -rpds-py = ">=0.7.1" - -[package.extras] -format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] -format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"] - -[[package]] -name = "jsonschema-specifications" -version = "2023.12.1" -description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" -optional = false -python-versions = ">=3.8" -files = [ - {file = "jsonschema_specifications-2023.12.1-py3-none-any.whl", hash = "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c"}, - {file = "jsonschema_specifications-2023.12.1.tar.gz", hash = "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc"}, -] - -[package.dependencies] -referencing = ">=0.31.0" - -[[package]] -name = "multidict" -version = "6.0.5" -description = "multidict implementation" -optional = false -python-versions = ">=3.7" -files = [ - {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"}, - {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"}, - {file = "multidict-6.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc"}, - {file = "multidict-6.0.5-cp310-cp310-win32.whl", hash = "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319"}, - {file = "multidict-6.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8"}, - {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"}, - {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"}, - {file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"}, - {file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"}, - {file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"}, - {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"}, - {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"}, - {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"}, - {file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"}, - {file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"}, - {file = "multidict-6.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc"}, - {file = "multidict-6.0.5-cp37-cp37m-win32.whl", hash = "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee"}, - {file = "multidict-6.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423"}, - {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54"}, - {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d"}, - {file = "multidict-6.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44"}, - {file = "multidict-6.0.5-cp38-cp38-win32.whl", hash = "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241"}, - {file = "multidict-6.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c"}, - {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929"}, - {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9"}, - {file = "multidict-6.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c"}, - {file = "multidict-6.0.5-cp39-cp39-win32.whl", hash = "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b"}, - {file = "multidict-6.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755"}, - {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"}, - {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, -] - [[package]] name = "nodeenv" version = "1.8.0" @@ -776,133 +327,6 @@ nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" -[[package]] -name = "pyblish-base" -version = "1.8.11" -description = "Plug-in driven automation framework for content" -optional = false -python-versions = "*" -files = [ - {file = "pyblish-base-1.8.11.tar.gz", hash = "sha256:86dfeec0567430eb7eb25f89a18312054147a729ec66f6ac8c7e421fd15b66e1"}, - {file = "pyblish_base-1.8.11-py2.py3-none-any.whl", hash = "sha256:c321be7020c946fe9dfa11941241bd985a572c5009198b4f9810e5afad1f0b4b"}, -] - -[[package]] -name = "pynput" -version = "1.7.6" -description = "Monitor and control user input devices" -optional = false -python-versions = "*" -files = [ - {file = "pynput-1.7.6-py2.py3-none-any.whl", hash = "sha256:19861b2a0c430d646489852f89500e0c9332e295f2c020e7c2775e7046aa2e2f"}, - {file = "pynput-1.7.6.tar.gz", hash = "sha256:3a5726546da54116b687785d38b1db56997ce1d28e53e8d22fc656d8b92e533c"}, -] - -[package.dependencies] -evdev = {version = ">=1.3", markers = "sys_platform in \"linux\""} -pyobjc-framework-ApplicationServices = {version = ">=8.0", markers = "sys_platform == \"darwin\""} -pyobjc-framework-Quartz = {version = ">=8.0", markers = "sys_platform == \"darwin\""} -python-xlib = {version = ">=0.17", markers = "sys_platform in \"linux\""} -six = "*" - -[[package]] -name = "pyobjc-core" -version = "10.2" -description = "Python<->ObjC Interoperability Module" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-core-10.2.tar.gz", hash = "sha256:0153206e15d0e0d7abd53ee8a7fbaf5606602a032e177a028fc8589516a8771c"}, - {file = "pyobjc_core-10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b8eab50ce7f17017a0f1d68c3b7e88bb1bb033415fdff62b8e0a9ee4ab72f242"}, - {file = "pyobjc_core-10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f2115971463073426ab926416e17e5c16de5b90d1a1f2a2d8724637eb1c21308"}, - {file = "pyobjc_core-10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a70546246177c23acb323c9324330e37638f1a0a3d13664abcba3bb75e43012c"}, - {file = "pyobjc_core-10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a9b5a215080d13bd7526031d21d5eb27a410780878d863f486053a0eba7ca9a5"}, - {file = "pyobjc_core-10.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:eb1ab700a44bcc4ceb125091dfaae0b998b767b49990df5fdc83eb58158d8e3f"}, - {file = "pyobjc_core-10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c9a7163aff9c47d654f835f80361c1b112886ec754800d34e75d1e02ff52c3d7"}, -] - -[[package]] -name = "pyobjc-framework-applicationservices" -version = "10.2" -description = "Wrappers for the framework ApplicationServices on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-ApplicationServices-10.2.tar.gz", hash = "sha256:f83d6ed3320afb6648be6defafe0f05bac00d0281fc84ee4766ff977309b659f"}, - {file = "pyobjc_framework_ApplicationServices-10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2aebfed888f9bcb4f11d93f9ef9a76d561e92848dcb6011da5d5e9d3593371be"}, - {file = "pyobjc_framework_ApplicationServices-10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:edfd3153e64ee9573bcff7ccaa1fbbbd6964658f187464c461ad34f24552bc85"}, - {file = "pyobjc_framework_ApplicationServices-10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9d2c89b246c19a041221ff36e9121c92e86a4422016f809a40f5ce3d647882d9"}, - {file = "pyobjc_framework_ApplicationServices-10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ee1e69947f31aad5fdec44921ce37f7f921faf50a0ceb27ed40b6d54f4b15d0e"}, - {file = "pyobjc_framework_ApplicationServices-10.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:101f5b09d71e55bd39e6e91f0787433805d422622336b72fde969a7c54528045"}, - {file = "pyobjc_framework_ApplicationServices-10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a3ef00c9aea09c5ef5840b8749d0753249869bc30e124145b763cd0b4b81155"}, -] - -[package.dependencies] -pyobjc-core = ">=10.2" -pyobjc-framework-Cocoa = ">=10.2" -pyobjc-framework-CoreText = ">=10.2" -pyobjc-framework-Quartz = ">=10.2" - -[[package]] -name = "pyobjc-framework-cocoa" -version = "10.2" -description = "Wrappers for the Cocoa frameworks on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-Cocoa-10.2.tar.gz", hash = "sha256:6383141379636b13855dca1b39c032752862b829f93a49d7ddb35046abfdc035"}, - {file = "pyobjc_framework_Cocoa-10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9227b4f271fda2250f5a88cbc686ff30ae02c0f923bb7854bb47972397496b2"}, - {file = "pyobjc_framework_Cocoa-10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6a6042b7703bdc33b7491959c715c1e810a3f8c7a560c94b36e00ef321480797"}, - {file = "pyobjc_framework_Cocoa-10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:18886d5013cd7dc7ecd6e0df5134c767569b5247fc10a5e293c72ee3937b217b"}, - {file = "pyobjc_framework_Cocoa-10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ecf01400ee698d2e0ff4c907bcf9608d9d710e97203fbb97b37d208507a9362"}, - {file = "pyobjc_framework_Cocoa-10.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:0def036a7b24e3ae37a244c77bec96b7c9c8384bf6bb4d33369f0a0c8807a70d"}, - {file = "pyobjc_framework_Cocoa-10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5f47ecc393bc1019c4b47e8653207188df784ac006ad54d8c2eb528906ff7013"}, -] - -[package.dependencies] -pyobjc-core = ">=10.2" - -[[package]] -name = "pyobjc-framework-coretext" -version = "10.2" -description = "Wrappers for the framework CoreText on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-CoreText-10.2.tar.gz", hash = "sha256:59ef8ca8d88bb53ce9980dda0b8094daa3e2dabe355847365ba965ff0b49f961"}, - {file = "pyobjc_framework_CoreText-10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:44052f752f42b62d342fa8aced5d1b8928831e70830eccddc594726d40500d5c"}, - {file = "pyobjc_framework_CoreText-10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0bc278f509a3fd3eea89124d81e77de11af10167c0df0d0cc15a369f060465a0"}, - {file = "pyobjc_framework_CoreText-10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7b819119dc859e49c0ce9040ae09d6a3bd66658003793f486ef5a21e46a2d34f"}, - {file = "pyobjc_framework_CoreText-10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2719c57ff08af6e4fdcddd0fa5eda56113808a1690c3325f1c6926740817f9a1"}, - {file = "pyobjc_framework_CoreText-10.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:8239ce92f9496587a60fc1bfd4994136832bad99405bb45572f92d960cbe746e"}, - {file = "pyobjc_framework_CoreText-10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:80a1d207fcdb2999841daa430c83d760ac1a3f2f65c605949fc5ff789425b1f6"}, -] - -[package.dependencies] -pyobjc-core = ">=10.2" -pyobjc-framework-Cocoa = ">=10.2" -pyobjc-framework-Quartz = ">=10.2" - -[[package]] -name = "pyobjc-framework-quartz" -version = "10.2" -description = "Wrappers for the Quartz frameworks on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-Quartz-10.2.tar.gz", hash = "sha256:9b947e081f5bd6cd01c99ab5d62c36500d2d6e8d3b87421c1cbb7f9c885555eb"}, - {file = "pyobjc_framework_Quartz-10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bc0ab739259a717d9d13a739434991b54eb8963ad7c27f9f6d04d68531fb479b"}, - {file = "pyobjc_framework_Quartz-10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2a74d00e933c1e1a1820839323dc5cf252bee8bb98e2a298d961f7ae7905ce71"}, - {file = "pyobjc_framework_Quartz-10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3e8e33246d966c2bd7f5ee2cf3b431582fa434a6ec2b6dbe580045ebf1f55be5"}, - {file = "pyobjc_framework_Quartz-10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c6ca490eff1be0dd8dc7726edde79c97e21ec1afcf55f75962a79e27b4eb2961"}, - {file = "pyobjc_framework_Quartz-10.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d3d54d9fa50de09ee8994248151def58f30b4738eb20755b0bdd5ee1e1f5883d"}, - {file = "pyobjc_framework_Quartz-10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:520c8031b2389110f80070b078dde1968caaecb10921f8070046c26132ac9286"}, -] - -[package.dependencies] -pyobjc-core = ">=10.2" -pyobjc-framework-Cocoa = ">=10.2" - [[package]] name = "pytest" version = "8.1.1" @@ -942,34 +366,6 @@ pytest = ">=7.4" [package.extras] test = ["covdefaults (>=2.3)", "coverage (>=7.3)", "pytest-mock (>=3.11.1)"] -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, - {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "python-xlib" -version = "0.33" -description = "Python X Library" -optional = false -python-versions = "*" -files = [ - {file = "python-xlib-0.33.tar.gz", hash = "sha256:55af7906a2c75ce6cb280a584776080602444f75815a7aff4d287bb2d7018b32"}, - {file = "python_xlib-0.33-py2.py3-none-any.whl", hash = "sha256:c3534038d42e0df2f1392a1b30a15a4ff5fdc2b86cfa94f072bf11b10a164398"}, -] - -[package.dependencies] -six = ">=1.10.0" - [[package]] name = "pyyaml" version = "6.0.1" @@ -1029,53 +425,6 @@ files = [ {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] -[[package]] -name = "qtawesome" -version = "0.7.3" -description = "FontAwesome icons in PyQt and PySide applications" -optional = false -python-versions = "*" -files = [ - {file = "QtAwesome-0.7.3-py2.py3-none-any.whl", hash = "sha256:ddf4530b4af71cec13b24b88a4cdb56ec85b1e44c43c42d0698804c7137b09b0"}, - {file = "QtAwesome-0.7.3.tar.gz", hash = "sha256:b98b9038d19190e83ab26d91c4d8fc3a36591ee2bc7f5016d4438b8240d097bd"}, -] - -[package.dependencies] -qtpy = "*" -six = "*" - -[[package]] -name = "qtpy" -version = "2.4.1" -description = "Provides an abstraction layer on top of the various Qt bindings (PyQt5/6 and PySide2/6)." -optional = false -python-versions = ">=3.7" -files = [ - {file = "QtPy-2.4.1-py3-none-any.whl", hash = "sha256:1c1d8c4fa2c884ae742b069151b0abe15b3f70491f3972698c683b8e38de839b"}, - {file = "QtPy-2.4.1.tar.gz", hash = "sha256:a5a15ffd519550a1361bdc56ffc07fda56a6af7292f17c7b395d4083af632987"}, -] - -[package.dependencies] -packaging = "*" - -[package.extras] -test = ["pytest (>=6,!=7.0.0,!=7.0.1)", "pytest-cov (>=3.0.0)", "pytest-qt"] - -[[package]] -name = "referencing" -version = "0.34.0" -description = "JSON Referencing + Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "referencing-0.34.0-py3-none-any.whl", hash = "sha256:d53ae300ceddd3169f1ffa9caf2cb7b769e92657e4fafb23d34b93679116dfd4"}, - {file = "referencing-0.34.0.tar.gz", hash = "sha256:5773bd84ef41799a5a8ca72dc34590c041eb01bf9aa02632b4a973fb0181a844"}, -] - -[package.dependencies] -attrs = ">=22.2.0" -rpds-py = ">=0.7.0" - [[package]] name = "requests" version = "2.31.0" @@ -1097,114 +446,6 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] -[[package]] -name = "rpds-py" -version = "0.18.0" -description = "Python bindings to Rust's persistent data structures (rpds)" -optional = false -python-versions = ">=3.8" -files = [ - {file = "rpds_py-0.18.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:5b4e7d8d6c9b2e8ee2d55c90b59c707ca59bc30058269b3db7b1f8df5763557e"}, - {file = "rpds_py-0.18.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c463ed05f9dfb9baebef68048aed8dcdc94411e4bf3d33a39ba97e271624f8f7"}, - {file = "rpds_py-0.18.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01e36a39af54a30f28b73096dd39b6802eddd04c90dbe161c1b8dbe22353189f"}, - {file = "rpds_py-0.18.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d62dec4976954a23d7f91f2f4530852b0c7608116c257833922a896101336c51"}, - {file = "rpds_py-0.18.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd18772815d5f008fa03d2b9a681ae38d5ae9f0e599f7dda233c439fcaa00d40"}, - {file = "rpds_py-0.18.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:923d39efa3cfb7279a0327e337a7958bff00cc447fd07a25cddb0a1cc9a6d2da"}, - {file = "rpds_py-0.18.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39514da80f971362f9267c600b6d459bfbbc549cffc2cef8e47474fddc9b45b1"}, - {file = "rpds_py-0.18.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a34d557a42aa28bd5c48a023c570219ba2593bcbbb8dc1b98d8cf5d529ab1434"}, - {file = "rpds_py-0.18.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:93df1de2f7f7239dc9cc5a4a12408ee1598725036bd2dedadc14d94525192fc3"}, - {file = "rpds_py-0.18.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:34b18ba135c687f4dac449aa5157d36e2cbb7c03cbea4ddbd88604e076aa836e"}, - {file = "rpds_py-0.18.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c0b5dcf9193625afd8ecc92312d6ed78781c46ecbf39af9ad4681fc9f464af88"}, - {file = "rpds_py-0.18.0-cp310-none-win32.whl", hash = "sha256:c4325ff0442a12113a6379af66978c3fe562f846763287ef66bdc1d57925d337"}, - {file = "rpds_py-0.18.0-cp310-none-win_amd64.whl", hash = "sha256:7223a2a5fe0d217e60a60cdae28d6949140dde9c3bcc714063c5b463065e3d66"}, - {file = "rpds_py-0.18.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3a96e0c6a41dcdba3a0a581bbf6c44bb863f27c541547fb4b9711fd8cf0ffad4"}, - {file = "rpds_py-0.18.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30f43887bbae0d49113cbaab729a112251a940e9b274536613097ab8b4899cf6"}, - {file = "rpds_py-0.18.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fcb25daa9219b4cf3a0ab24b0eb9a5cc8949ed4dc72acb8fa16b7e1681aa3c58"}, - {file = "rpds_py-0.18.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d68c93e381010662ab873fea609bf6c0f428b6d0bb00f2c6939782e0818d37bf"}, - {file = "rpds_py-0.18.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b34b7aa8b261c1dbf7720b5d6f01f38243e9b9daf7e6b8bc1fd4657000062f2c"}, - {file = "rpds_py-0.18.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e6d75ab12b0bbab7215e5d40f1e5b738aa539598db27ef83b2ec46747df90e1"}, - {file = "rpds_py-0.18.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b8612cd233543a3781bc659c731b9d607de65890085098986dfd573fc2befe5"}, - {file = "rpds_py-0.18.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:aec493917dd45e3c69d00a8874e7cbed844efd935595ef78a0f25f14312e33c6"}, - {file = "rpds_py-0.18.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:661d25cbffaf8cc42e971dd570d87cb29a665f49f4abe1f9e76be9a5182c4688"}, - {file = "rpds_py-0.18.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1df3659d26f539ac74fb3b0c481cdf9d725386e3552c6fa2974f4d33d78e544b"}, - {file = "rpds_py-0.18.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a1ce3ba137ed54f83e56fb983a5859a27d43a40188ba798993812fed73c70836"}, - {file = "rpds_py-0.18.0-cp311-none-win32.whl", hash = "sha256:69e64831e22a6b377772e7fb337533c365085b31619005802a79242fee620bc1"}, - {file = "rpds_py-0.18.0-cp311-none-win_amd64.whl", hash = "sha256:998e33ad22dc7ec7e030b3df701c43630b5bc0d8fbc2267653577e3fec279afa"}, - {file = "rpds_py-0.18.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7f2facbd386dd60cbbf1a794181e6aa0bd429bd78bfdf775436020172e2a23f0"}, - {file = "rpds_py-0.18.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1d9a5be316c15ffb2b3c405c4ff14448c36b4435be062a7f578ccd8b01f0c4d8"}, - {file = "rpds_py-0.18.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd5bf1af8efe569654bbef5a3e0a56eca45f87cfcffab31dd8dde70da5982475"}, - {file = "rpds_py-0.18.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5417558f6887e9b6b65b4527232553c139b57ec42c64570569b155262ac0754f"}, - {file = "rpds_py-0.18.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:56a737287efecafc16f6d067c2ea0117abadcd078d58721f967952db329a3e5c"}, - {file = "rpds_py-0.18.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8f03bccbd8586e9dd37219bce4d4e0d3ab492e6b3b533e973fa08a112cb2ffc9"}, - {file = "rpds_py-0.18.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4457a94da0d5c53dc4b3e4de1158bdab077db23c53232f37a3cb7afdb053a4e3"}, - {file = "rpds_py-0.18.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0ab39c1ba9023914297dd88ec3b3b3c3f33671baeb6acf82ad7ce883f6e8e157"}, - {file = "rpds_py-0.18.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9d54553c1136b50fd12cc17e5b11ad07374c316df307e4cfd6441bea5fb68496"}, - {file = "rpds_py-0.18.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0af039631b6de0397ab2ba16eaf2872e9f8fca391b44d3d8cac317860a700a3f"}, - {file = "rpds_py-0.18.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:84ffab12db93b5f6bad84c712c92060a2d321b35c3c9960b43d08d0f639d60d7"}, - {file = "rpds_py-0.18.0-cp312-none-win32.whl", hash = "sha256:685537e07897f173abcf67258bee3c05c374fa6fff89d4c7e42fb391b0605e98"}, - {file = "rpds_py-0.18.0-cp312-none-win_amd64.whl", hash = "sha256:e003b002ec72c8d5a3e3da2989c7d6065b47d9eaa70cd8808b5384fbb970f4ec"}, - {file = "rpds_py-0.18.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:08f9ad53c3f31dfb4baa00da22f1e862900f45908383c062c27628754af2e88e"}, - {file = "rpds_py-0.18.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c0013fe6b46aa496a6749c77e00a3eb07952832ad6166bd481c74bda0dcb6d58"}, - {file = "rpds_py-0.18.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e32a92116d4f2a80b629778280103d2a510a5b3f6314ceccd6e38006b5e92dcb"}, - {file = "rpds_py-0.18.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e541ec6f2ec456934fd279a3120f856cd0aedd209fc3852eca563f81738f6861"}, - {file = "rpds_py-0.18.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bed88b9a458e354014d662d47e7a5baafd7ff81c780fd91584a10d6ec842cb73"}, - {file = "rpds_py-0.18.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2644e47de560eb7bd55c20fc59f6daa04682655c58d08185a9b95c1970fa1e07"}, - {file = "rpds_py-0.18.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e8916ae4c720529e18afa0b879473049e95949bf97042e938530e072fde061d"}, - {file = "rpds_py-0.18.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:465a3eb5659338cf2a9243e50ad9b2296fa15061736d6e26240e713522b6235c"}, - {file = "rpds_py-0.18.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ea7d4a99f3b38c37eac212dbd6ec42b7a5ec51e2c74b5d3223e43c811609e65f"}, - {file = "rpds_py-0.18.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:67071a6171e92b6da534b8ae326505f7c18022c6f19072a81dcf40db2638767c"}, - {file = "rpds_py-0.18.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:41ef53e7c58aa4ef281da975f62c258950f54b76ec8e45941e93a3d1d8580594"}, - {file = "rpds_py-0.18.0-cp38-none-win32.whl", hash = "sha256:fdea4952db2793c4ad0bdccd27c1d8fdd1423a92f04598bc39425bcc2b8ee46e"}, - {file = "rpds_py-0.18.0-cp38-none-win_amd64.whl", hash = "sha256:7cd863afe7336c62ec78d7d1349a2f34c007a3cc6c2369d667c65aeec412a5b1"}, - {file = "rpds_py-0.18.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:5307def11a35f5ae4581a0b658b0af8178c65c530e94893345bebf41cc139d33"}, - {file = "rpds_py-0.18.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:77f195baa60a54ef9d2de16fbbfd3ff8b04edc0c0140a761b56c267ac11aa467"}, - {file = "rpds_py-0.18.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39f5441553f1c2aed4de4377178ad8ff8f9d733723d6c66d983d75341de265ab"}, - {file = "rpds_py-0.18.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a00312dea9310d4cb7dbd7787e722d2e86a95c2db92fbd7d0155f97127bcb40"}, - {file = "rpds_py-0.18.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f2fc11e8fe034ee3c34d316d0ad8808f45bc3b9ce5857ff29d513f3ff2923a1"}, - {file = "rpds_py-0.18.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:586f8204935b9ec884500498ccc91aa869fc652c40c093bd9e1471fbcc25c022"}, - {file = "rpds_py-0.18.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddc2f4dfd396c7bfa18e6ce371cba60e4cf9d2e5cdb71376aa2da264605b60b9"}, - {file = "rpds_py-0.18.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ddcba87675b6d509139d1b521e0c8250e967e63b5909a7e8f8944d0f90ff36f"}, - {file = "rpds_py-0.18.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7bd339195d84439cbe5771546fe8a4e8a7a045417d8f9de9a368c434e42a721e"}, - {file = "rpds_py-0.18.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:d7c36232a90d4755b720fbd76739d8891732b18cf240a9c645d75f00639a9024"}, - {file = "rpds_py-0.18.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6b0817e34942b2ca527b0e9298373e7cc75f429e8da2055607f4931fded23e20"}, - {file = "rpds_py-0.18.0-cp39-none-win32.whl", hash = "sha256:99f70b740dc04d09e6b2699b675874367885217a2e9f782bdf5395632ac663b7"}, - {file = "rpds_py-0.18.0-cp39-none-win_amd64.whl", hash = "sha256:6ef687afab047554a2d366e112dd187b62d261d49eb79b77e386f94644363294"}, - {file = "rpds_py-0.18.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ad36cfb355e24f1bd37cac88c112cd7730873f20fb0bdaf8ba59eedf8216079f"}, - {file = "rpds_py-0.18.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:36b3ee798c58ace201289024b52788161e1ea133e4ac93fba7d49da5fec0ef9e"}, - {file = "rpds_py-0.18.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8a2f084546cc59ea99fda8e070be2fd140c3092dc11524a71aa8f0f3d5a55ca"}, - {file = "rpds_py-0.18.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e4461d0f003a0aa9be2bdd1b798a041f177189c1a0f7619fe8c95ad08d9a45d7"}, - {file = "rpds_py-0.18.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8db715ebe3bb7d86d77ac1826f7d67ec11a70dbd2376b7cc214199360517b641"}, - {file = "rpds_py-0.18.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:793968759cd0d96cac1e367afd70c235867831983f876a53389ad869b043c948"}, - {file = "rpds_py-0.18.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66e6a3af5a75363d2c9a48b07cb27c4ea542938b1a2e93b15a503cdfa8490795"}, - {file = "rpds_py-0.18.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6ef0befbb5d79cf32d0266f5cff01545602344eda89480e1dd88aca964260b18"}, - {file = "rpds_py-0.18.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:1d4acf42190d449d5e89654d5c1ed3a4f17925eec71f05e2a41414689cda02d1"}, - {file = "rpds_py-0.18.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:a5f446dd5055667aabaee78487f2b5ab72e244f9bc0b2ffebfeec79051679984"}, - {file = "rpds_py-0.18.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:9dbbeb27f4e70bfd9eec1be5477517365afe05a9b2c441a0b21929ee61048124"}, - {file = "rpds_py-0.18.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:22806714311a69fd0af9b35b7be97c18a0fc2826e6827dbb3a8c94eac6cf7eeb"}, - {file = "rpds_py-0.18.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:b34ae4636dfc4e76a438ab826a0d1eed2589ca7d9a1b2d5bb546978ac6485461"}, - {file = "rpds_py-0.18.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c8370641f1a7f0e0669ddccca22f1da893cef7628396431eb445d46d893e5cd"}, - {file = "rpds_py-0.18.0-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c8362467a0fdeccd47935f22c256bec5e6abe543bf0d66e3d3d57a8fb5731863"}, - {file = "rpds_py-0.18.0-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11a8c85ef4a07a7638180bf04fe189d12757c696eb41f310d2426895356dcf05"}, - {file = "rpds_py-0.18.0-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b316144e85316da2723f9d8dc75bada12fa58489a527091fa1d5a612643d1a0e"}, - {file = "rpds_py-0.18.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf1ea2e34868f6fbf070e1af291c8180480310173de0b0c43fc38a02929fc0e3"}, - {file = "rpds_py-0.18.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e546e768d08ad55b20b11dbb78a745151acbd938f8f00d0cfbabe8b0199b9880"}, - {file = "rpds_py-0.18.0-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:4901165d170a5fde6f589acb90a6b33629ad1ec976d4529e769c6f3d885e3e80"}, - {file = "rpds_py-0.18.0-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:618a3d6cae6ef8ec88bb76dd80b83cfe415ad4f1d942ca2a903bf6b6ff97a2da"}, - {file = "rpds_py-0.18.0-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ed4eb745efbff0a8e9587d22a84be94a5eb7d2d99c02dacf7bd0911713ed14dd"}, - {file = "rpds_py-0.18.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:6c81e5f372cd0dc5dc4809553d34f832f60a46034a5f187756d9b90586c2c307"}, - {file = "rpds_py-0.18.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:43fbac5f22e25bee1d482c97474f930a353542855f05c1161fd804c9dc74a09d"}, - {file = "rpds_py-0.18.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d7faa6f14017c0b1e69f5e2c357b998731ea75a442ab3841c0dbbbfe902d2c4"}, - {file = "rpds_py-0.18.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:08231ac30a842bd04daabc4d71fddd7e6d26189406d5a69535638e4dcb88fe76"}, - {file = "rpds_py-0.18.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:044a3e61a7c2dafacae99d1e722cc2d4c05280790ec5a05031b3876809d89a5c"}, - {file = "rpds_py-0.18.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3f26b5bd1079acdb0c7a5645e350fe54d16b17bfc5e71f371c449383d3342e17"}, - {file = "rpds_py-0.18.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:482103aed1dfe2f3b71a58eff35ba105289b8d862551ea576bd15479aba01f66"}, - {file = "rpds_py-0.18.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1374f4129f9bcca53a1bba0bb86bf78325a0374577cf7e9e4cd046b1e6f20e24"}, - {file = "rpds_py-0.18.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:635dc434ff724b178cb192c70016cc0ad25a275228f749ee0daf0eddbc8183b1"}, - {file = "rpds_py-0.18.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:bc362ee4e314870a70f4ae88772d72d877246537d9f8cb8f7eacf10884862432"}, - {file = "rpds_py-0.18.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:4832d7d380477521a8c1644bbab6588dfedea5e30a7d967b5fb75977c45fd77f"}, - {file = "rpds_py-0.18.0.tar.gz", hash = "sha256:42821446ee7a76f5d9f71f9e33a4fb2ffd724bb3e7f93386150b61a43115788d"}, -] - [[package]] name = "ruff" version = "0.3.3" @@ -1258,17 +499,6 @@ files = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] -[[package]] -name = "speedcopy" -version = "2.1.5" -description = "Replacement or alternative for python copyfile() utilizing server side copy on network shares for faster copying." -optional = false -python-versions = "*" -files = [ - {file = "speedcopy-2.1.5-py3-none-any.whl", hash = "sha256:903d0b466c2bef7c07dfac17493cdfbc09aadd70e947199c81caa6c6da2c095f"}, - {file = "speedcopy-2.1.5.tar.gz", hash = "sha256:9d6c482300791f02462ad451730ae247901978eae9e25290ca352de964698c82"}, -] - [[package]] name = "tomli" version = "2.0.1" @@ -1280,17 +510,6 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] -[[package]] -name = "types-python-dateutil" -version = "2.9.0.20240316" -description = "Typing stubs for python-dateutil" -optional = false -python-versions = ">=3.8" -files = [ - {file = "types-python-dateutil-2.9.0.20240316.tar.gz", hash = "sha256:5d2f2e240b86905e40944dd787db6da9263f0deabef1076ddaed797351ec0202"}, - {file = "types_python_dateutil-2.9.0.20240316-py3-none-any.whl", hash = "sha256:6b8cb66d960771ce5ff974e9dd45e38facb81718cc1e208b10b1baccbfdbee3b"}, -] - [[package]] name = "unidecode" version = "1.3.8" @@ -1339,130 +558,7 @@ platformdirs = ">=3.9.1,<5" docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] -[[package]] -name = "wsrpc-aiohttp" -version = "3.2.0" -description = "WSRPC is the RPC over WebSocket for aiohttp" -optional = false -python-versions = ">3.5.*, <4" -files = [ - {file = "wsrpc-aiohttp-3.2.0.tar.gz", hash = "sha256:f467abc51bcdc760fc5aeb7041abdeef46eeca3928dc43dd6e7fa7a533563818"}, - {file = "wsrpc_aiohttp-3.2.0-py3-none-any.whl", hash = "sha256:fa9b0bf5cb056898cb5c9f64cbc5eacb8a5dd18ab1b7f0cd4a2208b4a7fde282"}, -] - -[package.dependencies] -aiohttp = "<4" -yarl = "*" - -[package.extras] -develop = ["Sphinx", "async-timeout", "coverage (!=4.3)", "coveralls", "pytest", "pytest-aiohttp", "pytest-cov", "sphinxcontrib-plantuml", "tox (>=2.4)"] -testing = ["async-timeout", "coverage (!=4.3)", "coveralls", "pytest", "pytest-aiohttp", "pytest-cov"] -ujson = ["ujson"] - -[[package]] -name = "yarl" -version = "1.9.4" -description = "Yet another URL library" -optional = false -python-versions = ">=3.7" -files = [ - {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, - {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, - {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, - {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, - {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, - {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, - {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, - {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, - {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, - {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, - {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, - {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, - {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, - {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, - {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, - {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, - {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, - {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, -] - -[package.dependencies] -idna = ">=2.0" -multidict = ">=4.0" - [metadata] lock-version = "2.0" python-versions = ">=3.9.1,<3.10" -content-hash = "8a62cc31c960aff7e5df7bfdc5b65790e57bf0e7a87fedd16a68ababa49268c8" +content-hash = "1bb724694792fbc2b3c05e3355e6c25305d9f4034eb7b1b4b1791ee95427f8d2" diff --git a/pyproject.toml b/pyproject.toml index 29213281af..2740e9307e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,27 +7,17 @@ readme = "README.md" [tool.poetry.dependencies] python = ">=3.9.1,<3.10" -aiohttp_json_rpc = "*" # TVPaint server -aiohttp-middlewares = "^2.0.0" -wsrpc_aiohttp = "^3.1.1" # websocket server -Click = "^8" -clique = "1.6.*" -jsonschema = "^4" -pyblish-base = "^1.8.11" -pynput = "^1.7.2" # Timers manager - TODO remove -speedcopy = "^2.1" -six = "^1.15" -qtawesome = "0.7.3" -pre-commit = "^3.6.2" -codespell = "^2.2.6" [tool.poetry.dev-dependencies] +# test dependencies pytest = "^8.0" pytest-print = "^1.0" ayon-python-api = "^1.0" -arrow = "^1.3.0" +# linting dependencies ruff = "^0.3.3" +pre-commit = "^3.6.2" +codespell = "^2.2.6" [tool.ruff] From 945f395a89cdb4e64496f8719592d5a88e1154a7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 19 Mar 2024 14:24:01 +0100 Subject: [PATCH 068/150] rename 'get_template' to 'get_template_item' --- client/ayon_core/pipeline/anatomy/anatomy.py | 7 +++---- client/ayon_core/pipeline/anatomy/templates.py | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/pipeline/anatomy/anatomy.py b/client/ayon_core/pipeline/anatomy/anatomy.py index 77ba83234f..4c16f20a44 100644 --- a/client/ayon_core/pipeline/anatomy/anatomy.py +++ b/client/ayon_core/pipeline/anatomy/anatomy.py @@ -99,21 +99,20 @@ class BaseAnatomy(object): """Return `AnatomyTemplates` object of current Anatomy instance.""" return self._templates_obj - def get_template(self, category_name, template_name, subkey=None): + def get_template_item(self, *args, **kwargs): """Get template item from category. Args: category_name (str): Category name. template_name (str): Template name. subkey (Optional[str]): Subkey name. + default (Any): Default value. Returns: Any: Template item, subkey value as AnatomyStringTemplate or None. """ - return self._templates_obj.get_template( - category_name, template_name, subkey - ) + return self._templates_obj.get_template_item(*args, **kwargs) def format(self, *args, **kwargs): """Wrap `format` method of Anatomy's `templates_obj`.""" diff --git a/client/ayon_core/pipeline/anatomy/templates.py b/client/ayon_core/pipeline/anatomy/templates.py index 7d9a1255a3..46cad385f0 100644 --- a/client/ayon_core/pipeline/anatomy/templates.py +++ b/client/ayon_core/pipeline/anatomy/templates.py @@ -568,7 +568,7 @@ class AnatomyTemplates: """ return self.format(in_data, strict=False) - def get_template( + def get_template_item( self, category_name, template_name, subkey=None, default=_PLACEHOLDER ): """Get template item from category. From eaf580bf3b6d533c1f330dcc8cf56d95c20ef6ae Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 19 Mar 2024 14:24:24 +0100 Subject: [PATCH 069/150] 'root_value_for_template' can handle 'StringTemplate' object --- client/ayon_core/pipeline/anatomy/anatomy.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/anatomy/anatomy.py b/client/ayon_core/pipeline/anatomy/anatomy.py index 4c16f20a44..658f5fe3fc 100644 --- a/client/ayon_core/pipeline/anatomy/anatomy.py +++ b/client/ayon_core/pipeline/anatomy/anatomy.py @@ -7,7 +7,7 @@ import time import ayon_api -from ayon_core.lib import Logger, get_local_site_id +from ayon_core.lib import Logger, get_local_site_id, StringTemplate from ayon_core.addon import AddonsManager from .exceptions import RootCombinationError, ProjectNotSet @@ -204,6 +204,8 @@ class BaseAnatomy(object): def root_value_for_template(self, template): """Returns value of root key from template.""" + if isinstance(template, StringTemplate): + template = template.template root_templates = [] for group in re.findall(self.root_key_regex, template): root_templates.append("{" + group + "}") From e5fe964178c7fee39f8a62eda98fd5ad583a8213 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 19 Mar 2024 14:26:05 +0100 Subject: [PATCH 070/150] use new method name and fix issues with string conversions --- .../plugins/publish/collect_render_path.py | 6 ++++-- client/ayon_core/hosts/hiero/api/lib.py | 6 ++++-- .../plugins/publish/extract_workfile_xgen.py | 4 +++- .../hosts/maya/plugins/publish/extract_xgen.py | 2 +- .../hosts/photoshop/api/launch_logic.py | 6 +++--- .../tvpaint/plugins/load/load_workfile.py | 6 +++--- .../unreal/hooks/pre_workfile_preparation.py | 2 +- client/ayon_core/lib/applications.py | 6 +++--- .../publish/submit_celaction_deadline.py | 4 +++- .../plugins/publish/submit_fusion_deadline.py | 4 +++- .../plugins/publish/submit_nuke_deadline.py | 4 +++- .../publish/submit_publish_cache_job.py | 2 +- .../plugins/publish/submit_publish_job.py | 2 +- client/ayon_core/pipeline/context_tools.py | 2 +- client/ayon_core/pipeline/delivery.py | 17 ++++++++++------- client/ayon_core/pipeline/farm/tools.py | 2 +- client/ayon_core/pipeline/publish/lib.py | 8 ++++---- client/ayon_core/pipeline/usdlib.py | 4 ++-- .../pipeline/workfile/path_resolving.py | 4 +++- .../plugins/actions/open_file_explorer.py | 4 ++-- .../publish/collect_otio_subset_resources.py | 4 ++-- .../plugins/publish/collect_resources_path.py | 2 +- client/ayon_core/plugins/publish/integrate.py | 4 ++-- .../plugins/publish/integrate_hero_version.py | 12 ++++++++---- .../tools/push_to_project/models/integrate.py | 6 +++--- client/ayon_core/tools/texture_copy/app.py | 2 +- .../tools/workfiles/models/workfiles.py | 18 ++++++++++-------- 27 files changed, 83 insertions(+), 60 deletions(-) diff --git a/client/ayon_core/hosts/celaction/plugins/publish/collect_render_path.py b/client/ayon_core/hosts/celaction/plugins/publish/collect_render_path.py index a7eef0fce4..52bb183663 100644 --- a/client/ayon_core/hosts/celaction/plugins/publish/collect_render_path.py +++ b/client/ayon_core/hosts/celaction/plugins/publish/collect_render_path.py @@ -33,7 +33,7 @@ class CollectRenderPath(pyblish.api.InstancePlugin): m_anatomy_key = self.anatomy_template_key_metadata # get folder and path for rendering images from celaction - r_template_item = anatomy.get_template("publish", r_anatomy_key) + r_template_item = anatomy.get_template_item("publish", r_anatomy_key) render_dir = r_template_item["directory"].format_strict(anatomy_data) render_path = r_template_item["path"].format_strict(anatomy_data) self.log.debug("__ render_path: `{}`".format(render_path)) @@ -50,7 +50,9 @@ class CollectRenderPath(pyblish.api.InstancePlugin): instance.data["path"] = render_path # get anatomy for published renders folder path - m_template_item = anatomy.get_template("publish", m_anatomy_key) + m_template_item = anatomy.get_template_item( + "publish", m_anatomy_key, default=None + ) if m_template_item is not None: metadata_path = m_template_item["directory"].format_strict( anatomy_data diff --git a/client/ayon_core/hosts/hiero/api/lib.py b/client/ayon_core/hosts/hiero/api/lib.py index 666119593a..8e08e8cbf3 100644 --- a/client/ayon_core/hosts/hiero/api/lib.py +++ b/client/ayon_core/hosts/hiero/api/lib.py @@ -632,7 +632,9 @@ def sync_avalon_data_to_workfile(): project_name = get_current_project_name() anatomy = Anatomy(project_name) - work_template = anatomy.get_template("work", "default", "path") + work_template = anatomy.get_template_item( + "work", "default", "path" + ) work_root = anatomy.root_value_for_template(work_template) active_project_root = ( os.path.join(work_root, project_name) @@ -825,7 +827,7 @@ class PublishAction(QtWidgets.QAction): # root_node = hiero.core.nuke.RootNode() # # anatomy = Anatomy(get_current_project_name()) -# work_template = anatomy.get_template("work", "default", "path") +# work_template = anatomy.get_template_item("work", "default", "path") # root_path = anatomy.root_value_for_template(work_template) # # nuke_script.addNode(root_node) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_workfile_xgen.py b/client/ayon_core/hosts/maya/plugins/publish/extract_workfile_xgen.py index a0328725ca..d305b8dc6c 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_workfile_xgen.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_workfile_xgen.py @@ -129,7 +129,9 @@ class ExtractWorkfileXgen(publish.Extractor): template_data = copy.deepcopy(instance.data["anatomyData"]) anatomy = instance.context.data["anatomy"] - publish_template = anatomy.get_template("publish", "default", "file") + publish_template = anatomy.get_template_item( + "publish", "default", "file" + ) published_maya_path = publish_template.format(template_data) published_basename, _ = os.path.splitext(published_maya_path) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_xgen.py b/client/ayon_core/hosts/maya/plugins/publish/extract_xgen.py index eff4dcc881..73668da28d 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_xgen.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_xgen.py @@ -40,7 +40,7 @@ class ExtractXgen(publish.Extractor): template_data = copy.deepcopy(instance.data["anatomyData"]) template_data.update({"ext": "xgen"}) anatomy = instance.context.data["anatomy"] - file_template = anatomy.get_template("publish", "default", "file") + file_template = anatomy.get_template_item("publish", "default", "file") xgen_filename = file_template.format(template_data) xgen_path = os.path.join( diff --git a/client/ayon_core/hosts/photoshop/api/launch_logic.py b/client/ayon_core/hosts/photoshop/api/launch_logic.py index 9c6a1e8a01..d0823646d7 100644 --- a/client/ayon_core/hosts/photoshop/api/launch_logic.py +++ b/client/ayon_core/hosts/photoshop/api/launch_logic.py @@ -392,13 +392,13 @@ class PhotoshopRoute(WebSocketRoute): ) data["root"] = anatomy.roots - work_template = anatomy.get_template("work", template_key) + work_template = anatomy.get_template_item("work", template_key) # Define saving file extension extensions = host.get_workfile_extensions() - work_root = str(work_template["directory"].format_strict(data)) - file_template = str(work_template["file"]) + work_root = work_template["directory"].format_strict(data) + file_template = work_template["file"].template last_workfile_path = get_last_workfile( work_root, file_template, data, extensions, True ) diff --git a/client/ayon_core/hosts/tvpaint/plugins/load/load_workfile.py b/client/ayon_core/hosts/tvpaint/plugins/load/load_workfile.py index 0b6d67a840..203610288f 100644 --- a/client/ayon_core/hosts/tvpaint/plugins/load/load_workfile.py +++ b/client/ayon_core/hosts/tvpaint/plugins/load/load_workfile.py @@ -80,7 +80,7 @@ class LoadWorkfile(plugin.Loader): ) data["root"] = anatomy.roots - work_template = anatomy.get_template("work", template_key) + work_template = anatomy.get_template_item("work", template_key) # Define saving file extension extensions = host.get_workfile_extensions() @@ -93,9 +93,9 @@ class LoadWorkfile(plugin.Loader): data["ext"] = extension.lstrip(".") - work_root = str(work_template["directory"].format_strict(data)) + work_root = work_template["directory"].format_strict(data) version = get_last_workfile_with_version( - work_root, str(work_template["file"]), data, extensions + work_root, work_template["file"].template, data, extensions )[1] if version is None: diff --git a/client/ayon_core/hosts/unreal/hooks/pre_workfile_preparation.py b/client/ayon_core/hosts/unreal/hooks/pre_workfile_preparation.py index 040beccb5e..54ffba3a63 100644 --- a/client/ayon_core/hosts/unreal/hooks/pre_workfile_preparation.py +++ b/client/ayon_core/hosts/unreal/hooks/pre_workfile_preparation.py @@ -66,7 +66,7 @@ class UnrealPrelaunchHook(PreLaunchHook): self.host_name, ) # Fill templates - template_obj = anatomy.get_template( + template_obj = anatomy.get_template_item( "work", workfile_template_key, "file" ) diff --git a/client/ayon_core/lib/applications.py b/client/ayon_core/lib/applications.py index 123396f5f6..58f910ce31 100644 --- a/client/ayon_core/lib/applications.py +++ b/client/ayon_core/lib/applications.py @@ -1862,9 +1862,9 @@ def _prepare_last_workfile(data, workdir, addons_manager): project_settings=project_settings ) # Find last workfile - file_template = str( - anatomy.get_template("work", template_key, "file") - ) + file_template = anatomy.get_template_item( + "work", template_key, "file" + ).template workdir_data.update({ "version": 1, diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py index e11b74ae81..1fae23c9b2 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py @@ -75,7 +75,9 @@ class CelactionSubmitDeadline(pyblish.api.InstancePlugin): script_name = os.path.basename(script_path) anatomy = instance.context.data["anatomy"] - publish_template = anatomy.get_template("publish", "default", "path") + publish_template = anatomy.get_template_item( + "publish", "default", "path" + ) for item in instance.context: if "workfile" in item.data["productType"]: msg = "Workfile (scene) must be published along" diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py index f99a7c6ba3..cf124c0bcc 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py @@ -124,7 +124,9 @@ class FusionSubmitDeadline( script_path = context.data["currentFile"] anatomy = instance.context.data["anatomy"] - publish_template = anatomy.get_template("publish", "default", "path") + publish_template = anatomy.get_template_item( + "publish", "default", "path" + ) for item in context: if "workfile" in item.data["families"]: msg = "Workfile (scene) must be published along" diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py index 193cad1d24..ac01af901c 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -198,7 +198,9 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, AbstractSubmitDeadline""" anatomy = context.data["anatomy"] # WARNING Hardcoded template name 'default' > may not be used - publish_template = anatomy.get_template("publish", "default", "path") + publish_template = anatomy.get_template_item( + "publish", "default", "path" + ) for instance in context: if ( instance.data["productType"] != "workfile" diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py index dbc5a12fa8..50bd414587 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py @@ -450,7 +450,7 @@ class ProcessSubmittedCacheJobOnFarm(pyblish.api.InstancePlugin, "type": product_type, } - render_dir_template = anatomy.get_template( + render_dir_template = anatomy.get_template_item( "publish", template_name, "directory" ) return render_dir_template.format_strict(template_data) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py index ce1c75e2ee..84bac6d017 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py @@ -573,7 +573,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, "type": product_type, } - render_dir_template = anatomy.get_template( + render_dir_template = anatomy.get_template_item( "publish", template_name, "directory" ) return render_dir_template.format_strict(template_data) diff --git a/client/ayon_core/pipeline/context_tools.py b/client/ayon_core/pipeline/context_tools.py index bf21b43e0b..84a17be8f2 100644 --- a/client/ayon_core/pipeline/context_tools.py +++ b/client/ayon_core/pipeline/context_tools.py @@ -545,7 +545,7 @@ def get_workdir_from_session(session=None, template_key=None): ) anatomy = Anatomy(project_name) - template_obj = anatomy.get_template("work", template_key, "directory") + template_obj = anatomy.get_template_item("work", template_key, "directory") path = template_obj.format_strict(template_data) if path: path = os.path.normpath(path) diff --git a/client/ayon_core/pipeline/delivery.py b/client/ayon_core/pipeline/delivery.py index 666909ef8d..029775e1db 100644 --- a/client/ayon_core/pipeline/delivery.py +++ b/client/ayon_core/pipeline/delivery.py @@ -77,7 +77,9 @@ def check_destination_path( """ anatomy_data.update(datetime_data) - path_template = anatomy.get_template("delivery", template_name, "path") + path_template = anatomy.get_template_item( + "delivery", template_name, "path" + ) dest_path = path_template.format(anatomy_data) report_items = collections.defaultdict(list) @@ -150,7 +152,9 @@ def deliver_single_file( if format_dict: anatomy_data = copy.deepcopy(anatomy_data) anatomy_data["root"] = format_dict["root"] - template_obj = anatomy.get_template("delivery", template_name, "path") + template_obj = anatomy.get_template_item( + "delivery", template_name, "path" + ) delivery_path = template_obj.format_strict(anatomy_data) # Backwards compatibility when extension contained `.` @@ -220,8 +224,8 @@ def deliver_sequence( report_items["Source file was not found"].append(msg) return report_items, 0 - delivery_template = anatomy.get_template( - "delivery", template_name, "path" + delivery_template = anatomy.get_template_item( + "delivery", template_name, "path", default=None ) if delivery_template is None: msg = ( @@ -233,7 +237,7 @@ def deliver_sequence( # Check if 'frame' key is available in template which is required # for sequence delivery - if "{frame" not in delivery_template: + if "{frame" not in delivery_template.template: msg = ( "Delivery template \"{}\" in anatomy of project \"{}\"" "does not contain '{{frame}}' key to fill. Delivery of sequence" @@ -278,8 +282,7 @@ def deliver_sequence( anatomy_data["frame"] = frame_indicator if format_dict: anatomy_data["root"] = format_dict["root"] - template_obj = anatomy.get_template("delivery", template_name, "path") - delivery_path = template_obj.format_strict(anatomy_data) + delivery_path = delivery_template.format_strict(anatomy_data) delivery_path = os.path.normpath(delivery_path.replace("\\", "/")) delivery_folder = os.path.dirname(delivery_path) diff --git a/client/ayon_core/pipeline/farm/tools.py b/client/ayon_core/pipeline/farm/tools.py index 1ed7e4b3f5..0b647340f3 100644 --- a/client/ayon_core/pipeline/farm/tools.py +++ b/client/ayon_core/pipeline/farm/tools.py @@ -54,7 +54,7 @@ def from_published_scene(instance, replace_in_path=True): template_data["comment"] = None anatomy = instance.context.data['anatomy'] - template_obj = anatomy.get_template("publish", "default", "path") + template_obj = anatomy.get_template_item("publish", "default", "path") template_filled = template_obj.format_strict(template_data) file_path = os.path.normpath(template_filled) diff --git a/client/ayon_core/pipeline/publish/lib.py b/client/ayon_core/pipeline/publish/lib.py index 8d2ae85694..8d3644637b 100644 --- a/client/ayon_core/pipeline/publish/lib.py +++ b/client/ayon_core/pipeline/publish/lib.py @@ -743,8 +743,8 @@ def get_custom_staging_dir_info( template_name = profile["template_name"] or TRANSIENT_DIR_TEMPLATE - custom_staging_dir = anatomy.get_template( - "staging", template_name, "directory" + custom_staging_dir = anatomy.get_template_item( + "staging", template_name, "directory", default=None ) if custom_staging_dir is None: raise ValueError(( @@ -753,7 +753,7 @@ def get_custom_staging_dir_info( ).format(project_name, template_name)) is_persistent = profile["custom_staging_dir_persistent"] - return str(custom_staging_dir), is_persistent + return custom_staging_dir.template, is_persistent def get_published_workfile_instance(context): @@ -805,7 +805,7 @@ def replace_with_published_scene_path(instance, replace_in_path=True): template_data["comment"] = None anatomy = instance.context.data["anatomy"] - template = anatomy.get_template("publish", "default", "path") + template = anatomy.get_template_item("publish", "default", "path") template_filled = template.format_strict(template_data) file_path = os.path.normpath(template_filled) diff --git a/client/ayon_core/pipeline/usdlib.py b/client/ayon_core/pipeline/usdlib.py index 9fabd1dce5..1c7943441e 100644 --- a/client/ayon_core/pipeline/usdlib.py +++ b/client/ayon_core/pipeline/usdlib.py @@ -341,8 +341,8 @@ def get_usd_master_path(folder_entity, product_name, representation): "version": 0, # stub version zero }) - template_obj = anatomy.get_template( - "publish", "default","path" + template_obj = anatomy.get_template_item( + "publish", "default", "path" ) path = template_obj.format_strict(template_data) diff --git a/client/ayon_core/pipeline/workfile/path_resolving.py b/client/ayon_core/pipeline/workfile/path_resolving.py index 239b8c1096..47d6f4ddfa 100644 --- a/client/ayon_core/pipeline/workfile/path_resolving.py +++ b/client/ayon_core/pipeline/workfile/path_resolving.py @@ -135,7 +135,9 @@ def get_workdir_with_workdir_data( project_settings ) - template_obj = anatomy.get_template("work", template_key, "directory") + template_obj = anatomy.get_template_item( + "work", template_key, "directory" + ) # Output is TemplateResult object which contain useful data output = template_obj.format_strict(workdir_data) if output: diff --git a/client/ayon_core/plugins/actions/open_file_explorer.py b/client/ayon_core/plugins/actions/open_file_explorer.py index 9369a25eb0..69375a7859 100644 --- a/client/ayon_core/plugins/actions/open_file_explorer.py +++ b/client/ayon_core/plugins/actions/open_file_explorer.py @@ -70,7 +70,7 @@ class OpenTaskPath(LauncherAction): data = get_template_data(project_entity, folder_entity, task_entity) anatomy = Anatomy(project_name) - workdir = anatomy.get_template( + workdir = anatomy.get_template_item( "work", "default", "folder" ).format(data) @@ -87,7 +87,7 @@ class OpenTaskPath(LauncherAction): return valid_workdir data.pop("task", None) - workdir = anatomy.get_template( + workdir = anatomy.get_template_item( "work", "default", "folder" ).format(data) valid_workdir = self._find_first_filled_path(workdir) diff --git a/client/ayon_core/plugins/publish/collect_otio_subset_resources.py b/client/ayon_core/plugins/publish/collect_otio_subset_resources.py index ad3a4d2c23..37a5e87a7a 100644 --- a/client/ayon_core/plugins/publish/collect_otio_subset_resources.py +++ b/client/ayon_core/plugins/publish/collect_otio_subset_resources.py @@ -43,9 +43,9 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): template_name = self.get_template_name(instance) anatomy = instance.context.data["anatomy"] - publish_path_template = anatomy.get_template( + publish_path_template = anatomy.get_template_item( "publish", template_name, "path" - ) + ).template template = os.path.normpath(publish_path_template) self.log.debug( ">> template: {}".format(template)) diff --git a/client/ayon_core/plugins/publish/collect_resources_path.py b/client/ayon_core/plugins/publish/collect_resources_path.py index 05015909d5..959523918e 100644 --- a/client/ayon_core/plugins/publish/collect_resources_path.py +++ b/client/ayon_core/plugins/publish/collect_resources_path.py @@ -79,7 +79,7 @@ class CollectResourcesPath(pyblish.api.InstancePlugin): "representation": "TEMP" }) - publish_templates = anatomy.get_template( + publish_templates = anatomy.get_template_item( "publish", "default", "directory" ) publish_folder = os.path.normpath( diff --git a/client/ayon_core/plugins/publish/integrate.py b/client/ayon_core/plugins/publish/integrate.py index 16c8ebaaf5..ce34f2e88b 100644 --- a/client/ayon_core/plugins/publish/integrate.py +++ b/client/ayon_core/plugins/publish/integrate.py @@ -665,9 +665,9 @@ class IntegrateAsset(pyblish.api.InstancePlugin): self.log.debug("Anatomy template name: {}".format(template_name)) anatomy = instance.context.data["anatomy"] - publish_template = anatomy.get_template("publish", template_name) + publish_template = anatomy.get_template_item("publish", template_name) path_template_obj = publish_template["path"] - template = os.path.normpath(path_template_obj) + template = path_template_obj.template.replace("\\", "/") is_udim = bool(repre.get("udim")) diff --git a/client/ayon_core/plugins/publish/integrate_hero_version.py b/client/ayon_core/plugins/publish/integrate_hero_version.py index e313c94393..c352e67f89 100644 --- a/client/ayon_core/plugins/publish/integrate_hero_version.py +++ b/client/ayon_core/plugins/publish/integrate_hero_version.py @@ -103,7 +103,9 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): project_name = anatomy.project_name template_key = self._get_template_key(project_name, instance) - hero_template = anatomy.get_template("hero", template_key, "path") + hero_template = anatomy.get_template_item( + "hero", template_key, "path", default=None + ) if hero_template is None: self.log.warning(( @@ -320,7 +322,7 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): try: src_to_dst_file_paths = [] repre_integrate_data = [] - path_template_obj = anatomy.get_template( + path_template_obj = anatomy.get_template_item( "hero", template_key, "path" ) for repre_info in published_repres.values(): @@ -335,7 +337,9 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): anatomy_data.pop("version", None) # Get filled path to repre context - template_filled = path_template_obj.format_strict(anatomy_data) + template_filled = path_template_obj.format_strict( + anatomy_data + ) repre_context = template_filled.used_values for key in self.db_representation_context_keys: value = anatomy_data.get(key) @@ -538,7 +542,7 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): "originalBasename": instance.data.get("originalBasename") }) - template_obj = anatomy.get_template( + template_obj = anatomy.get_template_item( "hero", template_key, "directory" ) publish_folder = os.path.normpath( diff --git a/client/ayon_core/tools/push_to_project/models/integrate.py b/client/ayon_core/tools/push_to_project/models/integrate.py index 0987dc0c56..6e43050c05 100644 --- a/client/ayon_core/tools/push_to_project/models/integrate.py +++ b/client/ayon_core/tools/push_to_project/models/integrate.py @@ -973,8 +973,8 @@ class ProjectPushItemProcess: "version": version_entity["version"] }) - publish_template = anatomy.get_template("publish", template_name) - path_template = publish_template["path"].replace("\\", "/") + publish_template = anatomy.get_template_item("publish", template_name) + path_template = publish_template["path"].template.replace("\\", "/") file_template = publish_template["file"] self._log_info("Preparing files to transfer") processed_repre_items = self._prepare_file_transactions( @@ -1011,7 +1011,7 @@ class ProjectPushItemProcess: if repre_output_name is not None: repre_format_data["output"] = repre_output_name - template_obj = anatomy.get_template( + template_obj = anatomy.get_template_item( "publish", template_name, "directory" ) folder_path = template_obj.format_strict(formatting_data) diff --git a/client/ayon_core/tools/texture_copy/app.py b/client/ayon_core/tools/texture_copy/app.py index 9a94ba44f7..c288187aac 100644 --- a/client/ayon_core/tools/texture_copy/app.py +++ b/client/ayon_core/tools/texture_copy/app.py @@ -40,7 +40,7 @@ class TextureCopy: }, }) anatomy = Anatomy(project_name, project_entity=project_entity) - template_obj = anatomy.get_template( + template_obj = anatomy.get_template_item( "publish", "texture", "path" ) return template_obj.format_strict(template_data) diff --git a/client/ayon_core/tools/workfiles/models/workfiles.py b/client/ayon_core/tools/workfiles/models/workfiles.py index ef502e3d6f..479c8ea849 100644 --- a/client/ayon_core/tools/workfiles/models/workfiles.py +++ b/client/ayon_core/tools/workfiles/models/workfiles.py @@ -253,7 +253,7 @@ class WorkareaModel: return list(comment_hints), current_comment def _get_workdir(self, anatomy, template_key, fill_data): - directory_template = anatomy.get_template( + directory_template = anatomy.get_template_item( "work", template_key, "directory" ) return directory_template.format_strict(fill_data).normalized() @@ -300,9 +300,12 @@ class WorkareaModel: workdir = self._get_workdir(anatomy, template_key, fill_data) - file_template = str( - anatomy.get_template("work", template_key, "file") - ) + file_template = anatomy.get_template_item( + "work", template_key, "file" + ).template + + template_has_version = "{version" in file_template + template_has_comment = "{comment" in file_template comment_hints, comment = self._get_comments_from_root( file_template, @@ -313,9 +316,6 @@ class WorkareaModel: ) last_version = self._get_last_workfile_version( workdir, file_template, fill_data, extensions) - str_file_template = str(file_template) - template_has_version = "{version" in str_file_template - template_has_comment = "{comment" in str_file_template return { "template_key": template_key, @@ -344,7 +344,9 @@ class WorkareaModel: workdir = self._get_workdir(anatomy, template_key, fill_data) - file_template = anatomy.get_template("work", template_key, "file") + file_template = anatomy.get_template_item( + "work", template_key, "file" + ).template if use_last_version: version = self._get_last_workfile_version( From 2f0d87d084a9dc70eecb11e0aa4100c33d2dc30d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 19 Mar 2024 14:39:31 +0100 Subject: [PATCH 071/150] handle 'StingTemplate' in '_root_keys_from_templates' --- client/ayon_core/pipeline/anatomy/anatomy.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/ayon_core/pipeline/anatomy/anatomy.py b/client/ayon_core/pipeline/anatomy/anatomy.py index 658f5fe3fc..0d250116bd 100644 --- a/client/ayon_core/pipeline/anatomy/anatomy.py +++ b/client/ayon_core/pipeline/anatomy/anatomy.py @@ -192,6 +192,9 @@ class BaseAnatomy(object): keys_queue.append(data) while keys_queue: queue_data = keys_queue.popleft() + if isinstance(queue_data, StringTemplate): + queue_data = queue_data.template + if isinstance(queue_data, dict): for value in queue_data.values(): keys_queue.append(value) From 38c8cf2e3bdf8a5210eb64ceed369d00aa0f2cb8 Mon Sep 17 00:00:00 2001 From: moonyuet Date: Tue, 19 Mar 2024 14:48:46 +0100 Subject: [PATCH 072/150] add zbrush being part of application.json --- .../applications/server/applications.json | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/server_addon/applications/server/applications.json b/server_addon/applications/server/applications.json index b72d117225..85bf6f1dda 100644 --- a/server_addon/applications/server/applications.json +++ b/server_addon/applications/server/applications.json @@ -1225,6 +1225,32 @@ } ] }, + "zbrush": { + "enabled": true, + "label": "Zbrush", + "icon": "{}/app_icons/zbrush.png", + "host_name": "zbrush", + "environment": "{\n \"ZBRUSH_PLUGIN_PATH\": [\n \"{ZBRUSH_PLUGIN_PATH}\",\n \"{OPENPYPE_STUDIO_PLUGINS}/zbrush/api/zscripts\"\n ]\n}", + "variants": [ + { + "name": "2024", + "use_python_2": false, + "executables": { + "windows": [ + "C:\\Program Files\\Maxon ZBrush 2024\\ZBrush.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{}" + } + ] + }, "additional_apps": [] } } From 0477e1775c043c199a244e2c15e00ac06e47b473 Mon Sep 17 00:00:00 2001 From: moonyuet Date: Tue, 19 Mar 2024 14:55:10 +0100 Subject: [PATCH 073/150] add icon --- .../ayon_core/resources/app_icons/zbrush.png | Bin 0 -> 287255 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 client/ayon_core/resources/app_icons/zbrush.png diff --git a/client/ayon_core/resources/app_icons/zbrush.png b/client/ayon_core/resources/app_icons/zbrush.png new file mode 100644 index 0000000000000000000000000000000000000000..4b0662580c907e797d68ed61041c23f8134fea53 GIT binary patch literal 287255 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 literal 0 HcmV?d00001 From 42219e11184df511c48d2f0d812498c33e9b03a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 19 Mar 2024 15:09:34 +0100 Subject: [PATCH 074/150] :memo: update readme --- README.md | 24 ++++++++++++++++++------ pyproject.toml | 5 +++++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e2aa98bb16..11a660e643 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -AYON Core addon -======== +AYON Core Addon +=============== -AYON core provides the base building blocks for all other AYON addons and integrations and is responsible for discovery and initialization of other addons. +AYON core provides the base building blocks for all other AYON addons and integrations and is responsible for discovery and initialization of other addons. - Some of its key functions include: - It is used as the main command line handler in [ayon-launcher](https://github.com/ynput/ayon-launcher) application. @@ -13,8 +13,20 @@ AYON core provides the base building blocks for all other AYON addons and integr - Defines pipeline API used by other integrations - Provides all graphical tools for artists - Defines AYON QT styling -- A bunch more things +- A bunch more things -Together with [ayon-launcher](https://github.com/ynput/ayon-launcher) , they form the base of AYON pipeline and is one of few compulsory addons for AYON pipeline to be useful in a meaningful way. +Together with [ayon-launcher](https://github.com/ynput/ayon-launcher) , they form the base of AYON pipeline and is one of few compulsory addons for AYON pipeline to be useful in a meaningful way. -AYON-core is a successor to OpenPype repository (minus all the addons) and still in the process of cleaning up of all references. Please bear with us during this transitional phase. +AYON-core is a successor to [OpenPype repository](https://github.com/ynput/OpenPype) (minus all the addons) and still in the process of cleaning up of all references. Please bear with us during this transitional phase. + +Development and testing notes +----------------------------- +There is `pyproject.toml` file in the root of the repository. This file is used to define the development environment and is used by `poetry` to create a virtual environment. +This virtual environment is used to run tests and to develop the code, to help with +linting and formatting. Dependencies defined here are not used in actual addon +deployment - for that you need to edit `./client/pyproject.toml` file. That file +will be then processed [ayon-dependencies-tool](https://github.com/ynput/ayon-dependencies-tool) +to create dependency package. + +Right now, this file needs to by synced with dependencies manually, but in the future +we plan to automate process of development environment creation. diff --git a/pyproject.toml b/pyproject.toml index 2740e9307e..ee124ddc2d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,8 @@ +# WARNING: This file is used only for development done on this addon. +# Be aware that dependencies used here might not match the ones used by +# the specific addon bundle set up on the AYON server. This file should +# be used only for local development and CI/CD purposes. + [tool.poetry] name = "ayon-core" version = "0.3.0" From 72e48537123629d53faddfd945a4ca9282b3b8ce Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 19 Mar 2024 15:12:09 +0100 Subject: [PATCH 075/150] add missing variant 'task_name' in CreateContext.create --- client/ayon_core/pipeline/create/context.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/ayon_core/pipeline/create/context.py b/client/ayon_core/pipeline/create/context.py index 308762fae6..54f2afb766 100644 --- a/client/ayon_core/pipeline/create/context.py +++ b/client/ayon_core/pipeline/create/context.py @@ -1986,6 +1986,8 @@ class CreateContext: raise CreatorError( "Folder '{}' was not found".format(folder_path) ) + + task_name = None if task_entity is None: task_name = self.get_current_task_name() task_entity = ayon_api.get_task_by_name( From c020085dc1e4de6bfc7b13a12832151176448e10 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 19 Mar 2024 15:20:12 +0100 Subject: [PATCH 076/150] update docstring of create method --- client/ayon_core/pipeline/create/context.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/pipeline/create/context.py b/client/ayon_core/pipeline/create/context.py index 54f2afb766..8c6a7f1bb6 100644 --- a/client/ayon_core/pipeline/create/context.py +++ b/client/ayon_core/pipeline/create/context.py @@ -1953,11 +1953,11 @@ class CreateContext: """Trigger create of plugins with standartized arguments. Arguments 'folder_entity' and 'task_name' use current context as - default values. If only 'task_name' is provided it will be overriden - by task name from current context. If 'task_name' is not provided - when 'folder_entity' is, it is considered that task name is not - specified, which can lead to error if product name template requires - task name. + default values. If only 'task_entity' is provided it will be + overridden by task name from current context. If 'task_name' is not + provided when 'folder_entity' is, it is considered that task name is + not specified, which can lead to error if product name template + requires task name. Args: creator_identifier (str): Identifier of creator plugin. From 17176fd41330b51681a4cfbf6f74b4dd88c15fe8 Mon Sep 17 00:00:00 2001 From: moonyuet Date: Tue, 19 Mar 2024 15:43:50 +0100 Subject: [PATCH 077/150] add missing code for adding application for zbrush --- server_addon/applications/server/settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server_addon/applications/server/settings.py b/server_addon/applications/server/settings.py index a49175d488..5743e9f471 100644 --- a/server_addon/applications/server/settings.py +++ b/server_addon/applications/server/settings.py @@ -188,6 +188,8 @@ class ApplicationsSettings(BaseSettingsModel): default_factory=AppGroupWithPython, title="Wrap") openrv: AppGroup = SettingsField( default_factory=AppGroupWithPython, title="OpenRV") + zbrush: AppGroup = SettingsField( + default_factory=AppGroupWithPython, title="Zbrush") additional_apps: list[AdditionalAppGroup] = SettingsField( default_factory=list, title="Additional Applications") From 58d63af8848e4a45868d17168cbb2c7fde68c00b Mon Sep 17 00:00:00 2001 From: moonyuet Date: Tue, 19 Mar 2024 15:44:16 +0100 Subject: [PATCH 078/150] upversioning --- server_addon/applications/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/applications/server/version.py b/server_addon/applications/server/version.py index f1380eede2..9cb17e7976 100644 --- a/server_addon/applications/server/version.py +++ b/server_addon/applications/server/version.py @@ -1 +1 @@ -__version__ = "0.1.7" +__version__ = "0.1.8" From ceb2f71c3dcd3df764395f55f668445aeaa348da Mon Sep 17 00:00:00 2001 From: moonyuet Date: Tue, 19 Mar 2024 17:17:23 +0100 Subject: [PATCH 079/150] make sure when validator is being optional, it can allow users to choose whether it should be included in maya/max for validation --- .../max/plugins/publish/validate_mesh_has_uv.py | 2 ++ .../plugins/publish/validate_animated_reference.py | 9 +++++++-- .../plugins/publish/validate_animation_content.py | 9 +++++++-- .../validate_animation_out_set_related_node_ids.py | 10 +++++++--- .../publish/validate_arnold_scene_source_cbid.py | 3 +++ .../plugins/publish/validate_ass_relative_paths.py | 9 +++++++-- .../maya/plugins/publish/validate_assembly_name.py | 10 +++++++--- .../plugins/publish/validate_assembly_namespaces.py | 10 +++++++--- .../plugins/publish/validate_assembly_transforms.py | 13 +++++++++++-- .../plugins/publish/validate_camera_attributes.py | 12 +++++++++--- .../plugins/publish/validate_camera_contents.py | 11 ++++++++--- .../validate_current_renderlayer_renderable.py | 4 +++- .../maya/plugins/publish/validate_glsl_material.py | 7 +++++-- .../maya/plugins/publish/validate_glsl_plugin.py | 9 +++++++-- .../plugins/publish/validate_instancer_content.py | 12 +++++++++--- .../publish/validate_instancer_frame_ranges.py | 13 +++++++++---- .../maya/plugins/publish/validate_loaded_plugin.py | 4 +++- .../plugins/publish/validate_look_shading_group.py | 5 ++++- .../maya/plugins/publish/validate_maya_units.py | 10 +++++++--- .../plugins/publish/validate_mesh_lamina_faces.py | 8 ++++++-- .../maya/plugins/publish/validate_mesh_ngons.py | 11 +++++++++-- .../publish/validate_mesh_no_negative_scale.py | 10 +++++++--- .../plugins/publish/validate_mesh_non_manifold.py | 10 +++++++--- .../publish/validate_mesh_shader_connections.py | 10 +++++++--- .../publish/validate_mesh_vertices_have_edges.py | 13 ++++++++++--- .../maya/plugins/publish/validate_model_content.py | 10 +++++++--- .../plugins/publish/validate_no_default_camera.py | 9 +++++++-- .../maya/plugins/publish/validate_no_namespace.py | 9 +++++++-- .../plugins/publish/validate_no_null_transforms.py | 9 +++++++-- .../maya/plugins/publish/validate_no_vraymesh.py | 13 +++++++++---- .../plugins/publish/validate_node_no_ghosting.py | 13 +++++++++---- .../publish/validate_plugin_path_attributes.py | 9 +++++++-- .../plugins/publish/validate_render_image_rule.py | 13 ++++++++++--- .../publish/validate_render_no_default_cameras.py | 7 ++++++- .../publish/validate_render_single_camera.py | 9 +++++++-- .../plugins/publish/validate_renderlayer_aovs.py | 13 ++++++++++--- .../maya/plugins/publish/validate_rig_contents.py | 10 ++++++++-- .../plugins/publish/validate_rig_controllers.py | 10 ++++++++-- .../validate_rig_controllers_arnold_attributes.py | 10 ++++++++-- .../plugins/publish/validate_rig_joints_hidden.py | 9 +++++++-- .../publish/validate_rig_out_set_node_ids.py | 11 ++++++++--- .../plugins/publish/validate_shape_render_stats.py | 8 ++++++-- .../maya/plugins/publish/validate_shape_zero.py | 9 +++++++-- .../publish/validate_skeletalmesh_hierarchy.py | 7 ++++++- .../validate_skeleton_top_group_hierarchy.py | 4 ++++ .../publish/validate_skinCluster_deformer_set.py | 8 ++++++-- .../maya/plugins/publish/validate_step_size.py | 11 +++++++---- .../maya/plugins/publish/validate_transform_zero.py | 10 +++++++--- .../maya/plugins/publish/validate_unique_names.py | 13 +++++++++---- .../publish/validate_unreal_mesh_triangulated.py | 10 ++++++++-- .../maya/plugins/publish/validate_visible_only.py | 10 +++++++--- .../publish/validate_vray_distributed_rendering.py | 13 ++++++++++--- .../publish/validate_vray_referenced_aovs.py | 12 +++++++++--- .../publish/validate_vray_translator_settings.py | 9 +++++++-- .../maya/plugins/publish/validate_vrayproxy.py | 9 ++++++--- .../plugins/publish/validate_vrayproxy_members.py | 10 +++++++--- .../publish/validate_yeti_renderscript_callbacks.py | 13 +++++++++---- .../publish/validate_yeti_rig_cache_state.py | 9 +++++++-- .../publish/validate_yeti_rig_input_in_instance.py | 10 +++++++--- .../plugins/publish/validate_yeti_rig_settings.py | 12 +++++++++--- 60 files changed, 431 insertions(+), 144 deletions(-) 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 index 109b7fe0b5..ccd91da2be 100644 --- 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 @@ -39,6 +39,8 @@ class ValidateMeshHasUVs(pyblish.api.InstancePlugin, return invalid def process(self, instance): + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: bullet_point_invalid_statement = "\n".join( diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_animated_reference.py b/client/ayon_core/hosts/maya/plugins/publish/validate_animated_reference.py index b7f115b38f..2ba2bff6fc 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_animated_reference.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_animated_reference.py @@ -2,12 +2,14 @@ import pyblish.api import ayon_core.hosts.maya.api.action from ayon_core.pipeline.publish import ( PublishValidationError, - ValidateContentsOrder + ValidateContentsOrder, + OptionalPyblishPluginMixin ) from maya import cmds -class ValidateAnimatedReferenceRig(pyblish.api.InstancePlugin): +class ValidateAnimatedReferenceRig(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validate all nodes in skeletonAnim_SET are referenced""" order = ValidateContentsOrder @@ -16,8 +18,11 @@ class ValidateAnimatedReferenceRig(pyblish.api.InstancePlugin): label = "Animated Reference Rig" accepted_controllers = ["transform", "locator"] actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction] + optional = False def process(self, instance): + if not self.is_active(instance.data): + return animated_sets = instance.data.get("animated_skeleton", []) if not animated_sets: self.log.debug( diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_animation_content.py b/client/ayon_core/hosts/maya/plugins/publish/validate_animation_content.py index 8cf5c4278e..ea989bbcf3 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_animation_content.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_animation_content.py @@ -2,11 +2,13 @@ import pyblish.api import ayon_core.hosts.maya.api.action from ayon_core.pipeline.publish import ( PublishValidationError, - ValidateContentsOrder + ValidateContentsOrder, + OptionalPyblishPluginMixin ) -class ValidateAnimationContent(pyblish.api.InstancePlugin): +class ValidateAnimationContent(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Adheres to the content of 'animation' product type - Must have collected `out_hierarchy` data. @@ -19,6 +21,7 @@ class ValidateAnimationContent(pyblish.api.InstancePlugin): families = ["animation"] label = "Animation Content" actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction] + optional = False @classmethod def get_invalid(cls, instance): @@ -48,6 +51,8 @@ class ValidateAnimationContent(pyblish.api.InstancePlugin): return invalid def process(self, instance): + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: raise PublishValidationError( diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_animation_out_set_related_node_ids.py b/client/ayon_core/hosts/maya/plugins/publish/validate_animation_out_set_related_node_ids.py index 0adb0a201c..b96c5f07e2 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_animation_out_set_related_node_ids.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_animation_out_set_related_node_ids.py @@ -6,11 +6,13 @@ from ayon_core.hosts.maya.api import lib from ayon_core.pipeline.publish import ( RepairAction, ValidateContentsOrder, - PublishValidationError + PublishValidationError, + OptionalPyblishPluginMixin ) -class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin): +class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validate if deformed shapes have related IDs to the original shapes When a deformer is applied in the scene on a referenced mesh that already @@ -28,10 +30,12 @@ class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin): ayon_core.hosts.maya.api.action.SelectInvalidAction, RepairAction ] + optional = False def process(self, instance): """Process all meshes""" - + if not self.is_active(instance.data): + return # Ensure all nodes have a cbId and a related ID to the original shapes # if a deformer has been created on the shape invalid = self.get_invalid(instance) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_arnold_scene_source_cbid.py b/client/ayon_core/hosts/maya/plugins/publish/validate_arnold_scene_source_cbid.py index f50fa1ed41..8f8b1eb1c2 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_arnold_scene_source_cbid.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_arnold_scene_source_cbid.py @@ -16,6 +16,7 @@ class ValidateArnoldSceneSourceCbid(pyblish.api.InstancePlugin): families = ["ass"] label = "Validate Arnold Scene Source CBID" actions = [RepairAction] + optional = False @staticmethod def _get_nodes_by_name(nodes): @@ -55,6 +56,8 @@ class ValidateArnoldSceneSourceCbid(pyblish.api.InstancePlugin): return invalid_couples def process(self, instance): + if not self.is_active(instance.data): + return # Proxy validation. if not instance.data.get("proxy", []): return diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_ass_relative_paths.py b/client/ayon_core/hosts/maya/plugins/publish/validate_ass_relative_paths.py index 669708d3a6..6e65eee592 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_ass_relative_paths.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_ass_relative_paths.py @@ -8,11 +8,13 @@ import pyblish.api from ayon_core.pipeline.publish import ( RepairAction, ValidateContentsOrder, - PublishValidationError + PublishValidationError, + OptionalPyblishPluginMixin ) -class ValidateAssRelativePaths(pyblish.api.InstancePlugin): +class ValidateAssRelativePaths(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Ensure exporting ass file has set relative texture paths""" order = ValidateContentsOrder @@ -20,8 +22,11 @@ class ValidateAssRelativePaths(pyblish.api.InstancePlugin): families = ['ass'] label = "ASS has relative texture paths" actions = [RepairAction] + optional = False def process(self, instance): + if not self.is_active(instance.data): + return # we cannot ask this until user open render settings as # `defaultArnoldRenderOptions` doesn't exist errors = [] diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_assembly_name.py b/client/ayon_core/hosts/maya/plugins/publish/validate_assembly_name.py index 03fa0fd779..c829f4bf74 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_assembly_name.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_assembly_name.py @@ -2,11 +2,13 @@ import pyblish.api import maya.cmds as cmds import ayon_core.hosts.maya.api.action from ayon_core.pipeline.publish import ( - PublishValidationError + PublishValidationError, + OptionalPyblishPluginMixin ) -class ValidateAssemblyName(pyblish.api.InstancePlugin): +class ValidateAssemblyName(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """ Ensure Assembly name ends with `GRP` Check if assembly name ends with `_GRP` string. @@ -17,6 +19,7 @@ class ValidateAssemblyName(pyblish.api.InstancePlugin): families = ["assembly"] actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction] active = False + optional = True @classmethod def get_invalid(cls, instance): @@ -47,7 +50,8 @@ class ValidateAssemblyName(pyblish.api.InstancePlugin): return invalid def process(self, instance): - + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: raise PublishValidationError("Found {} invalid named assembly " diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_assembly_namespaces.py b/client/ayon_core/hosts/maya/plugins/publish/validate_assembly_namespaces.py index 2d3d8e71ac..814a8295c4 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_assembly_namespaces.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_assembly_namespaces.py @@ -1,10 +1,12 @@ import pyblish.api import ayon_core.hosts.maya.api.action from ayon_core.pipeline.publish import ( - PublishValidationError + PublishValidationError, + OptionalPyblishPluginMixin ) -class ValidateAssemblyNamespaces(pyblish.api.InstancePlugin): +class ValidateAssemblyNamespaces(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Ensure namespaces are not nested In the outliner an item in a normal namespace looks as following: @@ -20,9 +22,11 @@ class ValidateAssemblyNamespaces(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder families = ["assembly"] actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction] + optional = False def process(self, instance): - + if not self.is_active(instance.data): + return self.log.debug("Checking namespace for %s" % instance.name) if self.get_invalid(instance): raise PublishValidationError("Nested namespaces found") diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_assembly_transforms.py b/client/ayon_core/hosts/maya/plugins/publish/validate_assembly_transforms.py index 5069feb4b6..3bcae5de49 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_assembly_transforms.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_assembly_transforms.py @@ -2,10 +2,15 @@ import pyblish.api from maya import cmds import ayon_core.hosts.maya.api.action -from ayon_core.pipeline.publish import PublishValidationError, RepairAction +from ayon_core.pipeline.publish import ( + PublishValidationError, + RepairAction, + OptionalPyblishPluginMixin +) -class ValidateAssemblyModelTransforms(pyblish.api.InstancePlugin): +class ValidateAssemblyModelTransforms(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Verify only root nodes of the loaded asset have transformations. Note: This check is temporary and is subject to change. @@ -34,7 +39,11 @@ class ValidateAssemblyModelTransforms(pyblish.api.InstancePlugin): " This can alter the look of your scene. " "Are you sure you want to continue?") + optional = False + def process(self, instance): + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: raise PublishValidationError( diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_camera_attributes.py b/client/ayon_core/hosts/maya/plugins/publish/validate_camera_attributes.py index 5e940a48a9..5fd8772a96 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_camera_attributes.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_camera_attributes.py @@ -3,10 +3,14 @@ from maya import cmds import ayon_core.hosts.maya.api.action from ayon_core.pipeline.publish import ( - PublishValidationError, ValidateContentsOrder) + PublishValidationError, + ValidateContentsOrder, + OptionalPyblishPluginMixin +) -class ValidateCameraAttributes(pyblish.api.InstancePlugin): +class ValidateCameraAttributes(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validates Camera has no invalid attribute keys or values. The Alembic file format does not a specific subset of attributes as such @@ -20,6 +24,7 @@ class ValidateCameraAttributes(pyblish.api.InstancePlugin): hosts = ['maya'] label = 'Camera Attributes' actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction] + optional = True DEFAULTS = [ ("filmFitOffset", 0.0), @@ -62,7 +67,8 @@ class ValidateCameraAttributes(pyblish.api.InstancePlugin): def process(self, instance): """Process all the nodes in the instance""" - + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_camera_contents.py b/client/ayon_core/hosts/maya/plugins/publish/validate_camera_contents.py index 7d4c4341fd..0f14a057f9 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_camera_contents.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_camera_contents.py @@ -3,10 +3,13 @@ from maya import cmds import ayon_core.hosts.maya.api.action from ayon_core.pipeline.publish import ( - PublishValidationError, ValidateContentsOrder) + PublishValidationError, + ValidateContentsOrder, + OptionalPyblishPluginMixin) -class ValidateCameraContents(pyblish.api.InstancePlugin): +class ValidateCameraContents(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validates Camera instance contents. A Camera instance may only hold a SINGLE camera's transform, nothing else. @@ -22,6 +25,7 @@ class ValidateCameraContents(pyblish.api.InstancePlugin): label = 'Camera Contents' actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction] validate_shapes = True + optional = False @classmethod def get_invalid(cls, instance): @@ -71,7 +75,8 @@ class ValidateCameraContents(pyblish.api.InstancePlugin): def process(self, instance): """Process all the nodes in the instance""" - + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: raise PublishValidationError("Invalid camera contents: " diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py b/client/ayon_core/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py index 55c4973842..39a9707984 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py @@ -20,9 +20,11 @@ class ValidateCurrentRenderLayerIsRenderable(pyblish.api.ContextPlugin): order = pyblish.api.ValidatorOrder hosts = ["maya"] families = ["renderlayer"] + optional = False def process(self, context): - + if not self.is_active(context.data): + return # Workaround bug pyblish-base#250 if not context_plugin_should_run(self, context): return diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_glsl_material.py b/client/ayon_core/hosts/maya/plugins/publish/validate_glsl_material.py index e610a8118c..3735dbb74c 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_glsl_material.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_glsl_material.py @@ -6,10 +6,11 @@ from ayon_core.pipeline.publish import ( RepairAction, ValidateContentsOrder ) -from ayon_core.pipeline import PublishValidationError +from ayon_core.pipeline import PublishValidationError, OptionalPyblishPluginMixin -class ValidateGLSLMaterial(pyblish.api.InstancePlugin): +class ValidateGLSLMaterial(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """ Validate if the asset uses GLSL Shader """ @@ -23,6 +24,8 @@ class ValidateGLSLMaterial(pyblish.api.InstancePlugin): active = True def process(self, instance): + if not self.is_active(instance.data): + return shading_grp = self.get_material_from_shapes(instance) if not shading_grp: raise PublishValidationError("No shading group found") diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_glsl_plugin.py b/client/ayon_core/hosts/maya/plugins/publish/validate_glsl_plugin.py index e155315e4f..d783da8b5c 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_glsl_plugin.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_glsl_plugin.py @@ -5,11 +5,13 @@ import pyblish.api from ayon_core.pipeline.publish import ( RepairAction, ValidateContentsOrder, - PublishValidationError + PublishValidationError, + OptionalPyblishPluginMixin ) -class ValidateGLSLPlugin(pyblish.api.InstancePlugin): +class ValidateGLSLPlugin(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """ Validate if the asset uses GLSL Shader """ @@ -19,8 +21,11 @@ class ValidateGLSLPlugin(pyblish.api.InstancePlugin): hosts = ['maya'] label = 'maya2glTF plugin' actions = [RepairAction] + optional = False def process(self, instance): + if not self.is_active(instance.data): + return if not cmds.pluginInfo("maya2glTF", query=True, loaded=True): raise PublishValidationError("maya2glTF is not loaded") diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_instancer_content.py b/client/ayon_core/hosts/maya/plugins/publish/validate_instancer_content.py index 5f57b31868..a8f37539fc 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_instancer_content.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_instancer_content.py @@ -2,10 +2,14 @@ import maya.cmds as cmds import pyblish.api from ayon_core.hosts.maya.api import lib -from ayon_core.pipeline.publish import PublishValidationError +from ayon_core.pipeline.publish import ( + PublishValidationError, + OptionalPyblishPluginMixin +) -class ValidateInstancerContent(pyblish.api.InstancePlugin): +class ValidateInstancerContent(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validates that all meshes in the instance have object IDs. This skips a check on intermediate objects because we consider them @@ -14,9 +18,11 @@ class ValidateInstancerContent(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = 'Instancer Content' families = ['instancer'] + optional = False def process(self, instance): - + if not self.is_active(instance.data): + return error = False members = instance.data['setMembers'] export_members = instance.data['exactExportMembers'] diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_instancer_frame_ranges.py b/client/ayon_core/hosts/maya/plugins/publish/validate_instancer_frame_ranges.py index be6724d7e9..5efa9da678 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_instancer_frame_ranges.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_instancer_frame_ranges.py @@ -3,8 +3,10 @@ import re import pyblish.api -from ayon_core.pipeline.publish import PublishValidationError - +from ayon_core.pipeline.publish import ( + PublishValidationError, + OptionalPyblishPluginMixin +) def is_cache_resource(resource): """Return whether resource is a cacheFile resource""" @@ -34,7 +36,8 @@ def filter_ticks(files): return tick_files, ticks -class ValidateInstancerFrameRanges(pyblish.api.InstancePlugin): +class ValidateInstancerFrameRanges(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validates all instancer particle systems are cached correctly. This means they should have the files/frames as required by the start-end @@ -46,10 +49,12 @@ class ValidateInstancerFrameRanges(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = 'Instancer Cache Frame Ranges' families = ['instancer'] + optional = False @classmethod def get_invalid(cls, instance): - + if not self.is_active(instance.data): + return import pyseq start_frame = instance.data.get("frameStart", 0) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_loaded_plugin.py b/client/ayon_core/hosts/maya/plugins/publish/validate_loaded_plugin.py index 54a3e16111..8fde7d5f67 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_loaded_plugin.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_loaded_plugin.py @@ -15,6 +15,7 @@ class ValidateLoadedPlugin(pyblish.api.ContextPlugin): order = pyblish.api.ValidatorOrder host = ["maya"] actions = [RepairContextAction] + optional = True @classmethod def get_invalid(cls, context): @@ -35,7 +36,8 @@ class ValidateLoadedPlugin(pyblish.api.ContextPlugin): return invalid def process(self, context): - + if not self.is_active(context.data): + return invalid = self.get_invalid(context) if invalid: raise PublishValidationError( diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_look_shading_group.py b/client/ayon_core/hosts/maya/plugins/publish/validate_look_shading_group.py index 656b91216b..b1f2b3f307 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_look_shading_group.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_look_shading_group.py @@ -14,7 +14,7 @@ class ValidateShadingEngine(pyblish.api.InstancePlugin): Shading engines should be named "{surface_shader}SG" """ - +`` order = ValidateContentsOrder families = ["look"] hosts = ["maya"] @@ -22,9 +22,12 @@ class ValidateShadingEngine(pyblish.api.InstancePlugin): actions = [ ayon_core.hosts.maya.api.action.SelectInvalidAction, RepairAction ] + optional = True # The default connections to check def process(self, instance): + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_maya_units.py b/client/ayon_core/hosts/maya/plugins/publish/validate_maya_units.py index 1c00d86868..f1c171bddc 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_maya_units.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_maya_units.py @@ -7,11 +7,13 @@ from ayon_core.pipeline.context_tools import get_current_project_folder from ayon_core.pipeline.publish import ( RepairContextAction, ValidateSceneOrder, - PublishXmlValidationError + PublishXmlValidationError, + OptionalPyblishPluginMixin ) -class ValidateMayaUnits(pyblish.api.ContextPlugin): +class ValidateMayaUnits(pyblish.api.ContextPlugin, + OptionalPyblishPluginMixin): """Check if the Maya units are set correct""" order = ValidateSceneOrder @@ -35,6 +37,7 @@ class ValidateMayaUnits(pyblish.api.ContextPlugin): "Maya scene {setting} must be '{required_value}'. " "Current value is '{current_value}'." ) + optional = False @classmethod def apply_settings(cls, project_settings): @@ -52,7 +55,8 @@ class ValidateMayaUnits(pyblish.api.ContextPlugin): cls.validate_fps = settings.get("validate_fps", cls.validate_fps) def process(self, context): - + if not self.is_active(context.data): + return # Collected units linearunits = context.data.get('linearUnits') angularunits = context.data.get('angularUnits') diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_lamina_faces.py b/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_lamina_faces.py index e76553629f..8f80b689fd 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_lamina_faces.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_lamina_faces.py @@ -2,10 +2,11 @@ from maya import cmds import pyblish.api import ayon_core.hosts.maya.api.action -from ayon_core.pipeline.publish import ValidateMeshOrder +from ayon_core.pipeline.publish import ValidateMeshOrder, OptionalPyblishPluginMixin -class ValidateMeshLaminaFaces(pyblish.api.InstancePlugin): +class ValidateMeshLaminaFaces(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validate meshes don't have lamina faces. Lamina faces share all of their edges. @@ -17,6 +18,7 @@ class ValidateMeshLaminaFaces(pyblish.api.InstancePlugin): families = ['model'] label = 'Mesh Lamina Faces' actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction] + optional = True @staticmethod def get_invalid(instance): @@ -28,6 +30,8 @@ class ValidateMeshLaminaFaces(pyblish.api.InstancePlugin): def process(self, instance): """Process all the nodes in the instance 'objectSet'""" + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_ngons.py b/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_ngons.py index f8dfe65b32..5f107b7f7e 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_ngons.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_ngons.py @@ -3,10 +3,14 @@ from maya import cmds import pyblish.api import ayon_core.hosts.maya.api.action from ayon_core.hosts.maya.api import lib -from ayon_core.pipeline.publish import ValidateContentsOrder +from ayon_core.pipeline.publish import ( + ValidateContentsOrder, + OptionalPyblishPluginMixin +) -class ValidateMeshNgons(pyblish.api.Validator): +class ValidateMeshNgons(pyblish.api.Validator, + OptionalPyblishPluginMixin): """Ensure that meshes don't have ngons Ngon are faces with more than 4 sides. @@ -21,6 +25,7 @@ class ValidateMeshNgons(pyblish.api.Validator): families = ["model"] label = "Mesh ngons" actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction] + optional = True @staticmethod def get_invalid(instance): @@ -39,6 +44,8 @@ class ValidateMeshNgons(pyblish.api.Validator): def process(self, instance): """Process all the nodes in the instance "objectSet""" + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_no_negative_scale.py b/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_no_negative_scale.py index 0e9147d978..ff1dca87cf 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_no_negative_scale.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_no_negative_scale.py @@ -4,7 +4,8 @@ import pyblish.api import ayon_core.hosts.maya.api.action from ayon_core.pipeline.publish import ( ValidateMeshOrder, - PublishValidationError + PublishValidationError, + OptionalPyblishPluginMixin ) @@ -15,7 +16,8 @@ def _as_report_list(values, prefix="- ", suffix="\n"): return prefix + (suffix + prefix).join(values) -class ValidateMeshNoNegativeScale(pyblish.api.Validator): +class ValidateMeshNoNegativeScale(pyblish.api.Validator, + OptionalPyblishPluginMixin): """Ensure that meshes don't have a negative scale. Using negatively scaled proxies in a VRayMesh results in inverted @@ -32,6 +34,7 @@ class ValidateMeshNoNegativeScale(pyblish.api.Validator): families = ['model'] label = 'Mesh No Negative Scale' actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction] + optional = False @staticmethod def get_invalid(instance): @@ -52,7 +55,8 @@ class ValidateMeshNoNegativeScale(pyblish.api.Validator): def process(self, instance): """Process all the nodes in the instance 'objectSet'""" - + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_non_manifold.py b/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_non_manifold.py index 1c7ea10a50..6dbad538ef 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_non_manifold.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_non_manifold.py @@ -4,7 +4,8 @@ import pyblish.api import ayon_core.hosts.maya.api.action from ayon_core.pipeline.publish import ( ValidateMeshOrder, - PublishValidationError + PublishValidationError, + OptionalPyblishPluginMixin ) @@ -15,7 +16,8 @@ def _as_report_list(values, prefix="- ", suffix="\n"): return prefix + (suffix + prefix).join(values) -class ValidateMeshNonManifold(pyblish.api.Validator): +class ValidateMeshNonManifold(pyblish.api.Validator, + OptionalPyblishPluginMixin): """Ensure that meshes don't have non-manifold edges or vertices To debug the problem on the meshes you can use Maya's modeling @@ -28,6 +30,7 @@ class ValidateMeshNonManifold(pyblish.api.Validator): families = ['model'] label = 'Mesh Non-Manifold Edges/Vertices' actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction] + optional = True @staticmethod def get_invalid(instance): @@ -44,7 +47,8 @@ class ValidateMeshNonManifold(pyblish.api.Validator): def process(self, instance): """Process all the nodes in the instance 'objectSet'""" - + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_shader_connections.py b/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_shader_connections.py index d55b58cd0d..8672ac13dd 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_shader_connections.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_shader_connections.py @@ -5,7 +5,8 @@ import ayon_core.hosts.maya.api.action from ayon_core.pipeline.publish import ( RepairAction, ValidateMeshOrder, - PublishValidationError + PublishValidationError, + OptionalPyblishPluginMixin ) @@ -79,7 +80,8 @@ def disconnect(node_a, node_b): cmds.disconnectAttr(source, input) -class ValidateMeshShaderConnections(pyblish.api.InstancePlugin): +class ValidateMeshShaderConnections(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Ensure mesh shading engine connections are valid. In some scenarios Maya keeps connections to multiple shaders even if just @@ -96,10 +98,12 @@ class ValidateMeshShaderConnections(pyblish.api.InstancePlugin): label = "Mesh Shader Connections" actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction, RepairAction] + optional = True def process(self, instance): """Process all the nodes in the instance 'objectSet'""" - + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_vertices_have_edges.py b/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_vertices_have_edges.py index 10b5d77cf3..f0962148dc 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_vertices_have_edges.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_vertices_have_edges.py @@ -4,10 +4,15 @@ from maya import cmds import ayon_core.hosts.maya.api.action from ayon_core.hosts.maya.api.lib import len_flattened from ayon_core.pipeline.publish import ( - PublishValidationError, RepairAction, ValidateMeshOrder) + PublishValidationError, + RepairAction, + ValidateMeshOrder, + OptionalPyblishPluginMixin +) -class ValidateMeshVerticesHaveEdges(pyblish.api.InstancePlugin): +class ValidateMeshVerticesHaveEdges(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validate meshes have only vertices that are connected to edges. Maya can have invalid geometry with vertices that have no edges or @@ -32,6 +37,7 @@ class ValidateMeshVerticesHaveEdges(pyblish.api.InstancePlugin): label = 'Mesh Vertices Have Edges' actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction, RepairAction] + optional = True @classmethod def repair(cls, instance): @@ -72,7 +78,8 @@ class ValidateMeshVerticesHaveEdges(pyblish.api.InstancePlugin): return invalid def process(self, instance): - + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: raise PublishValidationError( diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_model_content.py b/client/ayon_core/hosts/maya/plugins/publish/validate_model_content.py index b0db5e435a..37c78a72ee 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_model_content.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_model_content.py @@ -5,11 +5,13 @@ import ayon_core.hosts.maya.api.action from ayon_core.hosts.maya.api import lib from ayon_core.pipeline.publish import ( ValidateContentsOrder, - PublishValidationError + PublishValidationError, + OptionalPyblishPluginMixin ) -class ValidateModelContent(pyblish.api.InstancePlugin): +class ValidateModelContent(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Adheres to the content of 'model' product type - Must have one top group. (configurable) @@ -24,6 +26,7 @@ class ValidateModelContent(pyblish.api.InstancePlugin): actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction] validate_top_group = True + optional = False @classmethod def get_invalid(cls, instance): @@ -91,7 +94,8 @@ class ValidateModelContent(pyblish.api.InstancePlugin): return list(invalid) def process(self, instance): - + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_no_default_camera.py b/client/ayon_core/hosts/maya/plugins/publish/validate_no_default_camera.py index 9977562ca3..3e21ec6e50 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_no_default_camera.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_no_default_camera.py @@ -4,7 +4,8 @@ import pyblish.api import ayon_core.hosts.maya.api.action from ayon_core.pipeline.publish import ( ValidateContentsOrder, - PublishValidationError + PublishValidationError, + OptionalPyblishPluginMixin ) @@ -15,7 +16,8 @@ def _as_report_list(values, prefix="- ", suffix="\n"): return prefix + (suffix + prefix).join(values) -class ValidateNoDefaultCameras(pyblish.api.InstancePlugin): +class ValidateNoDefaultCameras(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Ensure no default (startup) cameras are in the instance. This might be unnecessary. In the past there were some issues with @@ -28,6 +30,7 @@ class ValidateNoDefaultCameras(pyblish.api.InstancePlugin): families = ['camera'] label = "No Default Cameras" actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction] + optional = False @staticmethod def get_invalid(instance): @@ -37,6 +40,8 @@ class ValidateNoDefaultCameras(pyblish.api.InstancePlugin): def process(self, instance): """Process all the cameras in the instance""" + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: raise PublishValidationError( diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_no_namespace.py b/client/ayon_core/hosts/maya/plugins/publish/validate_no_namespace.py index b9b8aa2708..7ea2a79339 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_no_namespace.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_no_namespace.py @@ -4,7 +4,8 @@ import pyblish.api from ayon_core.pipeline.publish import ( RepairAction, ValidateContentsOrder, - PublishValidationError + PublishValidationError, + OptionalPyblishPluginMixin ) import ayon_core.hosts.maya.api.action @@ -24,7 +25,8 @@ def get_namespace(node_name): return node_name.rpartition(":")[0] -class ValidateNoNamespace(pyblish.api.InstancePlugin): +class ValidateNoNamespace(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Ensure the nodes don't have a namespace""" order = ValidateContentsOrder @@ -33,6 +35,7 @@ class ValidateNoNamespace(pyblish.api.InstancePlugin): label = 'No Namespaces' actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction, RepairAction] + optional = False @staticmethod def get_invalid(instance): @@ -41,6 +44,8 @@ class ValidateNoNamespace(pyblish.api.InstancePlugin): def process(self, instance): """Process all the nodes in the instance""" + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_no_null_transforms.py b/client/ayon_core/hosts/maya/plugins/publish/validate_no_null_transforms.py index 9899768dc0..a9dc1d5bef 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_no_null_transforms.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_no_null_transforms.py @@ -5,7 +5,8 @@ import ayon_core.hosts.maya.api.action from ayon_core.pipeline.publish import ( RepairAction, ValidateContentsOrder, - PublishValidationError + PublishValidationError, + OptionalPyblishPluginMixin ) @@ -37,7 +38,8 @@ def has_shape_children(node): return True -class ValidateNoNullTransforms(pyblish.api.InstancePlugin): +class ValidateNoNullTransforms(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Ensure no null transforms are in the scene. Warning: @@ -54,6 +56,7 @@ class ValidateNoNullTransforms(pyblish.api.InstancePlugin): label = 'No Empty/Null Transforms' actions = [RepairAction, ayon_core.hosts.maya.api.action.SelectInvalidAction] + optional = False @staticmethod def get_invalid(instance): @@ -70,6 +73,8 @@ class ValidateNoNullTransforms(pyblish.api.InstancePlugin): def process(self, instance): """Process all the transform nodes in the instance """ + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: raise PublishValidationError( diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_no_vraymesh.py b/client/ayon_core/hosts/maya/plugins/publish/validate_no_vraymesh.py index be8296a820..2d59608e11 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_no_vraymesh.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_no_vraymesh.py @@ -1,7 +1,9 @@ import pyblish.api from maya import cmds -from ayon_core.pipeline.publish import PublishValidationError - +from ayon_core.pipeline.publish import ( + PublishValidationError, + OptionalPyblishPluginMixin +) def _as_report_list(values, prefix="- ", suffix="\n"): """Return list as bullet point list for a report""" @@ -10,15 +12,18 @@ def _as_report_list(values, prefix="- ", suffix="\n"): return prefix + (suffix + prefix).join(values) -class ValidateNoVRayMesh(pyblish.api.InstancePlugin): +class ValidateNoVRayMesh(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validate there are no VRayMesh objects in the instance""" order = pyblish.api.ValidatorOrder label = 'No V-Ray Proxies (VRayMesh)' families = ["pointcache"] + optional = False def process(self, instance): - + if not self.is_active(instance.data): + return if not cmds.pluginInfo("vrayformaya", query=True, loaded=True): return diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_node_no_ghosting.py b/client/ayon_core/hosts/maya/plugins/publish/validate_node_no_ghosting.py index 297618fd4f..73701f8d83 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_node_no_ghosting.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_node_no_ghosting.py @@ -3,10 +3,13 @@ from maya import cmds import pyblish.api import ayon_core.hosts.maya.api.action -from ayon_core.pipeline.publish import ValidateContentsOrder +from ayon_core.pipeline.publish import ( + ValidateContentsOrder, + OptionalPyblishPluginMixin - -class ValidateNodeNoGhosting(pyblish.api.InstancePlugin): +) +class ValidateNodeNoGhosting(pyblish.api.InstancePlugin. + OptionalPyblishPluginMixin): """Ensure nodes do not have ghosting enabled. If one would publish towards a non-Maya format it's likely that stats @@ -23,6 +26,7 @@ class ValidateNodeNoGhosting(pyblish.api.InstancePlugin): families = ['model', 'rig'] label = "No Ghosting" actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction] + optional = False _attributes = {'ghosting': 0} @@ -46,7 +50,8 @@ class ValidateNodeNoGhosting(pyblish.api.InstancePlugin): return invalid def process(self, instance): - + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_plugin_path_attributes.py b/client/ayon_core/hosts/maya/plugins/publish/validate_plugin_path_attributes.py index fd71039e30..f961ec6e4a 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_plugin_path_attributes.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_plugin_path_attributes.py @@ -8,11 +8,13 @@ from ayon_core.hosts.maya.api.lib import pairwise from ayon_core.hosts.maya.api.action import SelectInvalidAction from ayon_core.pipeline.publish import ( ValidateContentsOrder, - PublishValidationError + PublishValidationError, + OptionalPyblishPluginMixin ) -class ValidatePluginPathAttributes(pyblish.api.InstancePlugin): +class ValidatePluginPathAttributes(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """ Validate plug-in path attributes point to existing file paths. """ @@ -22,6 +24,7 @@ class ValidatePluginPathAttributes(pyblish.api.InstancePlugin): families = ["workfile"] label = "Plug-in Path Attributes" actions = [SelectInvalidAction] + optional = False # Attributes are defined in project settings attribute = [] @@ -60,6 +63,8 @@ class ValidatePluginPathAttributes(pyblish.api.InstancePlugin): def process(self, instance): """Process all directories Set as Filenames in Non-Maya Nodes""" + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: raise PublishValidationError( diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_render_image_rule.py b/client/ayon_core/hosts/maya/plugins/publish/validate_render_image_rule.py index 384d99df1a..117f7df822 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_render_image_rule.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_render_image_rule.py @@ -5,10 +5,15 @@ import pyblish.api from maya import cmds from ayon_core.pipeline.publish import ( - PublishValidationError, RepairAction, ValidateContentsOrder) + PublishValidationError, + RepairAction, + ValidateContentsOrder, + OptionalPyblishPluginMixin +) -class ValidateRenderImageRule(pyblish.api.InstancePlugin): +class ValidateRenderImageRule(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validates Maya Workpace "images" file rule matches project settings. This validates against the configured default render image folder: @@ -22,9 +27,11 @@ class ValidateRenderImageRule(pyblish.api.InstancePlugin): hosts = ["maya"] families = ["renderlayer"] actions = [RepairAction] + optional = False def process(self, instance): - + if not self.is_active(instance.data): + return required_images_rule = os.path.normpath( self.get_default_render_image_folder(instance) ) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_render_no_default_cameras.py b/client/ayon_core/hosts/maya/plugins/publish/validate_render_no_default_cameras.py index 32d0470b7f..41c0fa4807 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_render_no_default_cameras.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_render_no_default_cameras.py @@ -6,10 +6,12 @@ import ayon_core.hosts.maya.api.action from ayon_core.pipeline.publish import ( ValidateContentsOrder, PublishValidationError, + OptionalPyblishPluginMixin ) -class ValidateRenderNoDefaultCameras(pyblish.api.InstancePlugin): +class ValidateRenderNoDefaultCameras(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Ensure no default (startup) cameras are to be rendered.""" order = ValidateContentsOrder @@ -17,6 +19,7 @@ class ValidateRenderNoDefaultCameras(pyblish.api.InstancePlugin): families = ['renderlayer'] label = "No Default Cameras Renderable" actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction] + optional = False @staticmethod def get_invalid(instance): @@ -32,6 +35,8 @@ class ValidateRenderNoDefaultCameras(pyblish.api.InstancePlugin): def process(self, instance): """Process all the cameras in the instance""" + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: raise PublishValidationError( diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_render_single_camera.py b/client/ayon_core/hosts/maya/plugins/publish/validate_render_single_camera.py index f31059f594..0171318813 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_render_single_camera.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_render_single_camera.py @@ -7,11 +7,13 @@ import ayon_core.hosts.maya.api.action from ayon_core.hosts.maya.api.lib_rendersettings import RenderSettings from ayon_core.pipeline.publish import ( ValidateContentsOrder, - PublishValidationError + PublishValidationError, + OptionalPyblishPluginMixin ) -class ValidateRenderSingleCamera(pyblish.api.InstancePlugin): +class ValidateRenderSingleCamera(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validate renderable camera count for layer and token. Pipeline is supporting multiple renderable cameras per layer, but image @@ -24,11 +26,14 @@ class ValidateRenderSingleCamera(pyblish.api.InstancePlugin): families = ["renderlayer", "vrayscene"] actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction] + optional = False R_CAMERA_TOKEN = re.compile(r'%c|', re.IGNORECASE) def process(self, instance): """Process all the cameras in the instance""" + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: raise PublishValidationError("Invalid cameras for render.") diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_renderlayer_aovs.py b/client/ayon_core/hosts/maya/plugins/publish/validate_renderlayer_aovs.py index 90f256ad45..910e336fc1 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_renderlayer_aovs.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_renderlayer_aovs.py @@ -2,10 +2,13 @@ import ayon_api import pyblish.api import ayon_core.hosts.maya.api.action -from ayon_core.pipeline.publish import PublishValidationError +from ayon_core.pipeline.publish import ( + PublishValidationError, + OptionalPyblishPluginMixin +) - -class ValidateRenderLayerAOVs(pyblish.api.InstancePlugin): +class ValidateRenderLayerAOVs(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validate created AOVs / RenderElement is registered in the database Each render element is registered as a product which is formatted based on @@ -26,8 +29,12 @@ class ValidateRenderLayerAOVs(pyblish.api.InstancePlugin): hosts = ["maya"] families = ["renderlayer"] actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction] + optional = False def process(self, instance): + if not self.is_active(instance.data): + return + invalid = self.get_invalid(instance) if invalid: raise PublishValidationError( diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_rig_contents.py b/client/ayon_core/hosts/maya/plugins/publish/validate_rig_contents.py index be495a8fb9..9034fef928 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_rig_contents.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_rig_contents.py @@ -3,11 +3,13 @@ from maya import cmds import ayon_core.hosts.maya.api.action from ayon_core.pipeline.publish import ( PublishValidationError, - ValidateContentsOrder + ValidateContentsOrder, + OptionalPyblishPluginMixin ) -class ValidateRigContents(pyblish.api.InstancePlugin): +class ValidateRigContents(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Ensure rig contains pipeline-critical content Every rig must contain at least two object sets: @@ -21,11 +23,14 @@ class ValidateRigContents(pyblish.api.InstancePlugin): hosts = ["maya"] families = ["rig"] action = [ayon_core.hosts.maya.api.action.SelectInvalidAction] + optional = True accepted_output = ["mesh", "transform"] accepted_controllers = ["transform"] def process(self, instance): + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: raise PublishValidationError( @@ -213,6 +218,7 @@ class ValidateSkeletonRigContents(ValidateRigContents): label = "Skeleton Rig Contents" hosts = ["maya"] families = ["rig.fbx"] + optional = True @classmethod def get_invalid(cls, instance): diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_rig_controllers.py b/client/ayon_core/hosts/maya/plugins/publish/validate_rig_controllers.py index 469412dd1a..814ff50177 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_rig_controllers.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_rig_controllers.py @@ -5,13 +5,15 @@ import pyblish.api from ayon_core.pipeline.publish import ( ValidateContentsOrder, RepairAction, - PublishValidationError + PublishValidationError, + OptionalPyblishPluginMixin ) import ayon_core.hosts.maya.api.action from ayon_core.hosts.maya.api.lib import undo_chunk -class ValidateRigControllers(pyblish.api.InstancePlugin): +class ValidateRigControllers(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validate rig controllers. Controls must have the transformation attributes on their default @@ -33,6 +35,7 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): label = "Rig Controllers" hosts = ["maya"] families = ["rig"] + optional = True actions = [RepairAction, ayon_core.hosts.maya.api.action.SelectInvalidAction] @@ -50,6 +53,9 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): } def process(self, instance): + if not self.is_active(instance.data): + return + invalid = self.get_invalid(instance) if invalid: raise PublishValidationError( diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_rig_controllers_arnold_attributes.py b/client/ayon_core/hosts/maya/plugins/publish/validate_rig_controllers_arnold_attributes.py index 2227899a5b..ea2de81036 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_rig_controllers_arnold_attributes.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_rig_controllers_arnold_attributes.py @@ -5,14 +5,16 @@ import pyblish.api from ayon_core.pipeline.publish import ( ValidateContentsOrder, RepairAction, - PublishValidationError + PublishValidationError, + OptionalPyblishPluginMixin ) from ayon_core.hosts.maya.api import lib import ayon_core.hosts.maya.api.action -class ValidateRigControllersArnoldAttributes(pyblish.api.InstancePlugin): +class ValidateRigControllersArnoldAttributes(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validate rig control curves have no keyable arnold attributes. The Arnold plug-in will create curve attributes like: @@ -35,6 +37,7 @@ class ValidateRigControllersArnoldAttributes(pyblish.api.InstancePlugin): label = "Rig Controllers (Arnold Attributes)" hosts = ["maya"] families = ["rig"] + optional = False actions = [RepairAction, ayon_core.hosts.maya.api.action.SelectInvalidAction] @@ -48,6 +51,9 @@ class ValidateRigControllersArnoldAttributes(pyblish.api.InstancePlugin): ] def process(self, instance): + if not self.is_active(instance.data): + return + invalid = self.get_invalid(instance) if invalid: raise PublishValidationError('{} failed, see log ' diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_rig_joints_hidden.py b/client/ayon_core/hosts/maya/plugins/publish/validate_rig_joints_hidden.py index bb5ec8353e..78cc3f5938 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_rig_joints_hidden.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_rig_joints_hidden.py @@ -7,11 +7,13 @@ from ayon_core.hosts.maya.api import lib from ayon_core.pipeline.publish import ( RepairAction, ValidateContentsOrder, - PublishValidationError + PublishValidationError, + OptionalPyblishPluginMixin ) -class ValidateRigJointsHidden(pyblish.api.InstancePlugin): +class ValidateRigJointsHidden(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validate all joints are hidden visually. This includes being hidden: @@ -28,6 +30,7 @@ class ValidateRigJointsHidden(pyblish.api.InstancePlugin): label = "Joints Hidden" actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction, RepairAction] + optional = True @staticmethod def get_invalid(instance): @@ -36,6 +39,8 @@ class ValidateRigJointsHidden(pyblish.api.InstancePlugin): def process(self, instance): """Process all the nodes in the instance 'objectSet'""" + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py b/client/ayon_core/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py index dccf9cc47b..ab8cc25210 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py @@ -7,11 +7,13 @@ from ayon_core.hosts.maya.api import lib from ayon_core.pipeline.publish import ( RepairAction, ValidateContentsOrder, - PublishValidationError + PublishValidationError, + OptionalPyblishPluginMixin ) -class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin): +class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validate if deformed shapes have related IDs to the original shapes. When a deformer is applied in the scene on a referenced mesh that already @@ -30,10 +32,12 @@ class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin): RepairAction ] allow_history_only = False + optional = False def process(self, instance): """Process all meshes""" - + if not self.is_active(instance.data): + return # Ensure all nodes have a cbId and a related ID to the original shapes # if a deformer has been created on the shape invalid = self.get_invalid(instance) @@ -114,6 +118,7 @@ class ValidateSkeletonRigOutSetNodeIds(ValidateRigOutSetNodeIds): families = ["rig.fbx"] hosts = ['maya'] label = 'Skeleton Rig Out Set Node Ids' + optional = False @classmethod def get_node(cls, instance): diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_shape_render_stats.py b/client/ayon_core/hosts/maya/plugins/publish/validate_shape_render_stats.py index ffdb43ef55..2783a6dbe8 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_shape_render_stats.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_shape_render_stats.py @@ -6,10 +6,12 @@ import ayon_core.hosts.maya.api.action from ayon_core.pipeline.publish import ( RepairAction, ValidateMeshOrder, + OptionalPyblishPluginMixin ) -class ValidateShapeRenderStats(pyblish.api.Validator): +class ValidateShapeRenderStats(pyblish.api.Validator, + OptionalPyblishPluginMixin): """Ensure all render stats are set to the default values.""" order = ValidateMeshOrder @@ -18,6 +20,7 @@ class ValidateShapeRenderStats(pyblish.api.Validator): label = 'Shape Default Render Stats' actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction, RepairAction] + optional = True defaults = {'castsShadows': 1, 'receiveShadows': 1, @@ -46,7 +49,8 @@ class ValidateShapeRenderStats(pyblish.api.Validator): return invalid def process(self, instance): - + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_shape_zero.py b/client/ayon_core/hosts/maya/plugins/publish/validate_shape_zero.py index 6cf3edf472..4f4826776c 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_shape_zero.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_shape_zero.py @@ -7,11 +7,13 @@ from ayon_core.hosts.maya.api import lib from ayon_core.pipeline.publish import ( ValidateContentsOrder, RepairAction, - PublishValidationError + PublishValidationError, + OptionalPyblishPluginMixin ) -class ValidateShapeZero(pyblish.api.Validator): +class ValidateShapeZero(pyblish.api.Validator, + OptionalPyblishPluginMixin): """Shape components may not have any "tweak" values To solve this issue, try freezing the shapes. @@ -26,6 +28,7 @@ class ValidateShapeZero(pyblish.api.Validator): ayon_core.hosts.maya.api.action.SelectInvalidAction, RepairAction ] + optional = True @staticmethod def get_invalid(instance): @@ -65,6 +68,8 @@ class ValidateShapeZero(pyblish.api.Validator): def process(self, instance): """Process all the nodes in the instance "objectSet""" + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_skeletalmesh_hierarchy.py b/client/ayon_core/hosts/maya/plugins/publish/validate_skeletalmesh_hierarchy.py index ff2ad822b6..172453f1ef 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_skeletalmesh_hierarchy.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_skeletalmesh_hierarchy.py @@ -4,20 +4,25 @@ import pyblish.api from ayon_core.pipeline.publish import ( ValidateContentsOrder, PublishXmlValidationError, + OptionalPyblishPluginMixin ) from maya import cmds -class ValidateSkeletalMeshHierarchy(pyblish.api.InstancePlugin): +class ValidateSkeletalMeshHierarchy(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validates that nodes has common root.""" order = ValidateContentsOrder hosts = ["maya"] families = ["skeletalMesh"] label = "Skeletal Mesh Top Node" + optional = False def process(self, instance): + if not self.is_active(instance.data): + return geo = instance.data.get("geometry") joints = instance.data.get("joints") diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_skeleton_top_group_hierarchy.py b/client/ayon_core/hosts/maya/plugins/publish/validate_skeleton_top_group_hierarchy.py index 7c876240ae..9fbe0f440b 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_skeleton_top_group_hierarchy.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_skeleton_top_group_hierarchy.py @@ -21,8 +21,12 @@ class ValidateSkeletonTopGroupHierarchy(pyblish.api.InstancePlugin, order = ValidateContentsOrder + 0.05 label = "Skeleton Rig Top Group Hierarchy" families = ["rig.fbx"] + optional = True def process(self, instance): + if not self.is_active(instance.data): + return + invalid = [] skeleton_mesh_data = instance.data("skeleton_mesh", []) if skeleton_mesh_data: diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_skinCluster_deformer_set.py b/client/ayon_core/hosts/maya/plugins/publish/validate_skinCluster_deformer_set.py index c104f0477f..48d8e63553 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_skinCluster_deformer_set.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_skinCluster_deformer_set.py @@ -3,10 +3,11 @@ from maya import cmds import pyblish.api import ayon_core.hosts.maya.api.action -from ayon_core.pipeline.publish import ValidateContentsOrder +from ayon_core.pipeline.publish import ValidateContentsOrder,OptionalPyblishPluginMixin -class ValidateSkinclusterDeformerSet(pyblish.api.InstancePlugin): +class ValidateSkinclusterDeformerSet(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validate skinClusters on meshes have valid member relationships. In rare cases it can happen that a mesh has a skinCluster in its history @@ -20,9 +21,12 @@ class ValidateSkinclusterDeformerSet(pyblish.api.InstancePlugin): families = ['fbx'] label = "Skincluster Deformer Relationships" actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction] + optional = False def process(self, instance): """Process all the transform nodes in the instance""" + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_step_size.py b/client/ayon_core/hosts/maya/plugins/publish/validate_step_size.py index 524c7b29ba..a3419a83a9 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_step_size.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_step_size.py @@ -3,11 +3,13 @@ import pyblish.api import ayon_core.hosts.maya.api.action from ayon_core.pipeline.publish import ( PublishValidationError, - ValidateContentsOrder + ValidateContentsOrder, + OptionalPyblishPluginMixin ) -class ValidateStepSize(pyblish.api.InstancePlugin): +class ValidateStepSize(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validates the step size for the instance is in a valid range. For example the `step` size should never be lower or equal to zero. @@ -20,7 +22,7 @@ class ValidateStepSize(pyblish.api.InstancePlugin): 'pointcache', 'animation'] actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction] - + optional = False MIN = 0.01 MAX = 1.0 @@ -40,7 +42,8 @@ class ValidateStepSize(pyblish.api.InstancePlugin): return [] def process(self, instance): - + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: raise PublishValidationError( diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_transform_zero.py b/client/ayon_core/hosts/maya/plugins/publish/validate_transform_zero.py index ddf9d3867d..1cbdd05b0b 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_transform_zero.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_transform_zero.py @@ -5,11 +5,13 @@ import pyblish.api import ayon_core.hosts.maya.api.action from ayon_core.pipeline.publish import ( ValidateContentsOrder, - PublishValidationError + PublishValidationError, + OptionalPyblishPluginMixin ) -class ValidateTransformZero(pyblish.api.Validator): +class ValidateTransformZero(pyblish.api.Validator, + OptionalPyblishPluginMixin): """Transforms can't have any values To solve this issue, try freezing the transforms. So long @@ -29,6 +31,7 @@ class ValidateTransformZero(pyblish.api.Validator): 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0] _tolerance = 1e-30 + optional = True @classmethod def get_invalid(cls, instance): @@ -62,7 +65,8 @@ class ValidateTransformZero(pyblish.api.Validator): def process(self, instance): """Process all the nodes in the instance "objectSet""" - + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_unique_names.py b/client/ayon_core/hosts/maya/plugins/publish/validate_unique_names.py index 55f8933fff..8ec704ddd1 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_unique_names.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_unique_names.py @@ -2,10 +2,13 @@ from maya import cmds import pyblish.api import ayon_core.hosts.maya.api.action -from ayon_core.pipeline.publish import ValidateContentsOrder +from ayon_core.pipeline.publish import ( + ValidateContentsOrder, + OptionalPyblishPluginMixin +) - -class ValidateUniqueNames(pyblish.api.Validator): +class ValidateUniqueNames(pyblish.api.Validator, + OptionalPyblishPluginMixin): """transform names should be unique ie: using cmds.ls(someNodeName) should always return shortname @@ -17,6 +20,7 @@ class ValidateUniqueNames(pyblish.api.Validator): families = ["model"] label = "Unique transform name" actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction] + optional = True @staticmethod def get_invalid(instance): @@ -32,7 +36,8 @@ class ValidateUniqueNames(pyblish.api.Validator): def process(self, instance): """Process all the nodes in the instance "objectSet""" - + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: raise ValueError("Nodes found with none unique names. " diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_unreal_mesh_triangulated.py b/client/ayon_core/hosts/maya/plugins/publish/validate_unreal_mesh_triangulated.py index a39ba7c4cc..101bd5bf04 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_unreal_mesh_triangulated.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_unreal_mesh_triangulated.py @@ -3,11 +3,15 @@ from maya import cmds import pyblish.api -from ayon_core.pipeline.publish import ValidateMeshOrder +from ayon_core.pipeline.publish import ( + ValidateMeshOrder, + OptionalPyblishPluginMixin +) import ayon_core.hosts.maya.api.action -class ValidateUnrealMeshTriangulated(pyblish.api.InstancePlugin): +class ValidateUnrealMeshTriangulated(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validate if mesh is made of triangles for Unreal Engine""" order = ValidateMeshOrder @@ -30,6 +34,8 @@ class ValidateUnrealMeshTriangulated(pyblish.api.InstancePlugin): return invalid def process(self, instance): + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) assert len(invalid) == 0, ( "Found meshes without triangles") diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_visible_only.py b/client/ayon_core/hosts/maya/plugins/publish/validate_visible_only.py index 29cf9420a3..af6c9a64c6 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_visible_only.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_visible_only.py @@ -4,11 +4,13 @@ from ayon_core.hosts.maya.api.lib import iter_visible_nodes_in_range import ayon_core.hosts.maya.api.action from ayon_core.pipeline.publish import ( ValidateContentsOrder, - PublishValidationError + PublishValidationError, + OptionalPyblishPluginMixin ) -class ValidateAlembicVisibleOnly(pyblish.api.InstancePlugin): +class ValidateAlembicVisibleOnly(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validates at least a single node is visible in frame range. This validation only validates if the `visibleOnly` flag is enabled @@ -20,9 +22,11 @@ class ValidateAlembicVisibleOnly(pyblish.api.InstancePlugin): hosts = ["maya"] families = ["pointcache", "animation"] actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction] + optional = False def process(self, instance): - + if not self.is_active(instance.data): + return if not instance.data.get("visibleOnly", False): self.log.debug("Visible only is disabled. Validation skipped..") return diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_vray_distributed_rendering.py b/client/ayon_core/hosts/maya/plugins/publish/validate_vray_distributed_rendering.py index 54eaa58e74..b35508d635 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_vray_distributed_rendering.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_vray_distributed_rendering.py @@ -3,10 +3,15 @@ from maya import cmds from ayon_core.hosts.maya.api import lib from ayon_core.pipeline.publish import ( - PublishValidationError, RepairAction, ValidateContentsOrder) + PublishValidationError, + RepairAction, + ValidateContentsOrder, + OptionalPyblishPluginMixin +) -class ValidateVRayDistributedRendering(pyblish.api.InstancePlugin): +class ValidateVRayDistributedRendering(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validate V-Ray Distributed Rendering is ignored in batch mode. Whenever Distributed Rendering is enabled for V-Ray in the render settings @@ -20,13 +25,15 @@ class ValidateVRayDistributedRendering(pyblish.api.InstancePlugin): label = "VRay Distributed Rendering" families = ["renderlayer"] actions = [RepairAction] + optional = False # V-Ray attribute names enabled_attr = "vraySettings.sys_distributed_rendering_on" ignored_attr = "vraySettings.sys_distributed_rendering_ignore_batch" def process(self, instance): - + if not self.is_active(instance.data): + return if instance.data.get("renderer") != "vray": # If not V-Ray ignore.. return diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_vray_referenced_aovs.py b/client/ayon_core/hosts/maya/plugins/publish/validate_vray_referenced_aovs.py index d4e53d69dc..0ad08b7d14 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_vray_referenced_aovs.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_vray_referenced_aovs.py @@ -4,10 +4,13 @@ import pyblish.api import types from maya import cmds -from ayon_core.pipeline.publish import RepairContextAction +from ayon_core.pipeline.publish import ( + RepairContextAction, + OptionalPyblishPluginMixin +) - -class ValidateVrayReferencedAOVs(pyblish.api.InstancePlugin): +class ValidateVrayReferencedAOVs(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validate whether the V-Ray Render Elements (AOVs) include references. This will check if there are AOVs pulled from references. If @@ -21,9 +24,12 @@ class ValidateVrayReferencedAOVs(pyblish.api.InstancePlugin): hosts = ['maya'] families = ['renderlayer'] actions = [RepairContextAction] + optional = False def process(self, instance): """Plugin main entry point.""" + if not self.is_active(instance.data): + return if instance.data.get("renderer") != "vray": # If not V-Ray ignore.. return diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_vray_translator_settings.py b/client/ayon_core/hosts/maya/plugins/publish/validate_vray_translator_settings.py index f366ee60cf..a3d93dd9c0 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_vray_translator_settings.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_vray_translator_settings.py @@ -5,22 +5,27 @@ from ayon_core.pipeline.publish import ( context_plugin_should_run, RepairContextAction, ValidateContentsOrder, - PublishValidationError + PublishValidationError, + OptionalPyblishPluginMixin ) from maya import cmds -class ValidateVRayTranslatorEnabled(pyblish.api.ContextPlugin): +class ValidateVRayTranslatorEnabled(pyblish.api.ContextPlugin, + OptionalPyblishPluginMixin): """Validate VRay Translator settings for extracting vrscenes.""" order = ValidateContentsOrder label = "VRay Translator Settings" families = ["vrayscene_layer"] actions = [RepairContextAction] + optional = False def process(self, context): """Plugin entry point.""" + if not self.is_active(context.data): + return # Workaround bug pyblish-base#250 if not context_plugin_should_run(self, context): return diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_vrayproxy.py b/client/ayon_core/hosts/maya/plugins/publish/validate_vrayproxy.py index 7e16006f97..29b8be411c 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_vrayproxy.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_vrayproxy.py @@ -1,18 +1,21 @@ import pyblish.api from ayon_core.pipeline import KnownPublishError +from ayon_core.pipeline.publish import OptionalPyblishPluginMixin - -class ValidateVrayProxy(pyblish.api.InstancePlugin): +class ValidateVrayProxy(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): order = pyblish.api.ValidatorOrder label = "VRay Proxy Settings" hosts = ["maya"] families = ["vrayproxy"] + optional = False def process(self, instance): data = instance.data - + if not self.is_active(data): + return if not data["setMembers"]: raise KnownPublishError( "'%s' is empty! This is a bug" % instance.name diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_vrayproxy_members.py b/client/ayon_core/hosts/maya/plugins/publish/validate_vrayproxy_members.py index 1a52771ee6..6732d09202 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_vrayproxy_members.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_vrayproxy_members.py @@ -4,12 +4,14 @@ from maya import cmds import ayon_core.hosts.maya.api.action from ayon_core.pipeline.publish import ( - PublishValidationError + PublishValidationError, + OptionalPyblishPluginMixin ) -class ValidateVrayProxyMembers(pyblish.api.InstancePlugin): +class ValidateVrayProxyMembers(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validate whether the V-Ray Proxy instance has shape members""" order = pyblish.api.ValidatorOrder @@ -17,9 +19,11 @@ class ValidateVrayProxyMembers(pyblish.api.InstancePlugin): hosts = ['maya'] families = ['vrayproxy'] actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction] + optional = False def process(self, instance): - + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_yeti_renderscript_callbacks.py b/client/ayon_core/hosts/maya/plugins/publish/validate_yeti_renderscript_callbacks.py index a72d930339..35b2443718 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_yeti_renderscript_callbacks.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_yeti_renderscript_callbacks.py @@ -1,10 +1,13 @@ from maya import cmds import pyblish.api -from ayon_core.pipeline.publish import ValidateContentsOrder +from ayon_core.pipeline.publish import ( + ValidateContentsOrder, + OptionalPyblishPluginMixin +) - -class ValidateYetiRenderScriptCallbacks(pyblish.api.InstancePlugin): +class ValidateYetiRenderScriptCallbacks(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Check if the render script callbacks will be used during the rendering In order to ensure the render tasks are executed properly we need to check @@ -24,6 +27,7 @@ class ValidateYetiRenderScriptCallbacks(pyblish.api.InstancePlugin): label = "Yeti Render Script Callbacks" hosts = ["maya"] families = ["renderlayer"] + optional = False # Settings per renderer callbacks = { @@ -37,7 +41,8 @@ class ValidateYetiRenderScriptCallbacks(pyblish.api.InstancePlugin): } def process(self, instance): - + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: raise ValueError("Invalid render callbacks found for '%s'!" diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_yeti_rig_cache_state.py b/client/ayon_core/hosts/maya/plugins/publish/validate_yeti_rig_cache_state.py index 22545d07fb..d81534192a 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_yeti_rig_cache_state.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_yeti_rig_cache_state.py @@ -3,12 +3,14 @@ import maya.cmds as cmds import ayon_core.hosts.maya.api.action from ayon_core.pipeline.publish import ( RepairAction, - PublishValidationError + PublishValidationError, + OptionalPyblishPluginMixin ) -class ValidateYetiRigCacheState(pyblish.api.InstancePlugin): +class ValidateYetiRigCacheState(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validate the I/O attributes of the node Every pgYetiMaya cache node per instance should have: @@ -23,8 +25,11 @@ class ValidateYetiRigCacheState(pyblish.api.InstancePlugin): families = ["yetiRig"] actions = [RepairAction, ayon_core.hosts.maya.api.action.SelectInvalidAction] + optional = False def process(self, instance): + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: raise PublishValidationError("Nodes have incorrect I/O settings") diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_yeti_rig_input_in_instance.py b/client/ayon_core/hosts/maya/plugins/publish/validate_yeti_rig_input_in_instance.py index 3d9d8faca8..aa229875fe 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_yeti_rig_input_in_instance.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_yeti_rig_input_in_instance.py @@ -5,11 +5,13 @@ import pyblish.api import ayon_core.hosts.maya.api.action from ayon_core.pipeline.publish import ( ValidateContentsOrder, - PublishValidationError + PublishValidationError, + OptionalPyblishPluginMixin ) -class ValidateYetiRigInputShapesInInstance(pyblish.api.Validator): +class ValidateYetiRigInputShapesInInstance(pyblish.api.Validator, + OptionalPyblishPluginMixin): """Validate if all input nodes are part of the instance's hierarchy""" order = ValidateContentsOrder @@ -17,9 +19,11 @@ class ValidateYetiRigInputShapesInInstance(pyblish.api.Validator): families = ["yetiRig"] label = "Yeti Rig Input Shapes In Instance" actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction] + optional = False def process(self, instance): - + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: raise PublishValidationError("Yeti Rig has invalid input meshes") diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_yeti_rig_settings.py b/client/ayon_core/hosts/maya/plugins/publish/validate_yeti_rig_settings.py index 22a5ccbaca..6bd2ebb753 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_yeti_rig_settings.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_yeti_rig_settings.py @@ -1,9 +1,13 @@ import pyblish.api -from ayon_core.pipeline.publish import PublishValidationError +from ayon_core.pipeline.publish import ( + PublishValidationError, + OptionalPyblishPluginMixin +) -class ValidateYetiRigSettings(pyblish.api.InstancePlugin): +class ValidateYetiRigSettings(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validate Yeti Rig Settings have collected input connections. The input connections are collected for the nodes in the `input_SET`. @@ -15,9 +19,11 @@ class ValidateYetiRigSettings(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Yeti Rig Settings" families = ["yetiRig"] + optional = False def process(self, instance): - + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: raise PublishValidationError( From 5ee980041a13ae2097a9067e2f128b88967e04f9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 19 Mar 2024 23:44:44 +0100 Subject: [PATCH 080/150] Fix outdated highlighting and filtering state for non-hero and hero version - Also optimize the query of highest version by doing one query per product id instead of one per representation id --- .../ayon_core/tools/sceneinventory/model.py | 56 ++++++++++++------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/client/ayon_core/tools/sceneinventory/model.py b/client/ayon_core/tools/sceneinventory/model.py index df0dea7a3d..92591206fb 100644 --- a/client/ayon_core/tools/sceneinventory/model.py +++ b/client/ayon_core/tools/sceneinventory/model.py @@ -68,13 +68,7 @@ class InventoryModel(TreeModel): } def outdated(self, item): - value = item.get("version") - if isinstance(value, HeroVersionType): - return False - - if item.get("version") == item.get("highest_version"): - return False - return True + return item.get("isOutdated", True) def data(self, index, role): if not index.isValid(): @@ -297,6 +291,23 @@ class InventoryModel(TreeModel): ) sites_info = self._controller.get_sites_information() + # Query the highest available version so the model can know + # whether current version is currently up-to-date. + highest_versions = ayon_api.get_versions( + project_name, + product_ids={ + group["version"]["productId"] for group in grouped.values() + }, + latest=True, + standard=True, + hero=False, + fields=["productId", "version"] + ) + highest_version_by_product_id = { + version["productId"]: version["version"] + for version in highest_versions + } + for repre_id, group_dict in sorted(grouped.items()): group_containers = group_dict["containers"] repre_entity = group_dict["representation"] @@ -306,12 +317,6 @@ class InventoryModel(TreeModel): product_type = product_entity["productType"] - # Store the highest available version so the model can know - # whether current version is currently up-to-date. - highest_version = ayon_api.get_last_version_by_product_id( - project_name, version_entity["productId"] - ) - # create the group header group_node = Item() group_node["Name"] = "{}_{}: ({})".format( @@ -321,7 +326,17 @@ class InventoryModel(TreeModel): ) group_node["representation"] = repre_id group_node["version"] = version_entity["version"] - group_node["highest_version"] = highest_version["version"] + + # We check against `abs(version)` because we allow a hero version + # which is represented by a negative number to also count as + # latest version + # If a hero version for whatever reason does not match the latest + # positive version number, we also consider it outdated + group_node["isOutdated"] = ( + abs(version_entity["version"]) != + highest_version_by_product_id.get(version_entity["productId"]) + ) + group_node["productType"] = product_type or "" group_node["productTypeIcon"] = product_type_icon group_node["count"] = len(group_containers) @@ -490,17 +505,15 @@ class FilterProxyModel(QtCore.QSortFilterProxyModel): def _is_outdated(self, row, parent): """Return whether row is outdated. - A row is considered outdated if it has "version" and "highest_version" - data and in the internal data structure, and they are not of an - equal value. + A row is considered outdated if it has no "version" or the "isOutdated" + value is True. """ def outdated(node): version = node.get("version", None) - highest = node.get("highest_version", None) # Always allow indices that have no version data at all - if version is None and highest is None: + if version is None: return True # If either a version or highest is present but not the other @@ -508,9 +521,10 @@ class FilterProxyModel(QtCore.QSortFilterProxyModel): if not self._hierarchy_view: # Skip this check if in hierarchy view, or the child item # node will be hidden even it's actually outdated. - if version is None or highest is None: + if version is None: return False - return version != highest + + return node.get("isOutdated", True) index = self.sourceModel().index(row, self.filterKeyColumn(), parent) From 7b5b2a980cf68afd3fa5e8051b0e15d9f88d913f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 19 Mar 2024 23:56:54 +0100 Subject: [PATCH 081/150] Fix update to latest - it now updates to latest instead of hero version --- client/ayon_core/pipeline/load/utils.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/pipeline/load/utils.py b/client/ayon_core/pipeline/load/utils.py index f3d39800cd..66ead70c14 100644 --- a/client/ayon_core/pipeline/load/utils.py +++ b/client/ayon_core/pipeline/load/utils.py @@ -479,10 +479,15 @@ def update_container(container, version=-1): project_name, current_version["productId"] ) elif version == -1: - new_version = ayon_api.get_last_version_by_product_id( - project_name, current_version["productId"] - ) - + # TODO: Use `ayon_api.get_last_version_by_product_id` when fixed + # to not return hero versions instead of the last version + new_version = next(ayon_api.get_versions( + project_name, + product_ids=[current_version["productId"]], + standard=True, + hero=False, + latest=True, + ), None) else: new_version = ayon_api.get_version_by_name( project_name, version, current_version["productId"] From b5c34db032f4d586eb12c41b767ee537cf304008 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 00:11:23 +0100 Subject: [PATCH 082/150] Fix import --- client/ayon_core/hosts/maya/plugins/load/load_vrayproxy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 651197627e..59d8eadefa 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_vrayproxy.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_vrayproxy.py @@ -7,9 +7,9 @@ loader will use them instead of native vray vrmesh format. """ import os -from ayon_api import get_representation_by_name import maya.cmds as cmds +import ayon_api from ayon_core.settings import get_project_settings from ayon_core.pipeline import ( load, From 7dc202acc9cbfbfcdd1b52db192c3ed8c2173326 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 00:25:21 +0100 Subject: [PATCH 083/150] Removed redundant logic which was broken anyway, call to `get_representation_context` had wrong arguments Note: Arguments passed to `get_representation_context` were wrong because it also takes `project_name` as argument --- .../hosts/tvpaint/plugins/load/load_reference_image.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) 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 a3bcf4c144..ec671b0cb1 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 @@ -1,10 +1,7 @@ import collections from ayon_core.lib.attribute_definitions import BoolDef -from ayon_core.pipeline import ( - get_representation_context, - registered_host, -) +from ayon_core.pipeline import registered_host from ayon_core.hosts.tvpaint.api import plugin from ayon_core.hosts.tvpaint.api.lib import ( get_layers_data, @@ -218,10 +215,7 @@ class LoadImage(plugin.Loader): removed. """ - repre_entity = context["representation"] # Create new containers first - context = get_representation_context(repre_entity["id"]) - # Get layer ids from previous container old_layer_names = self.get_members_from_container(container) From a1b32846f0506e2385c2e3b2a9bb441481c07397 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 00:47:03 +0100 Subject: [PATCH 084/150] Return the invalid nodes --- .../hosts/maya/plugins/publish/validate_rig_contents.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_rig_contents.py b/client/ayon_core/hosts/maya/plugins/publish/validate_rig_contents.py index be495a8fb9..7e483ca37a 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_rig_contents.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_rig_contents.py @@ -162,6 +162,8 @@ class ValidateRigContents(pyblish.api.InstancePlugin): if cmds.nodeType(shape) not in cls.accepted_output: invalid.append(shape) + return invalid + @classmethod def validate_controls(cls, set_members): """ From 714c798420cd57e1c2ebd1867d5a8e59685c61b5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 00:52:18 +0100 Subject: [PATCH 085/150] Fix docstrings --- .../plugins/publish/validate_rig_contents.py | 36 ++++++++----------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_rig_contents.py b/client/ayon_core/hosts/maya/plugins/publish/validate_rig_contents.py index 7e483ca37a..9d4ec0a41a 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_rig_contents.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_rig_contents.py @@ -87,9 +87,9 @@ class ValidateRigContents(pyblish.api.InstancePlugin): """Validate missing objectsets in rig sets Args: - instance (str): instance - required_objsets (list): list of objectset names - rig_sets (list): list of rig sets + instance (pyblish.api.Instance): instance + required_objsets (list[str]): list of objectset names + rig_sets (list[str]): list of rig sets Raises: PublishValidationError: When the error is raised, it will show @@ -109,15 +109,15 @@ class ValidateRigContents(pyblish.api.InstancePlugin): Check if all rig set members are within the hierarchy of the rig root Args: - instance (str): instance - content (list): list of content from rig sets + instance (pyblish.api.Instance): instance + content (list[str]): list of content from rig sets Raises: PublishValidationError: It means no dag nodes in the rig instance Returns: - list: invalid hierarchy + List[str]: invalid hierarchy """ # Ensure there are at least some transforms or dag nodes # in the rig instance @@ -140,15 +140,13 @@ class ValidateRigContents(pyblish.api.InstancePlugin): @classmethod def validate_geometry(cls, set_members): - """ - Checks if the node types of the set members valid + """Checks if the node types of the set members valid Args: - set_members: list of nodes of the controls_set - hierarchy: list of nodes which reside under the root node + set_members (list[str]): nodes of the out_set Returns: - errors (list) + list[str]: Nodes of invalid types. """ # Validate all shape types @@ -166,16 +164,13 @@ class ValidateRigContents(pyblish.api.InstancePlugin): @classmethod def validate_controls(cls, set_members): - """ - Checks if the control set members are allowed node types. - Checks if the node types of the set members valid + """Checks if the node types of the set members are valid for controls. Args: - set_members: list of nodes of the controls_set - hierarchy: list of nodes which reside under the root node + set_members (list[str]): list of nodes of the controls_set Returns: - errors (list) + list: Controls of disallowed node types. """ # Validate control types @@ -191,7 +186,7 @@ class ValidateRigContents(pyblish.api.InstancePlugin): """Get the target objectsets and rig sets nodes Args: - instance (str): instance + instance (pyblish.api.Instance): instance Returns: tuple: 2-tuple of list of objectsets, @@ -249,11 +244,10 @@ class ValidateSkeletonRigContents(ValidateRigContents): """Get the target objectsets and rig sets nodes Args: - instance (str): instance + instance (pyblish.api.Instance): instance Returns: - tuple: 2-tuple of list of objectsets, - list of rig sets nodes + tuple: 2-tuple of list of objectsets, list of rig sets nodes """ objectsets = ["skeletonMesh_SET"] skeleton_mesh_nodes = instance.data.get("skeleton_mesh", []) From d223be1a88e0cbe50f5df290df63836a812e7783 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 01:01:39 +0100 Subject: [PATCH 086/150] Remove plugins for `instancer` family - there is no `instancer` family? --- .../publish/validate_instancer_content.py | 75 -------- .../validate_instancer_frame_ranges.py | 167 ------------------ 2 files changed, 242 deletions(-) delete mode 100644 client/ayon_core/hosts/maya/plugins/publish/validate_instancer_content.py delete mode 100644 client/ayon_core/hosts/maya/plugins/publish/validate_instancer_frame_ranges.py diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_instancer_content.py b/client/ayon_core/hosts/maya/plugins/publish/validate_instancer_content.py deleted file mode 100644 index 5f57b31868..0000000000 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_instancer_content.py +++ /dev/null @@ -1,75 +0,0 @@ -import maya.cmds as cmds -import pyblish.api - -from ayon_core.hosts.maya.api import lib -from ayon_core.pipeline.publish import PublishValidationError - - -class ValidateInstancerContent(pyblish.api.InstancePlugin): - """Validates that all meshes in the instance have object IDs. - - This skips a check on intermediate objects because we consider them - not important. - """ - order = pyblish.api.ValidatorOrder - label = 'Instancer Content' - families = ['instancer'] - - def process(self, instance): - - error = False - members = instance.data['setMembers'] - export_members = instance.data['exactExportMembers'] - - self.log.debug("Contents {0}".format(members)) - - if not len(members) == len(cmds.ls(members, type="instancer")): - self.log.error("Instancer can only contain instancers") - error = True - - # TODO: Implement better check for particles are cached - if not cmds.ls(export_members, type="nucleus"): - self.log.error("Instancer must have a connected nucleus") - error = True - - if not cmds.ls(export_members, type="cacheFile"): - self.log.error("Instancer must be cached") - error = True - - hidden = self.check_geometry_hidden(export_members) - if not hidden: - error = True - self.log.error("Instancer input geometry must be hidden " - "the scene. Invalid: {0}".format(hidden)) - - # Ensure all in one group - parents = cmds.listRelatives(members, - allParents=True, - fullPath=True) or [] - roots = list(set(cmds.ls(parents, assemblies=True, long=True))) - if len(roots) > 1: - self.log.error("Instancer should all be contained in a single " - "group. Current roots: {0}".format(roots)) - error = True - - if error: - raise PublishValidationError( - "Instancer Content is invalid. See log.") - - def check_geometry_hidden(self, export_members): - - # Ensure all instanced geometry is hidden - shapes = cmds.ls(export_members, - dag=True, - shapes=True, - noIntermediate=True) - meshes = cmds.ls(shapes, type="mesh") - - visible = [node for node in meshes - if lib.is_visible(node, - displayLayer=False, - intermediateObject=False)] - if visible: - return False - - return True diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_instancer_frame_ranges.py b/client/ayon_core/hosts/maya/plugins/publish/validate_instancer_frame_ranges.py deleted file mode 100644 index be6724d7e9..0000000000 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_instancer_frame_ranges.py +++ /dev/null @@ -1,167 +0,0 @@ -import os -import re - -import pyblish.api - -from ayon_core.pipeline.publish import PublishValidationError - - -def is_cache_resource(resource): - """Return whether resource is a cacheFile resource""" - required = set(["maya", "node", "cacheFile"]) - tags = resource.get("tags", []) - return required.issubset(tags) - - -def valdidate_files(files): - for f in files: - assert os.path.exists(f) - assert f.endswith(".mcx") or f.endswith(".mcc") - - return True - - -def filter_ticks(files): - tick_files = set() - ticks = set() - for path in files: - match = re.match(".+Tick([0-9]+).mcx$", os.path.basename(path)) - if match: - tick_files.add(path) - num = match.group(1) - ticks.add(int(num)) - - return tick_files, ticks - - -class ValidateInstancerFrameRanges(pyblish.api.InstancePlugin): - """Validates all instancer particle systems are cached correctly. - - This means they should have the files/frames as required by the start-end - frame (including handles). - - This also checks the files exist and checks the "ticks" (substeps) files. - - """ - order = pyblish.api.ValidatorOrder - label = 'Instancer Cache Frame Ranges' - families = ['instancer'] - - @classmethod - def get_invalid(cls, instance): - - import pyseq - - start_frame = instance.data.get("frameStart", 0) - end_frame = instance.data.get("frameEnd", 0) - required = range(int(start_frame), int(end_frame) + 1) - - invalid = list() - resources = instance.data.get("resources", []) - - for resource in resources: - if not is_cache_resource(resource): - continue - - node = resource['node'] - all_files = resource['files'][:] - all_lookup = set(all_files) - - # The first file is usually the .xml description file. - xml = all_files.pop(0) - assert xml.endswith(".xml") - - # Ensure all files exist (including ticks) - # The remainder file paths should be the .mcx or .mcc files - valdidate_files(all_files) - - # Maya particle caches support substeps by saving out additional - # files that end with a Tick60.mcx, Tick120.mcx, etc. suffix. - # To avoid `pyseq` getting confused we filter those out and then - # for each file (except the last frame) check that at least all - # ticks exist. - - tick_files, ticks = filter_ticks(all_files) - if tick_files: - files = [f for f in all_files if f not in tick_files] - else: - files = all_files - - sequences = pyseq.get_sequences(files) - if len(sequences) != 1: - invalid.append(node) - cls.log.warning("More than one sequence found? " - "{0} {1}".format(node, files)) - cls.log.warning("Found caches: {0}".format(sequences)) - continue - - sequence = sequences[0] - cls.log.debug("Found sequence: {0}".format(sequence)) - - start = sequence.start() - end = sequence.end() - - if start > start_frame or end < end_frame: - invalid.append(node) - cls.log.warning("Sequence does not have enough " - "frames: {0}-{1} (requires: {2}-{3})" - "".format(start, end, - start_frame, - end_frame)) - continue - - # Ensure all frames are present - missing = set(sequence.missing()) - if missing: - required_missing = [x for x in required if x in missing] - if required_missing: - invalid.append(node) - cls.log.warning("Sequence is missing required frames: " - "{0}".format(required_missing)) - continue - - # Ensure all tick files (substep) exist for the files in the folder - # for the frames required by the time range. - if ticks: - ticks = list(sorted(ticks)) - cls.log.debug("Found ticks: {0} " - "(substeps: {1})".format(ticks, len(ticks))) - - # Check all frames except the last since we don't - # require subframes after our time range. - tick_check_frames = set(required[:-1]) - - # Check all frames - for item in sequence: - frame = item.frame - if not frame: - invalid.append(node) - cls.log.error("Path is not a frame in sequence: " - "{0}".format(item)) - continue - - # Not required for our time range - if frame not in tick_check_frames: - continue - - path = item.path - for num in ticks: - base, ext = os.path.splitext(path) - tick_file = base + "Tick{0}".format(num) + ext - if tick_file not in all_lookup: - invalid.append(node) - cls.log.warning("Tick file found that is not " - "in cache query filenames: " - "{0}".format(tick_file)) - - return invalid - - def process(self, instance): - - invalid = self.get_invalid(instance) - - if invalid: - self.log.error("Invalid nodes: {0}".format(invalid)) - raise PublishValidationError( - ("Invalid particle caches in instance. " - "See logs for details.")) From 065c11526e81f3018c33b0fb00ecd1b4fe17e5f4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 01:07:26 +0100 Subject: [PATCH 087/150] Fix redeclared `order` defined above without usage --- .../hosts/photoshop/plugins/publish/collect_auto_image.py | 1 - .../photoshop/plugins/publish/collect_auto_image_refresh.py | 1 - 2 files changed, 2 deletions(-) diff --git a/client/ayon_core/hosts/photoshop/plugins/publish/collect_auto_image.py b/client/ayon_core/hosts/photoshop/plugins/publish/collect_auto_image.py index b488ab364d..adbe02eb74 100644 --- a/client/ayon_core/hosts/photoshop/plugins/publish/collect_auto_image.py +++ b/client/ayon_core/hosts/photoshop/plugins/publish/collect_auto_image.py @@ -9,7 +9,6 @@ class CollectAutoImage(pyblish.api.ContextPlugin): """ label = "Collect Auto Image" - order = pyblish.api.CollectorOrder hosts = ["photoshop"] order = pyblish.api.CollectorOrder + 0.2 diff --git a/client/ayon_core/hosts/photoshop/plugins/publish/collect_auto_image_refresh.py b/client/ayon_core/hosts/photoshop/plugins/publish/collect_auto_image_refresh.py index 0585f4f226..7a5f297c89 100644 --- a/client/ayon_core/hosts/photoshop/plugins/publish/collect_auto_image_refresh.py +++ b/client/ayon_core/hosts/photoshop/plugins/publish/collect_auto_image_refresh.py @@ -8,7 +8,6 @@ class CollectAutoImageRefresh(pyblish.api.ContextPlugin): """ label = "Collect Auto Image Refresh" - order = pyblish.api.CollectorOrder hosts = ["photoshop"] order = pyblish.api.CollectorOrder + 0.2 From 81bdf3df3d804fda1e6bc5c3d69af81abf388302 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 01:09:01 +0100 Subject: [PATCH 088/150] Fix unreachable code - string wasn't actually joined --- .../hosts/maya/plugins/publish/collect_multiverse_look.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/collect_multiverse_look.py b/client/ayon_core/hosts/maya/plugins/publish/collect_multiverse_look.py index 31c0d0eaa1..83e743c92e 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/collect_multiverse_look.py +++ b/client/ayon_core/hosts/maya/plugins/publish/collect_multiverse_look.py @@ -40,9 +40,11 @@ class _NodeTypeAttrib(object): return "{}.{}".format(node, self.colour_space) def __str__(self): - return "_NodeTypeAttrib(name={}, fname={}, " - "computed_fname={}, colour_space={})".format( - self.name, self.fname, self.computed_fname, self.colour_space) + return ( + "_NodeTypeAttrib(name={}, fname={}, " + "computed_fname={}, colour_space={})".format( + self.name, self.fname, self.computed_fname, self.colour_space) + ) NODETYPES = { From 7c551c832d9face99d34d76564231ee338d3e2f6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 01:09:25 +0100 Subject: [PATCH 089/150] Fix indentations --- .../hosts/harmony/plugins/load/load_template_workfile.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 7bf634f00c..c7132ce373 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,11 +50,11 @@ class ImportTemplateLoader(load.LoaderPlugin): self.__class__.__name__ ) - def update(self, container, context): - pass + def update(self, container, context): + pass - def remove(self, container): - pass + def remove(self, container): + pass class ImportWorkfileLoader(ImportTemplateLoader): From 3d4ae8838d203d694fdfcf2fa1cb8952d5f7081f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 01:10:32 +0100 Subject: [PATCH 090/150] Remove type hint since docstring takes care of that --- client/ayon_core/hosts/houdini/api/plugin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_core/hosts/houdini/api/plugin.py b/client/ayon_core/hosts/houdini/api/plugin.py index 0809f4e566..b33d0fe297 100644 --- a/client/ayon_core/hosts/houdini/api/plugin.py +++ b/client/ayon_core/hosts/houdini/api/plugin.py @@ -147,7 +147,6 @@ class HoudiniCreatorBase(object): def create_instance_node( folder_path, node_name, parent, node_type="geometry" ): - # type: (str, str, str) -> hou.Node """Create node representing instance. Arguments: From 6adc2c9c26eb7a93fa3a30263392ab027bb2ff2a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 01:10:57 +0100 Subject: [PATCH 091/150] Fix type hint having too few arguments --- client/ayon_core/hosts/houdini/plugins/create/create_hda.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_hda.py b/client/ayon_core/hosts/houdini/plugins/create/create_hda.py index c16c95a270..6a2a41d1d0 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_hda.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_hda.py @@ -16,7 +16,7 @@ class CreateHDA(plugin.HoudiniCreator): maintain_selection = False def _check_existing(self, folder_path, product_name): - # type: (str) -> bool + # type: (str, str) -> bool """Check if existing product name versions already exists.""" # Get all products of the current folder project_name = self.project_name From 16b9dbbebe03a6aec56d5b75b2506ae16148d761 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 01:14:54 +0100 Subject: [PATCH 092/150] Fix typos --- .../plugins/publish/collect_default_deadline_server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py b/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py index 8123409052..b7ca227b01 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py @@ -9,11 +9,11 @@ class CollectDefaultDeadlineServer(pyblish.api.ContextPlugin): DL webservice addresses must be configured first in System Settings for project settings enum to work. - Default webservice could be overriden by + Default webservice could be overridden by `project_settings/deadline/deadline_servers`. Currently only single url is expected. - This url could be overriden by some hosts directly on instances with + This url could be overridden by some hosts directly on instances with `CollectDeadlineServerFromInstance`. """ From a68fa54661eba181eb1adc44bb7fd8df2e85ae26 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 01:15:12 +0100 Subject: [PATCH 093/150] Use set literal --- .../plugins/publish/validate_expected_and_rendered_files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/validate_expected_and_rendered_files.py b/client/ayon_core/modules/deadline/plugins/publish/validate_expected_and_rendered_files.py index a666c5c2dc..6263526d5c 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/validate_expected_and_rendered_files.py +++ b/client/ayon_core/modules/deadline/plugins/publish/validate_expected_and_rendered_files.py @@ -149,7 +149,7 @@ class ValidateExpectedFiles(pyblish.api.InstancePlugin): """ # no frames in file name at all, eg 'renderCompositingMain.withLut.mov' if not frame_placeholder: - return set([file_name_template]) + return {file_name_template} real_expected_rendered = set() src_padding_exp = "%0{}d".format(len(frame_placeholder)) From 3506c07a3c17a52415025590f28fc0065ed110cf Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 01:16:36 +0100 Subject: [PATCH 094/150] Fix docstring --- .../plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py b/client/ayon_core/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py index 9641c16d20..f146aef7b4 100644 --- a/client/ayon_core/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py +++ b/client/ayon_core/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py @@ -404,7 +404,7 @@ class OpenPypeTileAssembler(DeadlinePlugin): Args: output_width (int): Width of output image. output_height (int): Height of output image. - tiles_info (list): List of tile items, each item must be + tile_info (list): List of tile items, each item must be dictionary with `filepath`, `pos_x` and `pos_y` keys representing path to file and x, y coordinates on output image where top-left point of tile item should start. From de481e2e359ae4881f6f0d9def6a656d5bced766 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 01:17:32 +0100 Subject: [PATCH 095/150] Remove invalid type hint missing the optional argument - docstring should take care of this --- client/ayon_core/modules/deadline/deadline_module.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_core/modules/deadline/deadline_module.py b/client/ayon_core/modules/deadline/deadline_module.py index d2f0e263d4..c0ba83477e 100644 --- a/client/ayon_core/modules/deadline/deadline_module.py +++ b/client/ayon_core/modules/deadline/deadline_module.py @@ -46,7 +46,6 @@ class DeadlineModule(AYONAddon, IPluginPaths): @staticmethod def get_deadline_pools(webservice, log=None): - # type: (str) -> list """Get pools from Deadline. Args: webservice (str): Server url. From 0c96c8f76a2babb2d703154efac2df435eda0820 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 01:26:36 +0100 Subject: [PATCH 096/150] Fix `fields` for getting last versions --- client/ayon_core/tools/sceneinventory/switch_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/tools/sceneinventory/switch_dialog/dialog.py b/client/ayon_core/tools/sceneinventory/switch_dialog/dialog.py index 823a4a6631..4977ad13c6 100644 --- a/client/ayon_core/tools/sceneinventory/switch_dialog/dialog.py +++ b/client/ayon_core/tools/sceneinventory/switch_dialog/dialog.py @@ -240,7 +240,7 @@ class SwitchAssetDialog(QtWidgets.QDialog): return ayon_api.get_last_versions( project_name, product_ids, - fields={"id", "folderId", "version"} + fields={"id", "productId", "version"} ) def _on_show_timer(self): From acf31d0c605f5eab2634f8af2715aeae1b63aed3 Mon Sep 17 00:00:00 2001 From: moonyuet Date: Wed, 20 Mar 2024 08:59:59 +0100 Subject: [PATCH 097/150] add missing OptionalPyblishPluginMixin to some validators --- .../plugins/publish/validate_arnold_scene_source_cbid.py | 8 ++++++-- .../publish/validate_current_renderlayer_renderable.py | 9 ++++++--- .../hosts/maya/plugins/publish/validate_loaded_plugin.py | 6 ++++-- .../maya/plugins/publish/validate_look_shading_group.py | 6 ++++-- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_arnold_scene_source_cbid.py b/client/ayon_core/hosts/maya/plugins/publish/validate_arnold_scene_source_cbid.py index 8f8b1eb1c2..8bcd272d01 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_arnold_scene_source_cbid.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_arnold_scene_source_cbid.py @@ -1,11 +1,15 @@ import pyblish.api from ayon_core.hosts.maya.api import lib from ayon_core.pipeline.publish import ( - ValidateContentsOrder, PublishValidationError, RepairAction + ValidateContentsOrder, + PublishValidationError, + RepairAction, + OptionalPyblishPluginMixin ) -class ValidateArnoldSceneSourceCbid(pyblish.api.InstancePlugin): +class ValidateArnoldSceneSourceCbid(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validate Arnold Scene Source Cbid. It is required for the proxy and content nodes to share the same cbid. diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py b/client/ayon_core/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py index 39a9707984..4590c53931 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py @@ -1,10 +1,13 @@ import pyblish.api from maya import cmds -from ayon_core.pipeline.publish import context_plugin_should_run +from ayon_core.pipeline.publish import ( + context_plugin_should_run, + OptionalPyblishPluginMixin +) - -class ValidateCurrentRenderLayerIsRenderable(pyblish.api.ContextPlugin): +class ValidateCurrentRenderLayerIsRenderable(pyblish.api.ContextPlugin, + OptionalPyblishPluginMixin): """Validate if current render layer has a renderable camera There is a bug in Redshift which occurs when the current render layer diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_loaded_plugin.py b/client/ayon_core/hosts/maya/plugins/publish/validate_loaded_plugin.py index 8fde7d5f67..d155a9565a 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_loaded_plugin.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_loaded_plugin.py @@ -4,11 +4,13 @@ import maya.cmds as cmds from ayon_core.pipeline.publish import ( RepairContextAction, - PublishValidationError + PublishValidationError, + OptionalPyblishPluginMixin ) -class ValidateLoadedPlugin(pyblish.api.ContextPlugin): +class ValidateLoadedPlugin(pyblish.api.ContextPlugin, + OptionalPyblishPluginMixin): """Ensure there are no unauthorized loaded plugins""" label = "Loaded Plugin" diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_look_shading_group.py b/client/ayon_core/hosts/maya/plugins/publish/validate_look_shading_group.py index b1f2b3f307..7d249a6021 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_look_shading_group.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_look_shading_group.py @@ -5,11 +5,13 @@ import ayon_core.hosts.maya.api.action from ayon_core.pipeline.publish import ( RepairAction, ValidateContentsOrder, - PublishValidationError + PublishValidationError, + OptionalPyblishPluginMixin ) -class ValidateShadingEngine(pyblish.api.InstancePlugin): +class ValidateShadingEngine(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validate all shading engines are named after the surface material. Shading engines should be named "{surface_shader}SG" From f1f2eeb2da3f505d727f71f58182a62b24ec4733 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 20 Mar 2024 09:27:33 +0100 Subject: [PATCH 098/150] fix last versions getters for older ayon api --- client/ayon_core/lib/ayon_connection.py | 107 ++++++++++++++++++++++-- 1 file changed, 102 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/lib/ayon_connection.py b/client/ayon_core/lib/ayon_connection.py index 5ee654943f..1132d77aaa 100644 --- a/client/ayon_core/lib/ayon_connection.py +++ b/client/ayon_core/lib/ayon_connection.py @@ -10,6 +10,87 @@ class _Cache: initialized = False +def _new_get_last_versions( + self, + project_name, + product_ids, + active=True, + fields=None, + own_attributes=False +): + """Query last version entities by product ids. + + Args: + project_name (str): Project where to look for representation. + product_ids (Iterable[str]): Product ids. + active (Optional[bool]): Receive active/inactive entities. + Both are returned when 'None' is passed. + fields (Optional[Iterable[str]]): fields to be queried + for representations. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. + + Returns: + dict[str, dict[str, Any]]: Last versions by product id. + + """ + if fields: + fields = set(fields) + fields.add("productId") + + versions = self.get_versions( + project_name, + product_ids=product_ids, + latest=True, + hero=False, + active=active, + fields=fields, + own_attributes=own_attributes + ) + return { + version["productId"]: version + for version in versions + } + + +def _new_get_last_version_by_product_id( + self, + project_name, + product_id, + active=True, + fields=None, + own_attributes=False +): + """Query last version entity by product id. + + Args: + project_name (str): Project where to look for representation. + product_id (str): Product id. + active (Optional[bool]): Receive active/inactive entities. + Both are returned when 'None' is passed. + fields (Optional[Iterable[str]]): fields to be queried + for representations. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. + + Returns: + Union[dict[str, Any], None]: Queried version entity or None. + + """ + versions = self.get_versions( + project_name, + product_ids=[product_id], + latest=True, + hero=False, + active=active, + fields=fields, + own_attributes=own_attributes + ) + for version in versions: + return version + return None + + def _new_get_last_version_by_product_name( self, project_name, @@ -73,9 +154,15 @@ def initialize_ayon_connection(force=False): semver.VersionInfo.parse(ayon_api.__version__).to_tuple() ) # TODO remove mokey patching after when AYON api is safely updated - fix_last_version_by_product_name = ayon_api_version < (1, 0, 2) + fix_before_1_0_2 = ayon_api_version < (1, 0, 2) # Monkey patching to fix 'get_last_version_by_product_name' - if fix_last_version_by_product_name: + if fix_before_1_0_2: + ayon_api.ServerAPI.get_last_versions = ( + _new_get_last_versions + ) + ayon_api.ServerAPI.get_last_version_by_product_id = ( + _new_get_last_version_by_product_id + ) ayon_api.ServerAPI.get_last_version_by_product_name = ( _new_get_last_version_by_product_name ) @@ -85,12 +172,22 @@ def initialize_ayon_connection(force=False): if ayon_api.is_connection_created(): con = ayon_api.get_server_api_connection() # Monkey patching to fix 'get_last_version_by_product_name' - if fix_last_version_by_product_name: - def _con_wrapper(*args, **kwargs): + if fix_before_1_0_2: + def _lvs_wrapper(*args, **kwargs): + return _new_get_last_versions( + con, *args, **kwargs + ) + def _lv_by_pi_wrapper(*args, **kwargs): + return _new_get_last_version_by_product_id( + con, *args, **kwargs + ) + def _lv_by_pn_wrapper(*args, **kwargs): return _new_get_last_version_by_product_name( con, *args, **kwargs ) - con.get_last_version_by_product_name = _con_wrapper + con.get_last_versions = _lvs_wrapper + con.get_last_version_by_product_id = _lv_by_pi_wrapper + con.get_last_version_by_product_name = _lv_by_pn_wrapper con.set_site_id(site_id) con.set_client_version(version) else: From 7f83a62192bb8f52018b570105f984804d853011 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 09:56:23 +0100 Subject: [PATCH 099/150] Revert "Fix update to latest - it now updates to latest instead of hero version" This reverts commit 7b5b2a980cf68afd3fa5e8051b0e15d9f88d913f. --- client/ayon_core/pipeline/load/utils.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/client/ayon_core/pipeline/load/utils.py b/client/ayon_core/pipeline/load/utils.py index 66ead70c14..f3d39800cd 100644 --- a/client/ayon_core/pipeline/load/utils.py +++ b/client/ayon_core/pipeline/load/utils.py @@ -479,15 +479,10 @@ def update_container(container, version=-1): project_name, current_version["productId"] ) elif version == -1: - # TODO: Use `ayon_api.get_last_version_by_product_id` when fixed - # to not return hero versions instead of the last version - new_version = next(ayon_api.get_versions( - project_name, - product_ids=[current_version["productId"]], - standard=True, - hero=False, - latest=True, - ), None) + new_version = ayon_api.get_last_version_by_product_id( + project_name, current_version["productId"] + ) + else: new_version = ayon_api.get_version_by_name( project_name, version, current_version["productId"] From a71ea3a3e3318d69811dd00a2b257ceeb5ebc30e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 09:58:07 +0100 Subject: [PATCH 100/150] Use more specific api call --- client/ayon_core/tools/sceneinventory/model.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/client/ayon_core/tools/sceneinventory/model.py b/client/ayon_core/tools/sceneinventory/model.py index 92591206fb..b9b18b339e 100644 --- a/client/ayon_core/tools/sceneinventory/model.py +++ b/client/ayon_core/tools/sceneinventory/model.py @@ -293,14 +293,11 @@ class InventoryModel(TreeModel): # Query the highest available version so the model can know # whether current version is currently up-to-date. - highest_versions = ayon_api.get_versions( + highest_versions = ayon_api.get_last_versions( project_name, product_ids={ group["version"]["productId"] for group in grouped.values() }, - latest=True, - standard=True, - hero=False, fields=["productId", "version"] ) highest_version_by_product_id = { From bb5f83f6c4c49e1bf7086debc052d2ffe6e5bcb4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 10:15:01 +0100 Subject: [PATCH 101/150] Refactor `ayon_api.get_last_versions` already returns `dict` by `productId` --- client/ayon_core/tools/sceneinventory/model.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/tools/sceneinventory/model.py b/client/ayon_core/tools/sceneinventory/model.py index b9b18b339e..9c4d080470 100644 --- a/client/ayon_core/tools/sceneinventory/model.py +++ b/client/ayon_core/tools/sceneinventory/model.py @@ -293,16 +293,17 @@ class InventoryModel(TreeModel): # Query the highest available version so the model can know # whether current version is currently up-to-date. - highest_versions = ayon_api.get_last_versions( + highest_version_by_product_id = ayon_api.get_last_versions( project_name, product_ids={ group["version"]["productId"] for group in grouped.values() }, fields=["productId", "version"] ) + # Map value to `version` key highest_version_by_product_id = { - version["productId"]: version["version"] - for version in highest_versions + product_id: version["version"] + for product_id, version in highest_version_by_product_id.items() } for repre_id, group_dict in sorted(grouped.items()): From 88e81cca22a257e20330c5ba96092a80387d5492 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 10:40:42 +0100 Subject: [PATCH 102/150] Remove unused import --- client/ayon_core/tools/sceneinventory/model.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/client/ayon_core/tools/sceneinventory/model.py b/client/ayon_core/tools/sceneinventory/model.py index 9c4d080470..5f8b1ee81c 100644 --- a/client/ayon_core/tools/sceneinventory/model.py +++ b/client/ayon_core/tools/sceneinventory/model.py @@ -8,10 +8,7 @@ import ayon_api from qtpy import QtCore, QtGui import qtawesome -from ayon_core.pipeline import ( - get_current_project_name, - HeroVersionType, -) +from ayon_core.pipeline import get_current_project_name from ayon_core.style import get_default_entity_icon_color from ayon_core.tools.utils import get_qt_icon from ayon_core.tools.utils.models import TreeModel, Item From 19ba11409b96e71db45baed0e38ad695d31147e5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 17:12:37 +0100 Subject: [PATCH 103/150] Tweak clarity of the message that is about the workfile being unsaved - not being 'empty'. --- .../hosts/blender/plugins/publish/validate_file_saved.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/blender/plugins/publish/validate_file_saved.py b/client/ayon_core/hosts/blender/plugins/publish/validate_file_saved.py index 6a053eb47b..aa73525555 100644 --- a/client/ayon_core/hosts/blender/plugins/publish/validate_file_saved.py +++ b/client/ayon_core/hosts/blender/plugins/publish/validate_file_saved.py @@ -37,7 +37,8 @@ class ValidateFileSaved(pyblish.api.ContextPlugin, if not context.data["currentFile"]: # File has not been saved at all and has no filename raise PublishValidationError( - "Current file is empty. Save the file before continuing." + "Current workfile has not been saved yet.\n" + "Save the workfile before continuing." ) # Do not validate workfile has unsaved changes if only instances From aea9716f717300abeb956d56d66d1830d62b0442 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 17:23:38 +0100 Subject: [PATCH 104/150] Blender: Validate Transform Zero - improve validation report + add repair action --- .../publish/validate_transform_zero.py | 54 +++++++++++++++---- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/client/ayon_core/hosts/blender/plugins/publish/validate_transform_zero.py b/client/ayon_core/hosts/blender/plugins/publish/validate_transform_zero.py index 267eff47e4..4ca1a86de3 100644 --- a/client/ayon_core/hosts/blender/plugins/publish/validate_transform_zero.py +++ b/client/ayon_core/hosts/blender/plugins/publish/validate_transform_zero.py @@ -1,3 +1,4 @@ +import inspect from typing import List import mathutils @@ -5,29 +6,26 @@ import bpy import pyblish.api +from ayon_core.hosts.blender.api import plugin import ayon_core.hosts.blender.api.action from ayon_core.pipeline.publish import ( ValidateContentsOrder, OptionalPyblishPluginMixin, - PublishValidationError + PublishValidationError, + RepairAction ) class ValidateTransformZero(pyblish.api.InstancePlugin, OptionalPyblishPluginMixin): - """Transforms can't have any values - - To solve this issue, try freezing the transforms. So long - as the transforms, rotation and scale values are zero, - you're all good. - - """ + """Transforms can't have any values""" order = ValidateContentsOrder hosts = ["blender"] families = ["model"] label = "Transform Zero" - actions = [ayon_core.hosts.blender.api.action.SelectInvalidAction] + actions = [ayon_core.hosts.blender.api.action.SelectInvalidAction, + RepairAction] _identity = mathutils.Matrix() @@ -51,5 +49,41 @@ class ValidateTransformZero(pyblish.api.InstancePlugin, names = ", ".join(obj.name for obj in invalid) raise PublishValidationError( "Objects found in instance which do not" - f" have transform set to zero: {names}" + f" have transform set to zero: {names}", + description=self.get_description() ) + + @classmethod + def repair(cls, instance): + + invalid = cls.get_invalid(instance) + if not invalid: + return + + context = plugin.create_blender_context( + active=invalid[0], selected=invalid + ) + with bpy.context.temp_override(**context): + # TODO: Preferably this does allow custom pivot point locations + # and if so, this should likely apply to the delta instead + # using `bpy.ops.object.transforms_to_deltas(mode="ALL")` + bpy.ops.object.transform_apply(location=True, + rotation=True, + scale=True) + + def get_description(self): + return inspect.cleandoc( + """## Transforms can't have any values. + + The location, rotation and scale on the transform must be at + the default values. This also goes for the delta transforms. + + To solve this issue, try freezing the transforms: + - `Object` > `Apply` > `All Transforms` + + Using the Repair action directly will do the same. + + So long as the transforms, rotation and scale values are zero, + you're all good. + """ + ) From 16096e8407e87a071d38b4f3cee107f6cc11d8d8 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Sat, 23 Mar 2024 21:29:37 +0100 Subject: [PATCH 105/150] Re-add outputName to _rename_in_representation --- client/ayon_core/plugins/publish/extract_color_transcode.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/plugins/publish/extract_color_transcode.py b/client/ayon_core/plugins/publish/extract_color_transcode.py index b5ddebe05b..1130c575a3 100644 --- a/client/ayon_core/plugins/publish/extract_color_transcode.py +++ b/client/ayon_core/plugins/publish/extract_color_transcode.py @@ -257,6 +257,7 @@ class ExtractOIIOTranscode(publish.Extractor): return new_repre["ext"] = output_extension + new_repre["outputName"] = output_name renamed_files = [] for file_name in files_to_convert: From e5bc1fd3b7ddd2563b7e080fcf96bedf5b57239d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 24 Mar 2024 12:04:33 +0100 Subject: [PATCH 106/150] Blender: Add model publisher validator to ensure uv set is `map1` (Disabled by default) --- .../plugins/publish/validate_model_uv_map1.py | 94 +++++++++++++++++++ .../server/settings/publish_plugins.py | 9 ++ 2 files changed, 103 insertions(+) create mode 100644 client/ayon_core/hosts/blender/plugins/publish/validate_model_uv_map1.py diff --git a/client/ayon_core/hosts/blender/plugins/publish/validate_model_uv_map1.py b/client/ayon_core/hosts/blender/plugins/publish/validate_model_uv_map1.py new file mode 100644 index 0000000000..2315d54a2c --- /dev/null +++ b/client/ayon_core/hosts/blender/plugins/publish/validate_model_uv_map1.py @@ -0,0 +1,94 @@ +import inspect +from typing import List + +import bpy + +import pyblish.api + +from ayon_core.pipeline.publish import ( + ValidateContentsOrder, + OptionalPyblishPluginMixin, + PublishValidationError, + RepairAction +) +import ayon_core.hosts.blender.api.action + + +class ValidateModelMeshUvMap1( + pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin, +): + """Validate model mesh uvs are named `map1`. + + This is solely to get them to work nicely for the Maya pipeline. + """ + + order = ValidateContentsOrder + hosts = ["blender"] + families = ["model"] + label = "Mesh UVs named map1" + actions = [ayon_core.hosts.blender.api.action.SelectInvalidAction, + RepairAction] + optional = True + enabled = False + + @classmethod + def get_invalid(cls, instance) -> List: + + invalid = [] + for obj in instance: + if obj.mode != "OBJECT": + cls.log.warning( + f"Mesh object {obj.name} should be in 'OBJECT' mode" + " to be properly checked." + ) + + obj_data = obj.data + if isinstance(obj_data, bpy.types.Mesh): + mesh = obj_data + + # Ignore mesh without UVs + if not mesh.uv_layers: + continue + + # If mesh has map1 all is ok + if mesh.uv_layers.get("map1"): + continue + + cls.log.warning( + f"Mesh object {obj.name} should be in 'OBJECT' mode" + " to be properly checked." + ) + invalid.append(obj) + + return invalid + + @classmethod + def repair(cls, instance): + for obj in cls.get_invalid(instance): + mesh = obj.data + + # Rename the first UV set to map1 + mesh.uv_layers[0].name = "map1" + + def process(self, instance): + if not self.is_active(instance.data): + return + + invalid = self.get_invalid(instance) + if invalid: + raise PublishValidationError( + f"Meshes found in instance without valid UV's: {invalid}", + description=self.get_description() + ) + + def get_description(self): + return inspect.cleandoc( + """## Meshes must have `map1` uv set + + To accompany a better Maya-focused pipeline with Alembics it is + expected that a Mesh has a `map1` UV set. Blender defaults to + a UV set named `UVMap` and thus needs to be renamed. + + """ + ) diff --git a/server_addon/blender/server/settings/publish_plugins.py b/server_addon/blender/server/settings/publish_plugins.py index 79c489d080..c742fdc5bd 100644 --- a/server_addon/blender/server/settings/publish_plugins.py +++ b/server_addon/blender/server/settings/publish_plugins.py @@ -89,6 +89,10 @@ class PublishPuginsModel(BaseSettingsModel): default_factory=ValidatePluginModel, title="Validate Mesh No Negative Scale" ) + ValidateModelMeshUvMap1: ValidatePluginModel = SettingsField( + default_factory=ValidatePluginModel, + title="Validate Model Mesh Has UV map named map1" + ) ValidateTransformZero: ValidatePluginModel = SettingsField( default_factory=ValidatePluginModel, title="Validate Transform Zero" @@ -181,6 +185,11 @@ DEFAULT_BLENDER_PUBLISH_SETTINGS = { "optional": False, "active": True }, + "ValidateModelMeshUvMap1": { + "enabled": False, + "optional": True, + "active": True + }, "ValidateTransformZero": { "enabled": False, "optional": True, From cc82af1c3363ef98ebba09de9e0b30114ef4b0e6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 24 Mar 2024 12:04:55 +0100 Subject: [PATCH 107/150] Bump addon version --- server_addon/blender/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/blender/server/version.py b/server_addon/blender/server/version.py index f1380eede2..9cb17e7976 100644 --- a/server_addon/blender/server/version.py +++ b/server_addon/blender/server/version.py @@ -1 +1 @@ -__version__ = "0.1.7" +__version__ = "0.1.8" From 085e2aa82679ae9ba4962f8b01342e50fa7b98b4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 24 Mar 2024 12:29:30 +0100 Subject: [PATCH 108/150] Remove unused import --- .../ayon_core/hosts/fusion/plugins/create/create_image_saver.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_core/hosts/fusion/plugins/create/create_image_saver.py b/client/ayon_core/hosts/fusion/plugins/create/create_image_saver.py index 8110898ae9..729843d078 100644 --- a/client/ayon_core/hosts/fusion/plugins/create/create_image_saver.py +++ b/client/ayon_core/hosts/fusion/plugins/create/create_image_saver.py @@ -1,7 +1,6 @@ from ayon_core.lib import NumberDef from ayon_core.hosts.fusion.api.plugin import GenericCreateSaver -from ayon_core.hosts.fusion.api import get_current_comp class CreateImageSaver(GenericCreateSaver): From 5b0826a16da2678ca26581dc6df933593550bed4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 24 Mar 2024 12:40:26 +0100 Subject: [PATCH 109/150] Fix repair when objects are not currently selected --- .../publish/validate_transform_zero.py | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/client/ayon_core/hosts/blender/plugins/publish/validate_transform_zero.py b/client/ayon_core/hosts/blender/plugins/publish/validate_transform_zero.py index 4ca1a86de3..465ec15d7b 100644 --- a/client/ayon_core/hosts/blender/plugins/publish/validate_transform_zero.py +++ b/client/ayon_core/hosts/blender/plugins/publish/validate_transform_zero.py @@ -6,7 +6,7 @@ import bpy import pyblish.api -from ayon_core.hosts.blender.api import plugin +from ayon_core.hosts.blender.api import plugin, lib import ayon_core.hosts.blender.api.action from ayon_core.pipeline.publish import ( ValidateContentsOrder, @@ -63,13 +63,18 @@ class ValidateTransformZero(pyblish.api.InstancePlugin, context = plugin.create_blender_context( active=invalid[0], selected=invalid ) - with bpy.context.temp_override(**context): - # TODO: Preferably this does allow custom pivot point locations - # and if so, this should likely apply to the delta instead - # using `bpy.ops.object.transforms_to_deltas(mode="ALL")` - bpy.ops.object.transform_apply(location=True, - rotation=True, - scale=True) + with lib.maintained_selection(): + with bpy.context.temp_override(**context): + plugin.deselect_all() + for obj in invalid: + obj.select_set(True) + + # TODO: Preferably this does allow custom pivot point locations + # and if so, this should likely apply to the delta instead + # using `bpy.ops.object.transforms_to_deltas(mode="ALL")` + bpy.ops.object.transform_apply(location=True, + rotation=True, + scale=True) def get_description(self): return inspect.cleandoc( From 83fa71982a109cc41460b8ca510538a5dfa67c5e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 24 Mar 2024 19:21:04 +0100 Subject: [PATCH 110/150] Update client/ayon_core/hosts/blender/plugins/publish/validate_model_uv_map1.py --- .../hosts/blender/plugins/publish/validate_model_uv_map1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/blender/plugins/publish/validate_model_uv_map1.py b/client/ayon_core/hosts/blender/plugins/publish/validate_model_uv_map1.py index 2315d54a2c..752bc5fa58 100644 --- a/client/ayon_core/hosts/blender/plugins/publish/validate_model_uv_map1.py +++ b/client/ayon_core/hosts/blender/plugins/publish/validate_model_uv_map1.py @@ -84,7 +84,7 @@ class ValidateModelMeshUvMap1( def get_description(self): return inspect.cleandoc( - """## Meshes must have `map1` uv set + """## Meshes must have map1 uv set To accompany a better Maya-focused pipeline with Alembics it is expected that a Mesh has a `map1` UV set. Blender defaults to From da31aa2359fad434f204e7e72bc5790424cd9517 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Mon, 25 Mar 2024 11:08:47 +0100 Subject: [PATCH 111/150] Update cli_commands.py import qt related stuff only when using qt --- client/ayon_core/cli_commands.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/cli_commands.py b/client/ayon_core/cli_commands.py index 4335a3f2d9..bc0a22382c 100644 --- a/client/ayon_core/cli_commands.py +++ b/client/ayon_core/cli_commands.py @@ -67,8 +67,6 @@ class Commands: install_ayon_plugins, get_global_context, ) - from ayon_core.tools.utils.host_tools import show_publish - from ayon_core.tools.utils.lib import qt_app_context # Register target and host import pyblish.api @@ -134,6 +132,8 @@ class Commands: print(plugin) if gui: + from ayon_core.tools.utils.host_tools import show_publish + from ayon_core.tools.utils.lib import qt_app_context with qt_app_context(): show_publish() else: From 55ad0586dccb15331701299526f9733242e49e12 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 11:17:10 +0100 Subject: [PATCH 112/150] use AYON in docstrings, comments and readme --- client/ayon_core/hosts/flame/api/__init__.py | 2 +- client/ayon_core/hosts/flame/api/constants.py | 6 +++--- .../ayon_core/hosts/flame/api/scripts/wiretap_com.py | 2 +- client/ayon_core/hosts/flame/api/utils.py | 2 +- .../ayon_core/hosts/flame/startup/openpype_in_flame.py | 2 +- client/ayon_core/hosts/fusion/api/menu.py | 2 +- client/ayon_core/hosts/fusion/api/pipeline.py | 10 +++++----- .../hosts/fusion/deploy/MenuScripts/README.md | 4 ++-- .../hosts/fusion/hooks/pre_fusion_profile_hook.py | 2 +- .../ayon_core/hosts/fusion/hooks/pre_fusion_setup.py | 2 +- client/ayon_core/hosts/max/api/pipeline.py | 4 ++-- .../ayon_core/hosts/max/hooks/force_startup_script.py | 2 +- client/ayon_core/hosts/max/hooks/inject_python.py | 2 +- client/ayon_core/hosts/max/startup/startup.ms | 2 +- client/ayon_core/hosts/maya/api/commands.py | 2 +- client/ayon_core/hosts/maya/api/lib.py | 6 +++--- client/ayon_core/hosts/maya/api/lib_rendersetup.py | 2 +- client/ayon_core/hosts/maya/api/render_setup_tools.py | 2 +- .../hosts/maya/plugins/create/convert_legacy.py | 2 +- .../hosts/maya/plugins/publish/extract_look.py | 6 +++--- .../maya/plugins/publish/validate_loaded_plugin.py | 2 +- .../ayon_core/hosts/nuke/startup/custom_write_node.py | 2 +- .../hosts/nuke/startup/frame_setting_for_read_nodes.py | 2 +- client/ayon_core/hosts/unreal/api/pipeline.py | 2 +- client/ayon_core/pipeline/create/legacy_create.py | 2 +- client/ayon_core/pipeline/publish/README.md | 6 +++--- .../pipeline/workfile/workfile_template_builder.py | 2 +- 27 files changed, 41 insertions(+), 41 deletions(-) diff --git a/client/ayon_core/hosts/flame/api/__init__.py b/client/ayon_core/hosts/flame/api/__init__.py index c00ee958b6..e2c5ee154a 100644 --- a/client/ayon_core/hosts/flame/api/__init__.py +++ b/client/ayon_core/hosts/flame/api/__init__.py @@ -1,5 +1,5 @@ """ -OpenPype Autodesk Flame api +AYON Autodesk Flame api """ from .constants import ( COLOR_MAP, diff --git a/client/ayon_core/hosts/flame/api/constants.py b/client/ayon_core/hosts/flame/api/constants.py index 1833031e13..04191c539d 100644 --- a/client/ayon_core/hosts/flame/api/constants.py +++ b/client/ayon_core/hosts/flame/api/constants.py @@ -1,14 +1,14 @@ """ -OpenPype Flame api constances +AYON Flame api constances """ -# OpenPype marker workflow variables +# AYON marker workflow variables MARKER_NAME = "OpenPypeData" MARKER_DURATION = 0 MARKER_COLOR = "cyan" MARKER_PUBLISH_DEFAULT = False -# OpenPype color definitions +# AYON color definitions COLOR_MAP = { "red": (1.0, 0.0, 0.0), "orange": (1.0, 0.5, 0.0), diff --git a/client/ayon_core/hosts/flame/api/scripts/wiretap_com.py b/client/ayon_core/hosts/flame/api/scripts/wiretap_com.py index cffc6ec782..42b9257cbe 100644 --- a/client/ayon_core/hosts/flame/api/scripts/wiretap_com.py +++ b/client/ayon_core/hosts/flame/api/scripts/wiretap_com.py @@ -61,7 +61,7 @@ class WireTapCom(object): def get_launch_args( self, project_name, project_data, user_name, *args, **kwargs): - """Forming launch arguments for OpenPype launcher. + """Forming launch arguments for AYON launcher. Args: project_name (str): name of project diff --git a/client/ayon_core/hosts/flame/api/utils.py b/client/ayon_core/hosts/flame/api/utils.py index 91584456a6..e37555567e 100644 --- a/client/ayon_core/hosts/flame/api/utils.py +++ b/client/ayon_core/hosts/flame/api/utils.py @@ -11,7 +11,7 @@ log = Logger.get_logger(__name__) def _sync_utility_scripts(env=None): """ Synchronizing basic utlility scripts for flame. - To be able to run start OpenPype within Flame we have to copy + To be able to run start AYON within Flame we have to copy all utility_scripts and additional FLAME_SCRIPT_DIR into `/opt/Autodesk/shared/python`. This will be always synchronizing those folders. diff --git a/client/ayon_core/hosts/flame/startup/openpype_in_flame.py b/client/ayon_core/hosts/flame/startup/openpype_in_flame.py index cf0a24ede2..c57c76f73a 100644 --- a/client/ayon_core/hosts/flame/startup/openpype_in_flame.py +++ b/client/ayon_core/hosts/flame/startup/openpype_in_flame.py @@ -12,7 +12,7 @@ from ayon_core.pipeline import ( def openpype_install(): - """Registering OpenPype in context + """Registering AYON in context """ install_host(opfapi) print("Registered host: {}".format(registered_host())) diff --git a/client/ayon_core/hosts/fusion/api/menu.py b/client/ayon_core/hosts/fusion/api/menu.py index 642287eb10..7fdc3cc21c 100644 --- a/client/ayon_core/hosts/fusion/api/menu.py +++ b/client/ayon_core/hosts/fusion/api/menu.py @@ -125,7 +125,7 @@ class OpenPypeMenu(QtWidgets.QWidget): self._pulse = FusionPulse(parent=self) self._pulse.start() - # Detect Fusion events as OpenPype events + # Detect Fusion events as AYON events self._event_handler = FusionEventHandler(parent=self) self._event_handler.start() diff --git a/client/ayon_core/hosts/fusion/api/pipeline.py b/client/ayon_core/hosts/fusion/api/pipeline.py index 3bb66619a9..50157cfae6 100644 --- a/client/ayon_core/hosts/fusion/api/pipeline.py +++ b/client/ayon_core/hosts/fusion/api/pipeline.py @@ -70,7 +70,7 @@ class FusionHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): name = "fusion" def install(self): - """Install fusion-specific functionality of OpenPype. + """Install fusion-specific functionality of AYON. This is where you install menus and register families, data and loaders into fusion. @@ -177,7 +177,7 @@ def on_after_open(event): if any_outdated_containers(): log.warning("Scene has outdated content.") - # Find OpenPype menu to attach to + # Find AYON menu to attach to from . import menu def _on_show_scene_inventory(): @@ -326,9 +326,9 @@ class FusionEventThread(QtCore.QThread): class FusionEventHandler(QtCore.QObject): - """Emits OpenPype events based on Fusion events captured in a QThread. + """Emits AYON events based on Fusion events captured in a QThread. - This will emit the following OpenPype events based on Fusion actions: + This will emit the following AYON events based on Fusion actions: save: Comp_Save, Comp_SaveAs open: Comp_Opened new: Comp_New @@ -374,7 +374,7 @@ class FusionEventHandler(QtCore.QObject): self._event_thread.stop() def _on_event(self, event): - """Handle Fusion events to emit OpenPype events""" + """Handle Fusion events to emit AYON events""" if not event: return diff --git a/client/ayon_core/hosts/fusion/deploy/MenuScripts/README.md b/client/ayon_core/hosts/fusion/deploy/MenuScripts/README.md index f87eaea4a2..e291b8d8f2 100644 --- a/client/ayon_core/hosts/fusion/deploy/MenuScripts/README.md +++ b/client/ayon_core/hosts/fusion/deploy/MenuScripts/README.md @@ -1,6 +1,6 @@ -### OpenPype deploy MenuScripts +### AYON deploy MenuScripts Note that this `MenuScripts` is not an official Fusion folder. -OpenPype only uses this folder in `{fusion}/deploy/` to trigger the OpenPype menu actions. +AYON only uses this folder in `{fusion}/deploy/` to trigger the AYON menu actions. They are used in the actions defined in `.fu` files in `{fusion}/deploy/Config`. \ No newline at end of file diff --git a/client/ayon_core/hosts/fusion/hooks/pre_fusion_profile_hook.py b/client/ayon_core/hosts/fusion/hooks/pre_fusion_profile_hook.py index 5aa2783129..10b1c9c45d 100644 --- a/client/ayon_core/hosts/fusion/hooks/pre_fusion_profile_hook.py +++ b/client/ayon_core/hosts/fusion/hooks/pre_fusion_profile_hook.py @@ -19,7 +19,7 @@ class FusionCopyPrefsPrelaunch(PreLaunchHook): Prepares local Fusion profile directory, copies existing Fusion profile. This also sets FUSION MasterPrefs variable, which is used to apply Master.prefs file to override some Fusion profile settings to: - - enable the OpenPype menu + - enable the AYON menu - force Python 3 over Python 2 - force English interface Master.prefs is defined in openpype/hosts/fusion/deploy/fusion_shared.prefs diff --git a/client/ayon_core/hosts/fusion/hooks/pre_fusion_setup.py b/client/ayon_core/hosts/fusion/hooks/pre_fusion_setup.py index 7eaf2ddc02..5e97ae3de1 100644 --- a/client/ayon_core/hosts/fusion/hooks/pre_fusion_setup.py +++ b/client/ayon_core/hosts/fusion/hooks/pre_fusion_setup.py @@ -13,7 +13,7 @@ from ayon_core.hosts.fusion import ( class FusionPrelaunch(PreLaunchHook): """ - Prepares OpenPype Fusion environment. + Prepares AYON Fusion environment. Requires correct Python home variable to be defined in the environment settings for Fusion to point at a valid Python 3 build for Fusion. Python3 versions that are supported by Fusion: diff --git a/client/ayon_core/hosts/max/api/pipeline.py b/client/ayon_core/hosts/max/api/pipeline.py index 6fd0a501ff..fc42eada7c 100644 --- a/client/ayon_core/hosts/max/api/pipeline.py +++ b/client/ayon_core/hosts/max/api/pipeline.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -"""Pipeline tools for OpenPype Houdini integration.""" +"""Pipeline tools for AYON Houdini integration.""" import os import logging from operator import attrgetter @@ -148,7 +148,7 @@ attributes "OpenPypeContext" def ls() -> list: - """Get all OpenPype instances.""" + """Get all AYON containers.""" objs = rt.objects containers = [ obj for obj in objs diff --git a/client/ayon_core/hosts/max/hooks/force_startup_script.py b/client/ayon_core/hosts/max/hooks/force_startup_script.py index 659be7dfc6..8ccd658e8f 100644 --- a/client/ayon_core/hosts/max/hooks/force_startup_script.py +++ b/client/ayon_core/hosts/max/hooks/force_startup_script.py @@ -6,7 +6,7 @@ from ayon_core.lib.applications import PreLaunchHook, LaunchTypes class ForceStartupScript(PreLaunchHook): - """Inject OpenPype environment to 3ds max. + """Inject AYON environment to 3ds max. Note that this works in combination whit 3dsmax startup script that is translating it back to PYTHONPATH for cases when 3dsmax drops PYTHONPATH diff --git a/client/ayon_core/hosts/max/hooks/inject_python.py b/client/ayon_core/hosts/max/hooks/inject_python.py index 36d53551ba..b1b36e75bd 100644 --- a/client/ayon_core/hosts/max/hooks/inject_python.py +++ b/client/ayon_core/hosts/max/hooks/inject_python.py @@ -5,7 +5,7 @@ from ayon_core.lib.applications import PreLaunchHook, LaunchTypes class InjectPythonPath(PreLaunchHook): - """Inject OpenPype environment to 3dsmax. + """Inject AYON environment to 3dsmax. Note that this works in combination whit 3dsmax startup script that is translating it back to PYTHONPATH for cases when 3dsmax drops PYTHONPATH diff --git a/client/ayon_core/hosts/max/startup/startup.ms b/client/ayon_core/hosts/max/startup/startup.ms index 4c597901f3..2dfe53a6a5 100644 --- a/client/ayon_core/hosts/max/startup/startup.ms +++ b/client/ayon_core/hosts/max/startup/startup.ms @@ -1,4 +1,4 @@ --- OpenPype Init Script +-- AYON Init Script ( local sysPath = dotNetClass "System.IO.Path" local sysDir = dotNetClass "System.IO.Directory" diff --git a/client/ayon_core/hosts/maya/api/commands.py b/client/ayon_core/hosts/maya/api/commands.py index e63800e542..22cf0871e2 100644 --- a/client/ayon_core/hosts/maya/api/commands.py +++ b/client/ayon_core/hosts/maya/api/commands.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -"""OpenPype script commands to be used directly in Maya.""" +"""AYON script commands to be used directly in Maya.""" from maya import cmds from ayon_api import get_project, get_folder_by_path diff --git a/client/ayon_core/hosts/maya/api/lib.py b/client/ayon_core/hosts/maya/api/lib.py index 7c3c739d7c..8ca898f621 100644 --- a/client/ayon_core/hosts/maya/api/lib.py +++ b/client/ayon_core/hosts/maya/api/lib.py @@ -2931,13 +2931,13 @@ def bake_to_world_space(nodes, def load_capture_preset(data): - """Convert OpenPype Extract Playblast settings to `capture` arguments + """Convert AYON Extract Playblast settings to `capture` arguments Input data is the settings from: `project_settings/maya/publish/ExtractPlayblast/capture_preset` Args: - data (dict): Capture preset settings from OpenPype settings + data (dict): Capture preset settings from AYON settings Returns: dict: `capture.capture` compatible keyword arguments @@ -3288,7 +3288,7 @@ def set_colorspace(): else: # TODO: deprecated code from 3.15.5 - remove # Maya 2022+ introduces new OCIO v2 color management settings that - # can override the old color management preferences. OpenPype has + # can override the old color management preferences. AYON has # separate settings for both so we fall back when necessary. use_ocio_v2 = imageio["colorManagementPreference_v2"]["enabled"] if use_ocio_v2 and not ocio_v2_support: diff --git a/client/ayon_core/hosts/maya/api/lib_rendersetup.py b/client/ayon_core/hosts/maya/api/lib_rendersetup.py index fb6dd13ce0..c2b5ec843c 100644 --- a/client/ayon_core/hosts/maya/api/lib_rendersetup.py +++ b/client/ayon_core/hosts/maya/api/lib_rendersetup.py @@ -3,7 +3,7 @@ https://github.com/Colorbleed/colorbleed-config/blob/acre/colorbleed/maya/lib_rendersetup.py Credits: Roy Nieterau (BigRoy) / Colorbleed -Modified for use in OpenPype +Modified for use in AYON """ diff --git a/client/ayon_core/hosts/maya/api/render_setup_tools.py b/client/ayon_core/hosts/maya/api/render_setup_tools.py index a6b46e1e9a..a5e04de184 100644 --- a/client/ayon_core/hosts/maya/api/render_setup_tools.py +++ b/client/ayon_core/hosts/maya/api/render_setup_tools.py @@ -5,7 +5,7 @@ Export Maya nodes from Render Setup layer as if flattened in that layer instead of exporting the defaultRenderLayer as Maya forces by default Credits: Roy Nieterau (BigRoy) / Colorbleed -Modified for use in OpenPype +Modified for use in AYON """ diff --git a/client/ayon_core/hosts/maya/plugins/create/convert_legacy.py b/client/ayon_core/hosts/maya/plugins/create/convert_legacy.py index 9907b5f340..685602ef0b 100644 --- a/client/ayon_core/hosts/maya/plugins/create/convert_legacy.py +++ b/client/ayon_core/hosts/maya/plugins/create/convert_legacy.py @@ -19,7 +19,7 @@ class MayaLegacyConvertor(ProductConvertorPlugin, Its limitation is that you can have multiple creators creating product of the same type and there is no way to handle it. This code should - nevertheless cover all creators that came with OpenPype. + nevertheless cover all creators that came with AYON. """ identifier = "io.openpype.creators.maya.legacy" diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_look.py b/client/ayon_core/hosts/maya/plugins/publish/extract_look.py index fa74dd18d3..2a86b20131 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_look.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_look.py @@ -106,10 +106,10 @@ class TextureProcessor: self.log = log def apply_settings(self, project_settings): - """Apply OpenPype system/project settings to the TextureProcessor + """Apply AYON system/project settings to the TextureProcessor Args: - project_settings (dict): OpenPype project settings + project_settings (dict): AYON project settings Returns: None @@ -278,7 +278,7 @@ class MakeTX(TextureProcessor): """Process the texture. This function requires the `maketx` executable to be available in an - OpenImageIO toolset detectable by OpenPype. + OpenImageIO toolset detectable by AYON. Args: source (str): Path to source file. diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_loaded_plugin.py b/client/ayon_core/hosts/maya/plugins/publish/validate_loaded_plugin.py index d155a9565a..a05920a21e 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_loaded_plugin.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_loaded_plugin.py @@ -24,7 +24,7 @@ class ValidateLoadedPlugin(pyblish.api.ContextPlugin, invalid = [] loaded_plugin = cmds.pluginInfo(query=True, listPlugins=True) - # get variable from OpenPype settings + # get variable from AYON settings whitelist_native_plugins = cls.whitelist_native_plugins authorized_plugins = cls.authorized_plugins or [] diff --git a/client/ayon_core/hosts/nuke/startup/custom_write_node.py b/client/ayon_core/hosts/nuke/startup/custom_write_node.py index 075c8e7a17..d5b504a44b 100644 --- a/client/ayon_core/hosts/nuke/startup/custom_write_node.py +++ b/client/ayon_core/hosts/nuke/startup/custom_write_node.py @@ -1,4 +1,4 @@ -""" OpenPype custom script for setting up write nodes for non-publish """ +""" AYON custom script for setting up write nodes for non-publish """ import os import nuke import nukescripts diff --git a/client/ayon_core/hosts/nuke/startup/frame_setting_for_read_nodes.py b/client/ayon_core/hosts/nuke/startup/frame_setting_for_read_nodes.py index f0cbabe20f..3e1430c3b1 100644 --- a/client/ayon_core/hosts/nuke/startup/frame_setting_for_read_nodes.py +++ b/client/ayon_core/hosts/nuke/startup/frame_setting_for_read_nodes.py @@ -1,4 +1,4 @@ -""" OpenPype custom script for resetting read nodes start frame values """ +""" AYON custom script for resetting read nodes start frame values """ import nuke import nukescripts diff --git a/client/ayon_core/hosts/unreal/api/pipeline.py b/client/ayon_core/hosts/unreal/api/pipeline.py index 1f937437a4..a60564d5b0 100644 --- a/client/ayon_core/hosts/unreal/api/pipeline.py +++ b/client/ayon_core/hosts/unreal/api/pipeline.py @@ -98,7 +98,7 @@ class UnrealHost(HostBase, ILoadHost, IPublishHost): def install(): - """Install Unreal configuration for OpenPype.""" + """Install Unreal configuration for AYON.""" print("-=" * 40) logo = '''. . diff --git a/client/ayon_core/pipeline/create/legacy_create.py b/client/ayon_core/pipeline/create/legacy_create.py index a8a39b41e3..ab939343c9 100644 --- a/client/ayon_core/pipeline/create/legacy_create.py +++ b/client/ayon_core/pipeline/create/legacy_create.py @@ -44,7 +44,7 @@ class LegacyCreator(object): @classmethod def apply_settings(cls, project_settings): - """Apply OpenPype settings to a plugin class.""" + """Apply AYON settings to a plugin class.""" host_name = os.environ.get("AYON_HOST_NAME") plugin_type = "create" diff --git a/client/ayon_core/pipeline/publish/README.md b/client/ayon_core/pipeline/publish/README.md index 2a0f45d093..ee2124dfd3 100644 --- a/client/ayon_core/pipeline/publish/README.md +++ b/client/ayon_core/pipeline/publish/README.md @@ -1,8 +1,8 @@ # Publish -OpenPype is using `pyblish` for publishing process which is a little bit extented and modified mainly for UI purposes. OpenPype's (new) publish UI does not allow to enable/disable instances or plugins that can be done during creation part. Also does support actions only for validators after validation exception. +AYON is using `pyblish` for publishing process which is a little bit extented and modified mainly for UI purposes. OpenPype's (new) publish UI does not allow to enable/disable instances or plugins that can be done during creation part. Also does support actions only for validators after validation exception. ## Exceptions -OpenPype define few specific exceptions that should be used in publish plugins. +AYON define few specific exceptions that should be used in publish plugins. ### Validation exception Validation plugins should raise `PublishValidationError` to show to an artist what's wrong and give him actions to fix it. The exception says that error happened in plugin can be fixed by artist himself (with or without action on plugin). Any other errors will stop publishing immediately. Exception `PublishValidationError` raised after validation order has same effect as any other exception. @@ -35,4 +35,4 @@ class MyExtendedPlugin( ### Extensions Currently only extension is ability to define attributes for instances during creation. Method `get_attribute_defs` returns attribute definitions for families defined in plugin's `families` attribute if it's instance plugin or for whole context if it's context plugin. To convert existing values (or to remove legacy values) can be implemented `convert_attribute_values`. Values of publish attributes from created instance are never removed automatically so implementing of this method is best way to remove legacy data or convert them to new data structure. -Possible attribute definitions can be found in `openpype/pipeline/lib/attribute_definitions.py`. +Possible attribute definitions can be found in `ayon_core/lib/attribute_definitions.py`. diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index 124952b2c0..1d7b5ed5a7 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -1726,7 +1726,7 @@ class PlaceholderCreateMixin(object): items=creator_items, tooltip=( "Creator" - "\nDefines what OpenPype creator will be used to" + "\nDefines what AYON creator will be used to" " create publishable instance." "\nUseable creator depends on current host's creator list." "\nField is case sensitive." From cd0ee3b85c9accee167f68a1c2f1e366c6df1e03 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 11:22:29 +0100 Subject: [PATCH 113/150] more AYON in comments and docstrings --- client/ayon_core/addon/README.md | 2 +- client/ayon_core/addon/base.py | 2 +- client/ayon_core/lib/applications.py | 2 +- client/ayon_core/lib/ayon_info.py | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/addon/README.md b/client/ayon_core/addon/README.md index a15e8bdc69..8b6eccfc69 100644 --- a/client/ayon_core/addon/README.md +++ b/client/ayon_core/addon/README.md @@ -89,4 +89,4 @@ AYON addons should contain separated logic of specific kind of implementation, s ### TrayAddonsManager - inherits from `AddonsManager` -- has specific implementation for Pype Tray tool and handle `ITrayAddon` methods +- has specific implementation for AYON Tray and handle `ITrayAddon` methods diff --git a/client/ayon_core/addon/base.py b/client/ayon_core/addon/base.py index bbd5a486fe..42b53c59e3 100644 --- a/client/ayon_core/addon/base.py +++ b/client/ayon_core/addon/base.py @@ -741,7 +741,7 @@ class AddonsManager: addon_classes = [] for module in openpype_modules: - # Go through globals in `pype.modules` + # Go through globals in `ayon_core.modules` for name in dir(module): modules_item = getattr(module, name, None) # Filter globals that are not classes which inherit from diff --git a/client/ayon_core/lib/applications.py b/client/ayon_core/lib/applications.py index 4bf0c31d93..8912deaede 100644 --- a/client/ayon_core/lib/applications.py +++ b/client/ayon_core/lib/applications.py @@ -204,7 +204,7 @@ class ApplicationGroup: Application group wraps different versions(variants) of application. e.g. "maya" is group and "maya_2020" is variant. - Group hold `host_name` which is implementation name used in pype. Also + Group hold `host_name` which is implementation name used in AYON. Also holds `enabled` if whole app group is enabled or `icon` for application icon path in resources. diff --git a/client/ayon_core/lib/ayon_info.py b/client/ayon_core/lib/ayon_info.py index ec37d735d8..3975b35bc3 100644 --- a/client/ayon_core/lib/ayon_info.py +++ b/client/ayon_core/lib/ayon_info.py @@ -102,8 +102,8 @@ def get_all_current_info(): def extract_ayon_info_to_file(dirpath, filename=None): """Extract all current info to a file. - It is possible to define only directory path. Filename is concatenated with - pype version, workstation site id and timestamp. + It is possible to define only directory path. Filename is concatenated + with AYON version, workstation site id and timestamp. Args: dirpath (str): Path to directory where file will be stored. From 096a1f1f1f8908536d99f438741ddf37822fb3a3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 11:23:19 +0100 Subject: [PATCH 114/150] use ayon in logs --- client/ayon_core/hosts/flame/api/pipeline.py | 6 +++--- client/ayon_core/hosts/flame/api/utils.py | 2 +- .../maya/plugins/publish/validate_vray_referenced_aovs.py | 2 +- client/ayon_core/lib/terminal.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/hosts/flame/api/pipeline.py b/client/ayon_core/hosts/flame/api/pipeline.py index a902b9ee73..4578d7bb4b 100644 --- a/client/ayon_core/hosts/flame/api/pipeline.py +++ b/client/ayon_core/hosts/flame/api/pipeline.py @@ -38,12 +38,12 @@ def install(): pyblish.register_plugin_path(PUBLISH_PATH) register_loader_plugin_path(LOAD_PATH) register_creator_plugin_path(CREATE_PATH) - log.info("OpenPype Flame plug-ins registered ...") + log.info("AYON Flame plug-ins registered ...") # register callback for switching publishable pyblish.register_callback("instanceToggled", on_pyblish_instance_toggled) - log.info("OpenPype Flame host installed ...") + log.info("AYON Flame host installed ...") def uninstall(): @@ -57,7 +57,7 @@ def uninstall(): # register callback for switching publishable pyblish.deregister_callback("instanceToggled", on_pyblish_instance_toggled) - log.info("OpenPype Flame host uninstalled ...") + log.info("AYON Flame host uninstalled ...") def containerise(flame_clip_segment, diff --git a/client/ayon_core/hosts/flame/api/utils.py b/client/ayon_core/hosts/flame/api/utils.py index e37555567e..b76dd92ada 100644 --- a/client/ayon_core/hosts/flame/api/utils.py +++ b/client/ayon_core/hosts/flame/api/utils.py @@ -124,7 +124,7 @@ def setup(env=None): # synchronize resolve utility scripts _sync_utility_scripts(env) - log.info("Flame OpenPype wrapper has been installed") + log.info("Flame AYON wrapper has been installed") def get_flame_version(): diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_vray_referenced_aovs.py b/client/ayon_core/hosts/maya/plugins/publish/validate_vray_referenced_aovs.py index 0ad08b7d14..7c480a6bf7 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_vray_referenced_aovs.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_vray_referenced_aovs.py @@ -45,7 +45,7 @@ class ValidateVrayReferencedAOVs(pyblish.api.InstancePlugin, self.log.warning(( "Referenced AOVs are enabled in Vray " "Render Settings and are detected in scene, but " - "Pype render instance option for referenced AOVs is " + "AYON render instance option for referenced AOVs is " "disabled. Those AOVs will be rendered but not published " "by Pype." )) diff --git a/client/ayon_core/lib/terminal.py b/client/ayon_core/lib/terminal.py index f822a37286..a22f2358aa 100644 --- a/client/ayon_core/lib/terminal.py +++ b/client/ayon_core/lib/terminal.py @@ -69,7 +69,7 @@ class Terminal: Terminal.use_colors = False print( "Module `blessed` failed on import or terminal creation." - " Pype terminal won't use colors." + " AYON terminal won't use colors." ) Terminal._initialized = True return From 23584110d4828a67951da7751c8a32fabf45cf6a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 11:24:00 +0100 Subject: [PATCH 115/150] use AYON in uis --- client/ayon_core/hosts/flame/api/plugin.py | 2 +- client/ayon_core/hosts/flame/hooks/pre_flame_setup.py | 2 +- .../flame/startup/openpype_babypublisher/modules/panel_app.py | 2 +- .../startup/openpype_babypublisher/openpype_babypublisher.py | 2 +- client/ayon_core/hosts/flame/startup/openpype_in_flame.py | 2 +- client/ayon_core/hosts/hiero/api/plugin.py | 2 +- .../ayon_core/hosts/hiero/plugins/create/create_shot_clip.py | 2 +- client/ayon_core/hosts/maya/startup/userSetup.py | 4 ++-- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/client/ayon_core/hosts/flame/api/plugin.py b/client/ayon_core/hosts/flame/api/plugin.py index c5667eb75a..c57d021c69 100644 --- a/client/ayon_core/hosts/flame/api/plugin.py +++ b/client/ayon_core/hosts/flame/api/plugin.py @@ -38,7 +38,7 @@ class CreatorWidget(QtWidgets.QDialog): | QtCore.Qt.WindowCloseButtonHint | QtCore.Qt.WindowStaysOnTopHint ) - self.setWindowTitle(name or "Pype Creator Input") + self.setWindowTitle(name or "AYON Creator Input") self.resize(500, 700) # Where inputs and labels are set diff --git a/client/ayon_core/hosts/flame/hooks/pre_flame_setup.py b/client/ayon_core/hosts/flame/hooks/pre_flame_setup.py index b7fc431352..1ff7ad7ccf 100644 --- a/client/ayon_core/hosts/flame/hooks/pre_flame_setup.py +++ b/client/ayon_core/hosts/flame/hooks/pre_flame_setup.py @@ -72,7 +72,7 @@ class FlamePrelaunch(PreLaunchHook): project_data = { "Name": project_entity["name"], "Nickname": project_entity["code"], - "Description": "Created by OpenPype", + "Description": "Created by AYON", "SetupDir": project_entity["name"], "FrameWidth": int(width), "FrameHeight": int(height), diff --git a/client/ayon_core/hosts/flame/startup/openpype_babypublisher/modules/panel_app.py b/client/ayon_core/hosts/flame/startup/openpype_babypublisher/modules/panel_app.py index 5c5bb0b4a1..ce023a9e4d 100644 --- a/client/ayon_core/hosts/flame/startup/openpype_babypublisher/modules/panel_app.py +++ b/client/ayon_core/hosts/flame/startup/openpype_babypublisher/modules/panel_app.py @@ -79,7 +79,7 @@ class FlameBabyPublisherPanel(object): # creating ui self.window.setMinimumSize(1500, 600) - self.window.setWindowTitle('OpenPype: Baby-publisher') + self.window.setWindowTitle('AYON: Baby-publisher') self.window.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) self.window.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.window.setFocusPolicy(QtCore.Qt.StrongFocus) diff --git a/client/ayon_core/hosts/flame/startup/openpype_babypublisher/openpype_babypublisher.py b/client/ayon_core/hosts/flame/startup/openpype_babypublisher/openpype_babypublisher.py index 4675d163e3..76d74b5970 100644 --- a/client/ayon_core/hosts/flame/startup/openpype_babypublisher/openpype_babypublisher.py +++ b/client/ayon_core/hosts/flame/startup/openpype_babypublisher/openpype_babypublisher.py @@ -31,7 +31,7 @@ def scope_sequence(selection): def get_media_panel_custom_ui_actions(): return [ { - "name": "OpenPype: Baby-publisher", + "name": "AYON: Baby-publisher", "actions": [ { "name": "Create Shots", diff --git a/client/ayon_core/hosts/flame/startup/openpype_in_flame.py b/client/ayon_core/hosts/flame/startup/openpype_in_flame.py index c57c76f73a..b9cbf9700b 100644 --- a/client/ayon_core/hosts/flame/startup/openpype_in_flame.py +++ b/client/ayon_core/hosts/flame/startup/openpype_in_flame.py @@ -28,7 +28,7 @@ def exeption_handler(exctype, value, _traceback): tb (str): traceback to show """ import traceback - msg = "OpenPype: Python exception {} in {}".format(value, exctype) + msg = "AYON: Python exception {} in {}".format(value, exctype) mbox = QtWidgets.QMessageBox() mbox.setText(msg) mbox.setDetailedText( diff --git a/client/ayon_core/hosts/hiero/api/plugin.py b/client/ayon_core/hosts/hiero/api/plugin.py index 6a665dc9c5..4878368716 100644 --- a/client/ayon_core/hosts/hiero/api/plugin.py +++ b/client/ayon_core/hosts/hiero/api/plugin.py @@ -45,7 +45,7 @@ class CreatorWidget(QtWidgets.QDialog): | QtCore.Qt.WindowCloseButtonHint | QtCore.Qt.WindowStaysOnTopHint ) - self.setWindowTitle(name or "Pype Creator Input") + self.setWindowTitle(name or "AYON Creator Input") self.resize(500, 700) # Where inputs and labels are set diff --git a/client/ayon_core/hosts/hiero/plugins/create/create_shot_clip.py b/client/ayon_core/hosts/hiero/plugins/create/create_shot_clip.py index 90ea9ef50f..62e7041286 100644 --- a/client/ayon_core/hosts/hiero/plugins/create/create_shot_clip.py +++ b/client/ayon_core/hosts/hiero/plugins/create/create_shot_clip.py @@ -16,7 +16,7 @@ class CreateShotClip(phiero.Creator): gui_tracks = [track.name() for track in phiero.get_current_sequence().videoTracks()] - gui_name = "Pype publish attributes creator" + gui_name = "AYON publish attributes creator" gui_info = "Define sequential rename and fill hierarchy data." gui_inputs = { "renameHierarchy": { diff --git a/client/ayon_core/hosts/maya/startup/userSetup.py b/client/ayon_core/hosts/maya/startup/userSetup.py index adbbfe4f44..3112e2bf12 100644 --- a/client/ayon_core/hosts/maya/startup/userSetup.py +++ b/client/ayon_core/hosts/maya/startup/userSetup.py @@ -10,7 +10,7 @@ from maya import cmds host = MayaHost() install_host(host) -print("Starting OpenPype usersetup...") +print("Starting AYON usersetup...") project_name = get_current_project_name() settings = get_project_settings(project_name) @@ -47,4 +47,4 @@ if bool(int(os.environ.get(key, "0"))): ) -print("Finished OpenPype usersetup.") +print("Finished AYON usersetup.") From 1ef27f28390ccfd6da0501441a810dcffa26d047 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 11:27:10 +0100 Subject: [PATCH 116/150] use ayon name in classes and functions --- client/ayon_core/hosts/fusion/api/__init__.py | 4 +- client/ayon_core/hosts/fusion/api/menu.py | 14 ++--- .../fusion/deploy/MenuScripts/launch_menu.py | 2 +- .../hosts/houdini/startup/MainMenuCommon.xml | 2 +- client/ayon_core/hosts/max/api/menu.py | 58 +++++++++---------- client/ayon_core/hosts/max/api/pipeline.py | 6 +- .../ayon_core/hosts/resolve/api/__init__.py | 4 +- client/ayon_core/hosts/resolve/api/menu.py | 12 ++-- .../hosts/resolve/api/menu_style.qss | 2 +- client/ayon_core/hosts/resolve/startup.py | 2 +- .../resolve/utility_scripts/AYON__Menu.py | 4 +- 11 files changed, 55 insertions(+), 55 deletions(-) diff --git a/client/ayon_core/hosts/fusion/api/__init__.py b/client/ayon_core/hosts/fusion/api/__init__.py index ddd718e606..d2feee6d23 100644 --- a/client/ayon_core/hosts/fusion/api/__init__.py +++ b/client/ayon_core/hosts/fusion/api/__init__.py @@ -15,7 +15,7 @@ from .lib import ( comp_lock_and_undo_chunk ) -from .menu import launch_openpype_menu +from .menu import launch_ayon_menu __all__ = [ @@ -35,5 +35,5 @@ __all__ = [ "comp_lock_and_undo_chunk", # menu - "launch_openpype_menu", + "launch_ayon_menu", ] diff --git a/client/ayon_core/hosts/fusion/api/menu.py b/client/ayon_core/hosts/fusion/api/menu.py index 7fdc3cc21c..6a64ad2120 100644 --- a/client/ayon_core/hosts/fusion/api/menu.py +++ b/client/ayon_core/hosts/fusion/api/menu.py @@ -28,9 +28,9 @@ self = sys.modules[__name__] self.menu = None -class OpenPypeMenu(QtWidgets.QWidget): +class AYONMenu(QtWidgets.QWidget): def __init__(self, *args, **kwargs): - super(OpenPypeMenu, self).__init__(*args, **kwargs) + super(AYONMenu, self).__init__(*args, **kwargs) self.setObjectName(f"{MENU_LABEL}Menu") @@ -174,16 +174,16 @@ class OpenPypeMenu(QtWidgets.QWidget): set_current_context_framerange() -def launch_openpype_menu(): +def launch_ayon_menu(): app = get_qt_app() - pype_menu = OpenPypeMenu() + ayon_menu = AYONMenu() stylesheet = load_stylesheet() - pype_menu.setStyleSheet(stylesheet) + ayon_menu.setStyleSheet(stylesheet) - pype_menu.show() - self.menu = pype_menu + ayon_menu.show() + self.menu = ayon_menu result = app.exec_() print("Shutting down..") diff --git a/client/ayon_core/hosts/fusion/deploy/MenuScripts/launch_menu.py b/client/ayon_core/hosts/fusion/deploy/MenuScripts/launch_menu.py index 23b02b1b69..640f78eeb8 100644 --- a/client/ayon_core/hosts/fusion/deploy/MenuScripts/launch_menu.py +++ b/client/ayon_core/hosts/fusion/deploy/MenuScripts/launch_menu.py @@ -35,7 +35,7 @@ def main(env): log = Logger.get_logger(__name__) log.info(f"Registered host: {registered_host()}") - menu.launch_openpype_menu() + menu.launch_ayon_menu() # Initiate a QTimer to check if Fusion is still alive every X interval # If Fusion is not found - kill itself diff --git a/client/ayon_core/hosts/houdini/startup/MainMenuCommon.xml b/client/ayon_core/hosts/houdini/startup/MainMenuCommon.xml index b93445a974..b6e78cbdc8 100644 --- a/client/ayon_core/hosts/houdini/startup/MainMenuCommon.xml +++ b/client/ayon_core/hosts/houdini/startup/MainMenuCommon.xml @@ -1,7 +1,7 @@ - + QtWidgets.QAction: """Create AYON menu. @@ -73,7 +73,7 @@ class OpenPypeMenu(object): help_action = None for item in menu_items: if name in item.title(): - # we already have OpenPype menu + # we already have AYON menu return item if before in item.title(): @@ -85,50 +85,50 @@ class OpenPypeMenu(object): self.menu = op_menu return op_menu - def build_openpype_menu(self) -> QtWidgets.QAction: + def _build_ayon_menu(self) -> QtWidgets.QAction: """Build items in AYON menu.""" - openpype_menu = self.get_or_create_openpype_menu() - load_action = QtWidgets.QAction("Load...", openpype_menu) + ayon_menu = self._get_or_create_ayon_menu() + load_action = QtWidgets.QAction("Load...", ayon_menu) load_action.triggered.connect(self.load_callback) - openpype_menu.addAction(load_action) + ayon_menu.addAction(load_action) - publish_action = QtWidgets.QAction("Publish...", openpype_menu) + publish_action = QtWidgets.QAction("Publish...", ayon_menu) publish_action.triggered.connect(self.publish_callback) - openpype_menu.addAction(publish_action) + ayon_menu.addAction(publish_action) - manage_action = QtWidgets.QAction("Manage...", openpype_menu) + manage_action = QtWidgets.QAction("Manage...", ayon_menu) manage_action.triggered.connect(self.manage_callback) - openpype_menu.addAction(manage_action) + ayon_menu.addAction(manage_action) - library_action = QtWidgets.QAction("Library...", openpype_menu) + library_action = QtWidgets.QAction("Library...", ayon_menu) library_action.triggered.connect(self.library_callback) - openpype_menu.addAction(library_action) + ayon_menu.addAction(library_action) - openpype_menu.addSeparator() + ayon_menu.addSeparator() - workfiles_action = QtWidgets.QAction("Work Files...", openpype_menu) + workfiles_action = QtWidgets.QAction("Work Files...", ayon_menu) workfiles_action.triggered.connect(self.workfiles_callback) - openpype_menu.addAction(workfiles_action) + ayon_menu.addAction(workfiles_action) - openpype_menu.addSeparator() + ayon_menu.addSeparator() - res_action = QtWidgets.QAction("Set Resolution", openpype_menu) + res_action = QtWidgets.QAction("Set Resolution", ayon_menu) res_action.triggered.connect(self.resolution_callback) - openpype_menu.addAction(res_action) + ayon_menu.addAction(res_action) - frame_action = QtWidgets.QAction("Set Frame Range", openpype_menu) + frame_action = QtWidgets.QAction("Set Frame Range", ayon_menu) frame_action.triggered.connect(self.frame_range_callback) - openpype_menu.addAction(frame_action) + ayon_menu.addAction(frame_action) - colorspace_action = QtWidgets.QAction("Set Colorspace", openpype_menu) + colorspace_action = QtWidgets.QAction("Set Colorspace", ayon_menu) colorspace_action.triggered.connect(self.colorspace_callback) - openpype_menu.addAction(colorspace_action) + ayon_menu.addAction(colorspace_action) - unit_scale_action = QtWidgets.QAction("Set Unit Scale", openpype_menu) + unit_scale_action = QtWidgets.QAction("Set Unit Scale", ayon_menu) unit_scale_action.triggered.connect(self.unit_scale_callback) - openpype_menu.addAction(unit_scale_action) + ayon_menu.addAction(unit_scale_action) - return openpype_menu + return ayon_menu def load_callback(self): """Callback to show Loader tool.""" diff --git a/client/ayon_core/hosts/max/api/pipeline.py b/client/ayon_core/hosts/max/api/pipeline.py index fc42eada7c..afad6cbe75 100644 --- a/client/ayon_core/hosts/max/api/pipeline.py +++ b/client/ayon_core/hosts/max/api/pipeline.py @@ -14,7 +14,7 @@ from ayon_core.pipeline import ( AVALON_CONTAINER_ID, AYON_CONTAINER_ID, ) -from ayon_core.hosts.max.api.menu import OpenPypeMenu +from ayon_core.hosts.max.api.menu import AYONMenu from ayon_core.hosts.max.api import lib from ayon_core.hosts.max.api.plugin import MS_CUSTOM_ATTRIB from ayon_core.hosts.max import MAX_HOST_DIR @@ -48,7 +48,7 @@ class MaxHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): register_creator_plugin_path(CREATE_PATH) # self._register_callbacks() - self.menu = OpenPypeMenu() + self.menu = AYONMenu() self._has_been_setup = True @@ -94,7 +94,7 @@ class MaxHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): def _deferred_menu_creation(self): self.log.info("Building menu ...") - self.menu = OpenPypeMenu() + self.menu = AYONMenu() @staticmethod def create_context_node(): diff --git a/client/ayon_core/hosts/resolve/api/__init__.py b/client/ayon_core/hosts/resolve/api/__init__.py index dba275e6c4..3359430ef5 100644 --- a/client/ayon_core/hosts/resolve/api/__init__.py +++ b/client/ayon_core/hosts/resolve/api/__init__.py @@ -44,7 +44,7 @@ from .lib import ( get_reformated_path ) -from .menu import launch_pype_menu +from .menu import launch_ayon_menu from .plugin import ( ClipLoader, @@ -113,7 +113,7 @@ __all__ = [ "get_reformated_path", # menu - "launch_pype_menu", + "launch_ayon_menu", # plugin "ClipLoader", diff --git a/client/ayon_core/hosts/resolve/api/menu.py b/client/ayon_core/hosts/resolve/api/menu.py index 59eba14d83..dd8573acc0 100644 --- a/client/ayon_core/hosts/resolve/api/menu.py +++ b/client/ayon_core/hosts/resolve/api/menu.py @@ -38,9 +38,9 @@ class Spacer(QtWidgets.QWidget): self.setLayout(layout) -class OpenPypeMenu(QtWidgets.QWidget): +class AYONMenu(QtWidgets.QWidget): def __init__(self, *args, **kwargs): - super(OpenPypeMenu, self).__init__(*args, **kwargs) + super(AYONMenu, self).__init__(*args, **kwargs) self.setObjectName(f"{MENU_LABEL}Menu") @@ -170,14 +170,14 @@ class OpenPypeMenu(QtWidgets.QWidget): host_tools.show_experimental_tools_dialog() -def launch_pype_menu(): +def launch_ayon_menu(): app = QtWidgets.QApplication(sys.argv) - pype_menu = OpenPypeMenu() + ayon_menu = AYONMenu() stylesheet = load_stylesheet() - pype_menu.setStyleSheet(stylesheet) + ayon_menu.setStyleSheet(stylesheet) - pype_menu.show() + ayon_menu.show() sys.exit(app.exec_()) diff --git a/client/ayon_core/hosts/resolve/api/menu_style.qss b/client/ayon_core/hosts/resolve/api/menu_style.qss index 3d51c7139f..ad8932d881 100644 --- a/client/ayon_core/hosts/resolve/api/menu_style.qss +++ b/client/ayon_core/hosts/resolve/api/menu_style.qss @@ -51,7 +51,7 @@ QLineEdit { qproperty-alignment: AlignCenter; } -#OpenPypeMenu { +#AYONMenu { qproperty-alignment: AlignLeft; min-width: 10em; border: 1px solid #fef9ef; diff --git a/client/ayon_core/hosts/resolve/startup.py b/client/ayon_core/hosts/resolve/startup.py index b3c1a024d9..3ad0a6bf7b 100644 --- a/client/ayon_core/hosts/resolve/startup.py +++ b/client/ayon_core/hosts/resolve/startup.py @@ -35,7 +35,7 @@ def ensure_installed_host(): def launch_menu(): print("Launching Resolve AYON menu..") ensure_installed_host() - ayon_core.hosts.resolve.api.launch_pype_menu() + ayon_core.hosts.resolve.api.launch_ayon_menu() def open_workfile(path): diff --git a/client/ayon_core/hosts/resolve/utility_scripts/AYON__Menu.py b/client/ayon_core/hosts/resolve/utility_scripts/AYON__Menu.py index 08cefb9d61..b10b477beb 100644 --- a/client/ayon_core/hosts/resolve/utility_scripts/AYON__Menu.py +++ b/client/ayon_core/hosts/resolve/utility_scripts/AYON__Menu.py @@ -8,13 +8,13 @@ log = Logger.get_logger(__name__) def main(env): - from ayon_core.hosts.resolve.api import ResolveHost, launch_pype_menu + from ayon_core.hosts.resolve.api import ResolveHost, launch_ayon_menu # activate resolve from openpype host = ResolveHost() install_host(host) - launch_pype_menu() + launch_ayon_menu() if __name__ == "__main__": From f5fd0169aa8c9a76c1662ab4fc78e92cc69b25c0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 11:27:41 +0100 Subject: [PATCH 117/150] maya icon buttons use ayon prefix --- client/ayon_core/hosts/maya/api/customize.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/customize.py b/client/ayon_core/hosts/maya/api/customize.py index da046b538d..4db8819ff5 100644 --- a/client/ayon_core/hosts/maya/api/customize.py +++ b/client/ayon_core/hosts/maya/api/customize.py @@ -109,7 +109,7 @@ def override_toolbox_ui(): controls.append( cmds.iconTextButton( - "pype_toolbox_lookmanager", + "ayon_toolbox_lookmanager", annotation="Look Manager", label="Look Manager", image=os.path.join(icons, "lookmanager.png"), @@ -122,7 +122,7 @@ def override_toolbox_ui(): controls.append( cmds.iconTextButton( - "pype_toolbox_workfiles", + "ayon_toolbox_workfiles", annotation="Work Files", label="Work Files", image=os.path.join(icons, "workfiles.png"), @@ -137,7 +137,7 @@ def override_toolbox_ui(): controls.append( cmds.iconTextButton( - "pype_toolbox_loader", + "ayon_toolbox_loader", annotation="Loader", label="Loader", image=os.path.join(icons, "loader.png"), @@ -152,7 +152,7 @@ def override_toolbox_ui(): controls.append( cmds.iconTextButton( - "pype_toolbox_manager", + "ayon_toolbox_manager", annotation="Inventory", label="Inventory", image=os.path.join(icons, "inventory.png"), From f20ab5328594fe2fdecefbdee8f6996275db9b20 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 11:27:55 +0100 Subject: [PATCH 118/150] use ayon prefix in tvpaint temp file --- client/ayon_core/hosts/tvpaint/plugins/load/load_sound.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/tvpaint/plugins/load/load_sound.py b/client/ayon_core/hosts/tvpaint/plugins/load/load_sound.py index 182d95d6db..d8c6a7a430 100644 --- a/client/ayon_core/hosts/tvpaint/plugins/load/load_sound.py +++ b/client/ayon_core/hosts/tvpaint/plugins/load/load_sound.py @@ -54,7 +54,7 @@ class ImportSound(plugin.Loader): def load(self, context, name, namespace, options): # Create temp file for output output_file = tempfile.NamedTemporaryFile( - mode="w", prefix="pype_tvp_", suffix=".txt", delete=False + mode="w", prefix="ayon_tvp_", suffix=".txt", delete=False ) output_file.close() output_filepath = output_file.name.replace("\\", "/") From 4b9f9dbf5e2c19cd093a324e17a7dd5cc629833d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 11:28:12 +0100 Subject: [PATCH 119/150] remove pypeclub role from clockify ftrack actions --- .../clockify/ftrack/server/action_clockify_sync_server.py | 2 +- .../modules/clockify/ftrack/user/action_clockify_sync_local.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/modules/clockify/ftrack/server/action_clockify_sync_server.py b/client/ayon_core/modules/clockify/ftrack/server/action_clockify_sync_server.py index 985cf49b97..7854f0ceba 100644 --- a/client/ayon_core/modules/clockify/ftrack/server/action_clockify_sync_server.py +++ b/client/ayon_core/modules/clockify/ftrack/server/action_clockify_sync_server.py @@ -11,7 +11,7 @@ class SyncClockifyServer(ServerAction): label = "Sync To Clockify (server)" description = "Synchronise data to Clockify workspace" - role_list = ["Pypeclub", "Administrator", "project Manager"] + role_list = ["Administrator", "project Manager"] def __init__(self, *args, **kwargs): super(SyncClockifyServer, self).__init__(*args, **kwargs) diff --git a/client/ayon_core/modules/clockify/ftrack/user/action_clockify_sync_local.py b/client/ayon_core/modules/clockify/ftrack/user/action_clockify_sync_local.py index 0e8cf6bd37..4701653a0b 100644 --- a/client/ayon_core/modules/clockify/ftrack/user/action_clockify_sync_local.py +++ b/client/ayon_core/modules/clockify/ftrack/user/action_clockify_sync_local.py @@ -13,7 +13,7 @@ class SyncClockifyLocal(BaseAction): #: Action description. description = 'Synchronise data to Clockify workspace' #: roles that are allowed to register this action - role_list = ["Pypeclub", "Administrator", "project Manager"] + role_list = ["Administrator", "project Manager"] #: icon icon = statics_icon("app_icons", "clockify-white.png") From dc9eb3f2605393364679f8c5c02f1720372ce2a1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 11:28:29 +0100 Subject: [PATCH 120/150] removed unused 'OpenPypeCreatorError' from max --- client/ayon_core/hosts/max/api/plugin.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/client/ayon_core/hosts/max/api/plugin.py b/client/ayon_core/hosts/max/api/plugin.py index 4d5d18a42d..cd71d068f2 100644 --- a/client/ayon_core/hosts/max/api/plugin.py +++ b/client/ayon_core/hosts/max/api/plugin.py @@ -156,10 +156,6 @@ MS_CUSTOM_ATTRIB = """attributes "openPypeData" )""" -class OpenPypeCreatorError(CreatorError): - pass - - class MaxCreatorBase(object): @staticmethod From 9683090ee6d1ba31c190b90e9e19c6611a262cee Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 11:28:48 +0100 Subject: [PATCH 121/150] removed config name validation --- client/ayon_core/pipeline/schema/__init__.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/client/ayon_core/pipeline/schema/__init__.py b/client/ayon_core/pipeline/schema/__init__.py index 3abc576f89..67cf120b59 100644 --- a/client/ayon_core/pipeline/schema/__init__.py +++ b/client/ayon_core/pipeline/schema/__init__.py @@ -44,11 +44,6 @@ def validate(data, schema=None): _precache() root, schema = data["schema"].rsplit(":", 1) - # assert root in ( - # "mindbender-core", # Backwards compatiblity - # "avalon-core", - # "pype" - # ) if isinstance(schema, six.string_types): schema = _cache[schema + ".json"] From 47b2d86829b8843bd211f47fb152e04893fb92e9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 11:29:10 +0100 Subject: [PATCH 122/150] houdini create plugins are using 'CreatorError' --- client/ayon_core/hosts/houdini/plugins/create/create_hda.py | 5 +++-- .../hosts/houdini/plugins/create/create_redshift_rop.py | 3 ++- .../hosts/houdini/plugins/create/create_vray_rop.py | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_hda.py b/client/ayon_core/hosts/houdini/plugins/create/create_hda.py index 6a2a41d1d0..b307293dc8 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_hda.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_hda.py @@ -2,6 +2,7 @@ """Creator plugin for creating publishable Houdini Digital Assets.""" import ayon_api +from ayon_core.pipeline import CreatorError from ayon_core.hosts.houdini.api import plugin import hou @@ -52,7 +53,7 @@ class CreateHDA(plugin.HoudiniCreator): # if node type has not its definition, it is not user # created hda. We test if hda can be created from the node. if not to_hda.canCreateDigitalAsset(): - raise plugin.OpenPypeCreatorError( + raise CreatorError( "cannot create hda from node {}".format(to_hda)) hda_node = to_hda.createDigitalAsset( @@ -61,7 +62,7 @@ class CreateHDA(plugin.HoudiniCreator): ) hda_node.layoutChildren() elif self._check_existing(folder_path, node_name): - raise plugin.OpenPypeCreatorError( + raise CreatorError( ("product {} is already published with different HDA" "definition.").format(node_name)) else: diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py index 3d6d657cf0..ba0795a26e 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py @@ -2,6 +2,7 @@ """Creator plugin to create Redshift ROP.""" import hou # noqa +from ayon_core.pipeline import CreatorError from ayon_core.hosts.houdini.api import plugin from ayon_core.lib import EnumDef, BoolDef @@ -42,7 +43,7 @@ class CreateRedshiftROP(plugin.HoudiniCreator): "Redshift_IPR", node_name=f"{basename}_IPR" ) except hou.OperationFailed as e: - raise plugin.OpenPypeCreatorError( + raise CreatorError( ( "Cannot create Redshift node. Is Redshift " "installed and enabled?" diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py index 739796dc7c..6b2396bffb 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py @@ -3,7 +3,7 @@ import hou from ayon_core.hosts.houdini.api import plugin -from ayon_core.pipeline import CreatedInstance +from ayon_core.pipeline import CreatedInstance, CreatorError from ayon_core.lib import EnumDef, BoolDef @@ -42,7 +42,7 @@ class CreateVrayROP(plugin.HoudiniCreator): "vray", node_name=basename + "_IPR" ) except hou.OperationFailed: - raise plugin.OpenPypeCreatorError( + raise CreatorError( "Cannot create Vray render node. " "Make sure Vray installed and enabled!" ) From 6572bead963fb206c7b40434d771f06e0f2a17b4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 11:29:20 +0100 Subject: [PATCH 123/150] removed 'OpenPypeCreatorError' from houdini --- client/ayon_core/hosts/houdini/api/plugin.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/client/ayon_core/hosts/houdini/api/plugin.py b/client/ayon_core/hosts/houdini/api/plugin.py index b33d0fe297..a9c8c313b9 100644 --- a/client/ayon_core/hosts/houdini/api/plugin.py +++ b/client/ayon_core/hosts/houdini/api/plugin.py @@ -19,10 +19,6 @@ from ayon_core.lib import BoolDef from .lib import imprint, read, lsattr, add_self_publish_button -class OpenPypeCreatorError(CreatorError): - pass - - class Creator(LegacyCreator): """Creator plugin to create instances in Houdini @@ -92,8 +88,8 @@ class Creator(LegacyCreator): except hou.Error as er: six.reraise( - OpenPypeCreatorError, - OpenPypeCreatorError("Creator error: {}".format(er)), + CreatorError, + CreatorError("Creator error: {}".format(er)), sys.exc_info()[2]) @@ -209,8 +205,8 @@ class HoudiniCreator(NewCreator, HoudiniCreatorBase): except hou.Error as er: six.reraise( - OpenPypeCreatorError, - OpenPypeCreatorError("Creator error: {}".format(er)), + CreatorError, + CreatorError("Creator error: {}".format(er)), sys.exc_info()[2]) def lock_parameters(self, node, parameters): From 519218016debc69a296f6181a7ccf62bbb09ea6b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 11:56:53 +0100 Subject: [PATCH 124/150] more AYON replacements --- client/ayon_core/hosts/maya/api/menu.py | 2 +- client/ayon_core/hosts/maya/api/pipeline.py | 2 +- client/ayon_core/hosts/resolve/README.markdown | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/menu.py b/client/ayon_core/hosts/maya/api/menu.py index 8de025bfe5..5a266b5f25 100644 --- a/client/ayon_core/hosts/maya/api/menu.py +++ b/client/ayon_core/hosts/maya/api/menu.py @@ -50,7 +50,7 @@ def get_context_label(): def install(project_settings): if cmds.about(batch=True): - log.info("Skipping openpype.menu initialization in batch mode..") + log.info("Skipping AYON menu initialization in batch mode..") return def add_menu(): diff --git a/client/ayon_core/hosts/maya/api/pipeline.py b/client/ayon_core/hosts/maya/api/pipeline.py index 9792a4a5fe..d30d405a9e 100644 --- a/client/ayon_core/hosts/maya/api/pipeline.py +++ b/client/ayon_core/hosts/maya/api/pipeline.py @@ -673,7 +673,7 @@ def workfile_save_before_xgen(event): switching context. Args: - event (Event) - openpype/lib/events.py + event (Event) - ayon_core/lib/events.py """ if not cmds.pluginInfo("xgenToolkit", query=True, loaded=True): return diff --git a/client/ayon_core/hosts/resolve/README.markdown b/client/ayon_core/hosts/resolve/README.markdown index a8bb071e7e..064e791f65 100644 --- a/client/ayon_core/hosts/resolve/README.markdown +++ b/client/ayon_core/hosts/resolve/README.markdown @@ -18,7 +18,7 @@ This is how it looks on my testing project timeline ![image](https://user-images.githubusercontent.com/40640033/102637638-96ec6600-4156-11eb-9656-6e8e3ce4baf8.png) Notice I had renamed tracks to `main` (holding metadata markers) and `review` used for generating review data with ffmpeg confersion to jpg sequence. -1. you need to start OpenPype menu from Resolve/EditTab/Menu/Workspace/Scripts/Comp/**__OpenPype_Menu__** +1. you need to start AYON menu from Resolve/EditTab/Menu/Workspace/Scripts/Comp/**__OpenPype_Menu__** 2. then select any clips in `main` track and change their color to `Chocolate` 3. in OpenPype Menu select `Create` 4. in Creator select `Create Publishable Clip [New]` (temporary name) From cf1d9b191973063a4496d8c4b6fd1fa8f366dae7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 25 Mar 2024 11:57:49 +0100 Subject: [PATCH 125/150] Use correct host name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondřej Samohel <33513211+antirotor@users.noreply.github.com> --- client/ayon_core/hosts/max/api/pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/max/api/pipeline.py b/client/ayon_core/hosts/max/api/pipeline.py index afad6cbe75..4b1dcc25d3 100644 --- a/client/ayon_core/hosts/max/api/pipeline.py +++ b/client/ayon_core/hosts/max/api/pipeline.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -"""Pipeline tools for AYON Houdini integration.""" +"""Pipeline tools for AYON 3ds max integration.""" import os import logging from operator import attrgetter From 0f0f29a7b006fa394b2a7e1c4e0d8593c35f63f2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 12:05:58 +0100 Subject: [PATCH 126/150] also change avalon to AYON --- client/ayon_core/addon/README.md | 2 +- client/ayon_core/hosts/max/api/plugin.py | 2 +- client/ayon_core/hosts/maya/api/menu.py | 2 +- client/ayon_core/hosts/maya/api/pipeline.py | 6 +++--- client/ayon_core/hosts/maya/api/plugin.py | 2 +- client/ayon_core/hosts/maya/api/setdress.py | 8 ++++---- .../hosts/maya/plugins/load/load_vdb_to_arnold.py | 2 +- .../hosts/maya/plugins/load/load_vdb_to_redshift.py | 2 +- .../ayon_core/hosts/maya/plugins/load/load_vdb_to_vray.py | 2 +- .../hosts/maya/plugins/publish/collect_inputs.py | 6 +++--- client/ayon_core/hosts/nuke/api/pipeline.py | 6 +++--- 11 files changed, 20 insertions(+), 20 deletions(-) diff --git a/client/ayon_core/addon/README.md b/client/ayon_core/addon/README.md index 8b6eccfc69..88c27db154 100644 --- a/client/ayon_core/addon/README.md +++ b/client/ayon_core/addon/README.md @@ -27,7 +27,7 @@ AYON addons should contain separated logic of specific kind of implementation, s - default interfaces are defined in `interfaces.py` ## IPluginPaths -- addon wants to add directory path/s to avalon or publish plugins +- addon wants to add directory path/s to publish, load, create or inventory plugins - addon must implement `get_plugin_paths` which must return dictionary with possible keys `"publish"`, `"load"`, `"create"` or `"actions"` - each key may contain list or string with a path to directory with plugins diff --git a/client/ayon_core/hosts/max/api/plugin.py b/client/ayon_core/hosts/max/api/plugin.py index cd71d068f2..e5d12ce87d 100644 --- a/client/ayon_core/hosts/max/api/plugin.py +++ b/client/ayon_core/hosts/max/api/plugin.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -"""3dsmax specific Avalon/Pyblish plugin definitions.""" +"""3dsmax specific AYON/Pyblish plugin definitions.""" from abc import ABCMeta import six diff --git a/client/ayon_core/hosts/maya/api/menu.py b/client/ayon_core/hosts/maya/api/menu.py index 5a266b5f25..0cb7edd40d 100644 --- a/client/ayon_core/hosts/maya/api/menu.py +++ b/client/ayon_core/hosts/maya/api/menu.py @@ -261,7 +261,7 @@ def popup(): def update_menu_task_label(): - """Update the task label in Avalon menu to current session""" + """Update the task label in AYON menu to current session""" if IS_HEADLESS: return diff --git a/client/ayon_core/hosts/maya/api/pipeline.py b/client/ayon_core/hosts/maya/api/pipeline.py index d30d405a9e..b3e401b91e 100644 --- a/client/ayon_core/hosts/maya/api/pipeline.py +++ b/client/ayon_core/hosts/maya/api/pipeline.py @@ -361,13 +361,13 @@ def parse_container(container): def _ls(): - """Yields Avalon container node names. + """Yields AYON container node names. Used by `ls()` to retrieve the nodes and then query the full container's data. Yields: - str: Avalon container node name (objectSet) + str: AYON container node name (objectSet) """ @@ -384,7 +384,7 @@ def _ls(): } # Iterate over all 'set' nodes in the scene to detect whether - # they have the avalon container ".id" attribute. + # they have the ayon container ".id" attribute. fn_dep = om.MFnDependencyNode() iterator = om.MItDependencyNodes(om.MFn.kSet) for mobject in _maya_iterate(iterator): diff --git a/client/ayon_core/hosts/maya/api/plugin.py b/client/ayon_core/hosts/maya/api/plugin.py index bdb0cb1c99..6f8b74c906 100644 --- a/client/ayon_core/hosts/maya/api/plugin.py +++ b/client/ayon_core/hosts/maya/api/plugin.py @@ -899,7 +899,7 @@ class ReferenceLoader(Loader): cmds.disconnectAttr(input, node_attr) cmds.setAttr(node_attr, data["value"]) - # Fix PLN-40 for older containers created with Avalon that had the + # Fix PLN-40 for older containers created with AYON that had the # `.verticesOnlySet` set to True. if cmds.getAttr("{}.verticesOnlySet".format(node)): self.log.info("Setting %s.verticesOnlySet to False", node) diff --git a/client/ayon_core/hosts/maya/api/setdress.py b/client/ayon_core/hosts/maya/api/setdress.py index 7276ae254c..b1d5beb343 100644 --- a/client/ayon_core/hosts/maya/api/setdress.py +++ b/client/ayon_core/hosts/maya/api/setdress.py @@ -150,7 +150,7 @@ def load_package(filepath, name, namespace=None): containers.append(container) # TODO: Do we want to cripple? Or do we want to add a 'parent' parameter? - # Cripple the original avalon containers so they don't show up in the + # Cripple the original AYON containers so they don't show up in the # manager # for container in containers: # cmds.setAttr("%s.id" % container, @@ -175,7 +175,7 @@ def _add(instance, representation_id, loaders, namespace, root="|"): namespace (str): Returns: - str: The created Avalon container. + str: The created AYON container. """ @@ -244,7 +244,7 @@ def _instances_by_namespace(data): def get_contained_containers(container): - """Get the Avalon containers in this container + """Get the AYON containers in this container Args: container (dict): The container dict. @@ -256,7 +256,7 @@ def get_contained_containers(container): from .pipeline import parse_container - # Get avalon containers in this package setdress container + # Get AYON containers in this package setdress container containers = [] members = cmds.sets(container['objectName'], query=True) for node in cmds.ls(members, type="objectSet"): 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 eaa7ff1ae3..f0fb89e5a4 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 @@ -108,7 +108,7 @@ class LoadVDBtoArnold(load.LoaderPlugin): from maya import cmds - # Get all members of the avalon container, ensure they are unlocked + # Get all members of the AYON container, ensure they are unlocked # and delete everything members = cmds.sets(container['objectName'], query=True) cmds.lockNode(members, lock=False) 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 1707008b67..cad0900590 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 @@ -115,7 +115,7 @@ class LoadVDBtoRedShift(load.LoaderPlugin): def remove(self, container): from maya import cmds - # Get all members of the avalon container, ensure they are unlocked + # Get all members of the AYON container, ensure they are unlocked # and delete everything members = cmds.sets(container['objectName'], query=True) cmds.lockNode(members, lock=False) 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 42d4583d76..88f62e81a4 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 @@ -277,7 +277,7 @@ class LoadVDBtoVRay(load.LoaderPlugin): def remove(self, container): - # Get all members of the avalon container, ensure they are unlocked + # Get all members of the AYON container, ensure they are unlocked # and delete everything members = cmds.sets(container['objectName'], query=True) cmds.lockNode(members, lock=False) diff --git a/client/ayon_core/hosts/maya/plugins/publish/collect_inputs.py b/client/ayon_core/hosts/maya/plugins/publish/collect_inputs.py index d084804e05..fa5a694a76 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/collect_inputs.py +++ b/client/ayon_core/hosts/maya/plugins/publish/collect_inputs.py @@ -79,12 +79,12 @@ def iter_history(nodes, def collect_input_containers(containers, nodes): """Collect containers that contain any of the node in `nodes`. - This will return any loaded Avalon container that contains at least one of - the nodes. As such, the Avalon container is an input for it. Or in short, + This will return any loaded AYON container that contains at least one of + the nodes. As such, the AYON container is an input for it. Or in short, there are member nodes of that container. Returns: - list: Input avalon containers + list: Input loaded containers """ # Assume the containers have collected their cached '_members' data diff --git a/client/ayon_core/hosts/nuke/api/pipeline.py b/client/ayon_core/hosts/nuke/api/pipeline.py index a8876f6aa7..2255276c56 100644 --- a/client/ayon_core/hosts/nuke/api/pipeline.py +++ b/client/ayon_core/hosts/nuke/api/pipeline.py @@ -128,7 +128,7 @@ class NukeHost( register_creator_plugin_path(CREATE_PATH) register_inventory_action_path(INVENTORY_PATH) - # Register Avalon event for workfiles loading. + # Register AYON event for workfiles loading. register_event_callback("workio.open_file", check_inventory_versions) register_event_callback("taskChanged", change_context_label) @@ -230,9 +230,9 @@ def get_context_label(): def _install_menu(): - """Install Avalon menu into Nuke's main menu bar.""" + """Install AYON menu into Nuke's main menu bar.""" - # uninstall original avalon menu + # uninstall original AYON menu main_window = get_main_window() menubar = nuke.menu("Nuke") menu = menubar.addMenu(MENU_LABEL) From 0b0506392bee97ee9309ad52eb91ab4c8e90d916 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 12:06:14 +0100 Subject: [PATCH 127/150] fix docstring in 'classes_from_module' --- client/ayon_core/lib/python_module_tools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/lib/python_module_tools.py b/client/ayon_core/lib/python_module_tools.py index 4f9eb7f667..cb6e4c14c4 100644 --- a/client/ayon_core/lib/python_module_tools.py +++ b/client/ayon_core/lib/python_module_tools.py @@ -118,8 +118,8 @@ def classes_from_module(superclass, module): Arguments: superclass (superclass): Superclass of subclasses to look for - module (types.ModuleType): Imported module from which to - parse valid Avalon plug-ins. + module (types.ModuleType): Imported module where to look for + 'superclass' subclasses. Returns: List of plug-ins, or empty list if none is found. From 0be8f7a3e1e93ab83b57351e873261385332530e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 12:06:54 +0100 Subject: [PATCH 128/150] fix docstrings in applications lib --- client/ayon_core/lib/applications.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/client/ayon_core/lib/applications.py b/client/ayon_core/lib/applications.py index 8912deaede..cb8087905a 100644 --- a/client/ayon_core/lib/applications.py +++ b/client/ayon_core/lib/applications.py @@ -1897,12 +1897,12 @@ def should_start_last_workfile( `"0", "1", "true", "false", "yes", "no"`. Args: - project_name (str): Name of project. - host_name (str): Name of host which is launched. In avalon's - application context it's value stored in app definition under - key `"application_dir"`. Is not case sensitive. - task_name (str): Name of task which is used for launching the host. - Task name is not case sensitive. + project_name (str): Project name. + host_name (str): Host name. + task_name (str): Task name. + task_type (str): Task type. + default_output (Optional[bool]): Default output if no profile is + found. Returns: bool: True if host should start workfile. @@ -1947,12 +1947,12 @@ def should_workfile_tool_start( `"0", "1", "true", "false", "yes", "no"`. Args: - project_name (str): Name of project. - host_name (str): Name of host which is launched. In avalon's - application context it's value stored in app definition under - key `"application_dir"`. Is not case sensitive. - task_name (str): Name of task which is used for launching the host. - Task name is not case sensitive. + project_name (str): Project name. + host_name (str): Host name. + task_name (str): Task name. + task_type (str): Task type. + default_output (Optional[bool]): Default output if no profile is + found. Returns: bool: True if host should start workfile. From ed3ccf090f358d042404971b670dedf7e44838b0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 25 Mar 2024 12:28:36 +0100 Subject: [PATCH 129/150] Remove settings --- .../maya/server/settings/publishers.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 3a6de2eb44..ef23b77d19 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -753,14 +753,6 @@ class PublishersModel(BaseSettingsModel): default_factory=BasicValidateModel, title="Validate Ass Relative Paths" ) - ValidateInstancerContent: BasicValidateModel = SettingsField( - default_factory=BasicValidateModel, - title="Validate Instancer Content" - ) - ValidateInstancerFrameRanges: BasicValidateModel = SettingsField( - default_factory=BasicValidateModel, - title="Validate Instancer Cache Frame Ranges" - ) ValidateNoDefaultCameras: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate No Default Cameras" @@ -1300,16 +1292,6 @@ DEFAULT_PUBLISH_SETTINGS = { "optional": False, "active": True }, - "ValidateInstancerContent": { - "enabled": True, - "optional": False, - "active": True - }, - "ValidateInstancerFrameRanges": { - "enabled": True, - "optional": False, - "active": True - }, "ValidateNoDefaultCameras": { "enabled": True, "optional": False, From 27b6dcafba66fa82695219e582700b34c9a52908 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 25 Mar 2024 12:28:52 +0100 Subject: [PATCH 130/150] Bump version --- server_addon/maya/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/maya/server/version.py b/server_addon/maya/server/version.py index 8202425a2d..a86a3ce0a1 100644 --- a/server_addon/maya/server/version.py +++ b/server_addon/maya/server/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring addon version.""" -__version__ = "0.1.9" +__version__ = "0.1.10" From 3c23f6cc3f833ff085ef1c146511595ef5e16dc7 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 25 Mar 2024 14:00:57 +0100 Subject: [PATCH 131/150] Houdini: Improve load image - Support setting OCIO colorspace - Support single frame file versus sequence - Support 'switch' folder functionality - Allow loading more product types --- .../hosts/houdini/plugins/load/load_image.py | 96 ++++++++++++++----- 1 file changed, 73 insertions(+), 23 deletions(-) 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 b77e4f662a..72bb873eff 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_image.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_image.py @@ -1,4 +1,5 @@ import os +import re from ayon_core.pipeline import ( load, @@ -44,7 +45,14 @@ def get_image_avalon_container(): class ImageLoader(load.LoaderPlugin): """Load images into COP2""" - product_types = {"imagesequence"} + product_types = { + "imagesequence", + "review", + "render", + "plate", + "image", + "online", + } label = "Load Image (COP2)" representations = ["*"] order = -10 @@ -55,10 +63,8 @@ class ImageLoader(load.LoaderPlugin): def load(self, context, name=None, namespace=None, data=None): # Format file name, Houdini only wants forward slashes - file_path = self.filepath_from_context(context) - file_path = os.path.normpath(file_path) - file_path = file_path.replace("\\", "/") - file_path = self._get_file_sequence(file_path) + path = self.filepath_from_context(context) + path = self.format_path(path, representation=context["representation"]) # Get the root node parent = get_image_avalon_container() @@ -70,7 +76,10 @@ class ImageLoader(load.LoaderPlugin): node = parent.createNode("file", node_name=node_name) node.moveToGoodPosition() - node.setParms({"filename1": file_path}) + parms = {"filename1": path} + parms.update(self.get_colorspace_parms(context["representation"])) + + node.setParms(parms) # Imprint it manually data = { @@ -93,16 +102,17 @@ class ImageLoader(load.LoaderPlugin): # Update the file path file_path = get_representation_path(repre_entity) - file_path = file_path.replace("\\", "/") - file_path = self._get_file_sequence(file_path) + file_path = self.format_path(file_path, repre_entity) + + parms = { + "filename1": file_path, + "representation": repre_entity["id"], + } + + parms.update(self.get_colorspace_parms(repre_entity)) # Update attributes - node.setParms( - { - "filename1": file_path, - "representation": repre_entity["id"], - } - ) + node.setParms(parms) def remove(self, container): @@ -119,14 +129,54 @@ class ImageLoader(load.LoaderPlugin): if not parent.children(): parent.destroy() - def _get_file_sequence(self, file_path): - root = os.path.dirname(file_path) - files = sorted(os.listdir(root)) + @staticmethod + def format_path(path, representation): + """Format file path correctly for single image or sequence.""" + if not os.path.exists(path): + raise RuntimeError("Path does not exist: %s" % path) - first_fname = files[0] - prefix, padding, suffix = first_fname.rsplit(".", 2) - fname = ".".join([prefix, "$F{}".format(len(padding)), suffix]) - return os.path.join(root, fname).replace("\\", "/") + ext = os.path.splitext(path)[-1] - def switch(self, container, context): - self.update(container, context) + is_sequence = bool(representation["context"].get("frame")) + # The path is either a single file or sequence in a folder. + if not is_sequence: + filename = path + else: + filename = re.sub(r"(.*)\.(\d+){}$".format(re.escape(ext)), + "\\1.$F4{}".format(ext), + path) + + filename = os.path.join(path, filename) + + filename = os.path.normpath(filename) + filename = filename.replace("\\", "/") + + return filename + + def get_colorspace_parms(self, representation: dict) -> dict: + """Return the color space parameters. + + Returns the values for the colorspace parameters on the node if there + is colorspace data on the representation. + + Arguments: + representation (dict): The representation entity. + + Returns: + dict: Parm to value mapping if colorspace data is defined. + + """ + + data = representation.get("data", {}).get("colorspaceData", {}) + if not data: + return {} + + colorspace = data["colorspace"] + if colorspace: + return { + "colorspace": 3, # Use OpenColorIO + "ocio_space": colorspace + } + + def switch(self, container, representation): + self.update(container, representation) From b8d1377904d4f830145b53dc8ad4d8ef1b3a1c3a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 25 Mar 2024 14:35:16 +0100 Subject: [PATCH 132/150] Do not consider hero versions outdated ever --- client/ayon_core/tools/sceneinventory/model.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/client/ayon_core/tools/sceneinventory/model.py b/client/ayon_core/tools/sceneinventory/model.py index 5f8b1ee81c..befd0f5ab5 100644 --- a/client/ayon_core/tools/sceneinventory/model.py +++ b/client/ayon_core/tools/sceneinventory/model.py @@ -322,15 +322,15 @@ class InventoryModel(TreeModel): group_node["representation"] = repre_id group_node["version"] = version_entity["version"] - # We check against `abs(version)` because we allow a hero version - # which is represented by a negative number to also count as - # latest version - # If a hero version for whatever reason does not match the latest - # positive version number, we also consider it outdated - group_node["isOutdated"] = ( - abs(version_entity["version"]) != - highest_version_by_product_id.get(version_entity["productId"]) - ) + # Check if the version is outdated. If the version is below 0 it + # is a hero version and will never be considered outdated + is_outdated = False + if version_entity["version"] >= 0: + last_version = highest_version_by_product_id.get( + version_entity["productId"]) + if last_version is not None: + is_outdated = version_entity["version"] != last_version + group_node["isOutdated"] = is_outdated group_node["productType"] = product_type or "" group_node["productTypeIcon"] = product_type_icon From ebebc8fc9eeb3c9aa74720a5d56dc1edf3a522fd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 14:36:33 +0100 Subject: [PATCH 133/150] fix hero version integration --- client/ayon_core/plugins/publish/integrate_hero_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/integrate_hero_version.py b/client/ayon_core/plugins/publish/integrate_hero_version.py index c352e67f89..bbc20927c0 100644 --- a/client/ayon_core/plugins/publish/integrate_hero_version.py +++ b/client/ayon_core/plugins/publish/integrate_hero_version.py @@ -353,7 +353,7 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): repre_entity["context"] = repre_context repre_entity["attrib"] = { "path": str(template_filled), - "template": hero_template + "template": hero_template.template } dst_paths = [] From 092d6fcf46a2658d8472b149d75450a27f573259 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 14:36:43 +0100 Subject: [PATCH 134/150] removed unused '_default_template_name' variable --- client/ayon_core/plugins/publish/integrate_hero_version.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/ayon_core/plugins/publish/integrate_hero_version.py b/client/ayon_core/plugins/publish/integrate_hero_version.py index bbc20927c0..6a10bbd891 100644 --- a/client/ayon_core/plugins/publish/integrate_hero_version.py +++ b/client/ayon_core/plugins/publish/integrate_hero_version.py @@ -84,8 +84,6 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): # permissions error on files (files were used or user didn't have perms) # *but all other plugins must be sucessfully completed - _default_template_name = "hero" - def process(self, instance): self.log.debug( "--- Integration of Hero version for product `{}` begins.".format( From 7682137190ef015f8a9dd5581e02faece9dedc85 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 14:36:55 +0100 Subject: [PATCH 135/150] make hero integrator optional --- .../ayon_core/plugins/publish/integrate_hero_version.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/plugins/publish/integrate_hero_version.py b/client/ayon_core/plugins/publish/integrate_hero_version.py index 6a10bbd891..7969457697 100644 --- a/client/ayon_core/plugins/publish/integrate_hero_version.py +++ b/client/ayon_core/plugins/publish/integrate_hero_version.py @@ -13,7 +13,10 @@ from ayon_api.operations import ( from ayon_api.utils import create_entity_id from ayon_core.lib import create_hard_link, source_hash -from ayon_core.pipeline.publish import get_publish_template_name +from ayon_core.pipeline.publish import ( + get_publish_template_name, + OptionalPyblishPluginMixin, +) def prepare_changes(old_entity, new_entity): @@ -46,7 +49,9 @@ def prepare_changes(old_entity, new_entity): return changes -class IntegrateHeroVersion(pyblish.api.InstancePlugin): +class IntegrateHeroVersion( + OptionalPyblishPluginMixin, pyblish.api.InstancePlugin +): label = "Integrate Hero Version" # Must happen after IntegrateNew order = pyblish.api.IntegratorOrder + 0.1 From ccb00be0b3428a25d91a88956136cc6f7756af2a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 25 Mar 2024 15:11:30 +0100 Subject: [PATCH 136/150] Update client/ayon_core/tools/sceneinventory/model.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/tools/sceneinventory/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/tools/sceneinventory/model.py b/client/ayon_core/tools/sceneinventory/model.py index befd0f5ab5..14379664c6 100644 --- a/client/ayon_core/tools/sceneinventory/model.py +++ b/client/ayon_core/tools/sceneinventory/model.py @@ -295,7 +295,7 @@ class InventoryModel(TreeModel): product_ids={ group["version"]["productId"] for group in grouped.values() }, - fields=["productId", "version"] + fields={"productId", "version"} ) # Map value to `version` key highest_version_by_product_id = { From 5d9fe5d86f82556ee9aa7e2be01a4fb732590cb4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 25 Mar 2024 15:12:52 +0100 Subject: [PATCH 137/150] Use `HeroVersionType` instead of negative version number check --- client/ayon_core/tools/sceneinventory/model.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/tools/sceneinventory/model.py b/client/ayon_core/tools/sceneinventory/model.py index befd0f5ab5..c7f20a974d 100644 --- a/client/ayon_core/tools/sceneinventory/model.py +++ b/client/ayon_core/tools/sceneinventory/model.py @@ -8,7 +8,10 @@ import ayon_api from qtpy import QtCore, QtGui import qtawesome -from ayon_core.pipeline import get_current_project_name +from ayon_core.pipeline import ( + get_current_project_name, + HeroVersionType, +) from ayon_core.style import get_default_entity_icon_color from ayon_core.tools.utils import get_qt_icon from ayon_core.tools.utils.models import TreeModel, Item @@ -320,12 +323,17 @@ class InventoryModel(TreeModel): repre_entity["name"] ) group_node["representation"] = repre_id - group_node["version"] = version_entity["version"] - # Check if the version is outdated. If the version is below 0 it - # is a hero version and will never be considered outdated + # Detect hero version type + version = version_entity["version"] + if version < 0: + version = HeroVersionType(version) + group_node["version"] = version + + # Check if the version is outdated. + # Hero versions are never considered to be outdated. is_outdated = False - if version_entity["version"] >= 0: + if not isinstance(version, HeroVersionType): last_version = highest_version_by_product_id.get( version_entity["productId"]) if last_version is not None: From 910c79f886274d66e616229ad88afd90d3f5aa90 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 25 Mar 2024 15:18:28 +0100 Subject: [PATCH 138/150] Remove redundant logic --- client/ayon_core/tools/sceneinventory/model.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/client/ayon_core/tools/sceneinventory/model.py b/client/ayon_core/tools/sceneinventory/model.py index df251ef69e..8de74b978a 100644 --- a/client/ayon_core/tools/sceneinventory/model.py +++ b/client/ayon_core/tools/sceneinventory/model.py @@ -519,14 +519,6 @@ class FilterProxyModel(QtCore.QSortFilterProxyModel): if version is None: return True - # If either a version or highest is present but not the other - # consider the item invalid. - if not self._hierarchy_view: - # Skip this check if in hierarchy view, or the child item - # node will be hidden even it's actually outdated. - if version is None: - return False - return node.get("isOutdated", True) index = self.sourceModel().index(row, self.filterKeyColumn(), parent) From e735c5dc723bc127eba840a03569e7e0c3db05e9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 25 Mar 2024 15:20:12 +0100 Subject: [PATCH 139/150] Simplify further --- client/ayon_core/tools/sceneinventory/model.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/client/ayon_core/tools/sceneinventory/model.py b/client/ayon_core/tools/sceneinventory/model.py index 8de74b978a..dd5f369fed 100644 --- a/client/ayon_core/tools/sceneinventory/model.py +++ b/client/ayon_core/tools/sceneinventory/model.py @@ -513,12 +513,6 @@ class FilterProxyModel(QtCore.QSortFilterProxyModel): """ def outdated(node): - version = node.get("version", None) - - # Always allow indices that have no version data at all - if version is None: - return True - return node.get("isOutdated", True) index = self.sourceModel().index(row, self.filterKeyColumn(), parent) From 54db9178a3d8ad33630eda59aa9e5a5692f88a5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Mon, 25 Mar 2024 15:20:51 +0100 Subject: [PATCH 140/150] :recycle: create manage script --- poetry.toml | 1 + scripts/setup_env.ps1 | 67 ---------- tools/manage.ps1 | 283 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 284 insertions(+), 67 deletions(-) delete mode 100644 scripts/setup_env.ps1 create mode 100644 tools/manage.ps1 diff --git a/poetry.toml b/poetry.toml index ab1033bd37..62e2dff2a2 100644 --- a/poetry.toml +++ b/poetry.toml @@ -1,2 +1,3 @@ [virtualenvs] in-project = true +create = true diff --git a/scripts/setup_env.ps1 b/scripts/setup_env.ps1 deleted file mode 100644 index 82ff515bc6..0000000000 --- a/scripts/setup_env.ps1 +++ /dev/null @@ -1,67 +0,0 @@ -$current_dir = Get-Location -$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent -$repo_root = (Get-Item $script_dir).parent.FullName -& git submodule update --init --recursive - - -function Exit-WithCode($exitcode) { - # Only exit this host process if it's a child of another PowerShell parent process... - $parentPID = (Get-CimInstance -ClassName Win32_Process -Filter "ProcessId=$PID" | Select-Object -Property ParentProcessId).ParentProcessId - $parentProcName = (Get-CimInstance -ClassName Win32_Process -Filter "ProcessId=$parentPID" | Select-Object -Property Name).Name - if ('powershell.exe' -eq $parentProcName) { $host.SetShouldExit($exitcode) } - - exit $exitcode - } - - -function Install-Poetry() { - Write-Host ">>> Installing Poetry ... " - $python = "python" - if (Get-Command "pyenv" -ErrorAction SilentlyContinue) { - if (-not (Test-Path -PathType Leaf -Path "$($repo_root)\.python-version")) { - $result = & pyenv global - if ($result -eq "no global version configured") { - Write-Host "!!! Using pyenv but having no local or global version of Python set." -Color Red, Yellow - Exit-WithCode 1 - } - } - $python = & pyenv which python - - } - - $env:POETRY_HOME="$repo_root\.poetry" - (Invoke-WebRequest -Uri https://install.python-poetry.org/ -UseBasicParsing).Content | & $($python) - -} - -Write-Host ">>> Reading Poetry ... " -NoNewline -if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) { - Write-Host "NOT FOUND" - Install-Poetry - Write-Host "INSTALLED" -} else { - Write-Host "OK" -} - -if (-not (Test-Path -PathType Leaf -Path "$($repo_root)\poetry.lock")) { - Write-Host ">>> Installing virtual environment and creating lock." -} else { - Write-Host ">>> Installing virtual environment from lock." -} -$startTime = [int][double]::Parse((Get-Date -UFormat %s)) -& "$env:POETRY_HOME\bin\poetry" install --no-root $poetry_verbosity --ansi -if ($LASTEXITCODE -ne 0) { - Write-Host "!!! ", "Poetry command failed." - Set-Location -Path $current_dir - Exit-WithCode 1 -} -Write-Host ">>> Installing pre-commit hooks ..." -& "$env:POETRY_HOME\bin\poetry" run pre-commit install -if ($LASTEXITCODE -ne 0) { - Write-Host "!!! Installation of pre-commit hooks failed." - Set-Location -Path $current_dir - Exit-WithCode 1 -} - -$endTime = [int][double]::Parse((Get-Date -UFormat %s)) -Set-Location -Path $current_dir -Write-Host ">>> Done in $( $endTime - $startTime ) secs." diff --git a/tools/manage.ps1 b/tools/manage.ps1 new file mode 100644 index 0000000000..b2187cbaa2 --- /dev/null +++ b/tools/manage.ps1 @@ -0,0 +1,283 @@ +<# +.SYNOPSIS + Helper script to run various tasks on ayon-core addon repository. + +.DESCRIPTION + This script will detect Python installation, and build OpenPype to `build` + directory using existing virtual environment created by Poetry (or + by running `/tools/create_venv.ps1`). It will then shuffle dependencies in + build folder to optimize for different Python versions (2/3) in Python host. + +.EXAMPLE + +PS> .\tools\manage.ps1 + +.EXAMPLE + +To create virtual environment using Poetry: +PS> .\tools\manage.ps1 create-env + +.EXAMPLE + +To run Ruff check: +PS> .\tools\manage.ps1 ruff-check + +.LINK +https://github.com/ynput/ayon-core + +#> + +# Settings and gitmodule init +$CurrentDir = Get-Location +$ScriptDir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent +$RepoRoot = (Get-Item $ScriptDir).parent.FullName +& git submodule update --init --recursive +$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\tools\modules\powershell" + +$FunctionName=$ARGS[0] +$Arguments=@() +if ($ARGS.Length -gt 1) { + $Arguments = $ARGS[1..($ARGS.Length - 1)] +} + +function Exit-WithCode($exitcode) { + # Only exit this host process if it's a child of another PowerShell parent process... + $parentPID = (Get-CimInstance -ClassName Win32_Process -Filter "ProcessId=$PID" | Select-Object -Property ParentProcessId).ParentProcessId + $parentProcName = (Get-CimInstance -ClassName Win32_Process -Filter "ProcessId=$parentPID" | Select-Object -Property Name).Name + if ('powershell.exe' -eq $parentProcName) { $host.SetShouldExit($exitcode) } + + exit $exitcode +} + +function Test-CommandExists { + param ( + [Parameter(Mandatory=$true)] + [string]$command + ) + + $commandExists = $null -ne (Get-Command $command -ErrorAction SilentlyContinue) + return $commandExists +} + +function Write-Info { + <# + .SYNOPSIS + Write-Info function to write information messages. + + It uses Write-Color if that is available, otherwise falls back to Write-Host. + + #> + [CmdletBinding()] + param ( + [alias ('T')] [String[]]$Text, + [alias ('C', 'ForegroundColor', 'FGC')] [ConsoleColor[]]$Color = [ConsoleColor]::White, + [alias ('B', 'BGC')] [ConsoleColor[]]$BackGroundColor = $null, + [alias ('Indent')][int] $StartTab = 0, + [int] $LinesBefore = 0, + [int] $LinesAfter = 0, + [int] $StartSpaces = 0, + [alias ('L')] [string] $LogFile = '', + [Alias('DateFormat', 'TimeFormat')][string] $DateTimeFormat = 'yyyy-MM-dd HH:mm:ss', + [alias ('LogTimeStamp')][bool] $LogTime = $true, + [int] $LogRetry = 2, + [ValidateSet('unknown', 'string', 'unicode', 'bigendianunicode', 'utf8', 'utf7', 'utf32', 'ascii', 'default', 'oem')][string]$Encoding = 'Unicode', + [switch] $ShowTime, + [switch] $NoNewLine + ) + if (Test-CommandExists "Write-Color") { + Write-Color -Text $Text -Color $Color -BackGroundColor $BackGroundColor -StartTab $StartTab -LinesBefore $LinesBefore -LinesAfter $LinesAfter -StartSpaces $StartSpaces -LogFile $LogFile -DateTimeFormat $DateTimeFormat -LogTime $LogTime -LogRetry $LogRetry -Encoding $Encoding -ShowTime $ShowTime -NoNewLine $NoNewLine + } else { + $message = $Text -join ' ' + if ($NoNewLine) + { + Write-Host $message -NoNewline + } + else + { + Write-Host $message + } + } +} + +$art = @" + + ▄██▄ + ▄███▄ ▀██▄ ▀██▀ ▄██▀ ▄██▀▀▀██▄ ▀███▄ █▄ + ▄▄ ▀██▄ ▀██▄ ▄██▀ ██▀ ▀██▄ ▄ ▀██▄ ███ + ▄██▀ ██▄ ▀ ▄▄ ▀ ██ ▄██ ███ ▀██▄ ███ + ▄██▀ ▀██▄ ██ ▀██▄ ▄██▀ ███ ▀██ ▀█▀ + ▄██▀ ▀██▄ ▀█ ▀██▄▄▄▄██▀ █▀ ▀██▄ + + · · - =[ by YNPUT ]:[ http://ayon.ynput.io ]= - · · + +"@ + +function Write-AsciiArt() { + Write-Host $art -ForegroundColor DarkGreen +} + +function Show-PSWarning() { + if ($PSVersionTable.PSVersion.Major -lt 7) { + Write-Info -Text "!!! ", "You are using old version of PowerShell - ", "$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)" -Color Red, Yellow, White + Write-Info -Text " Please update to at least 7.0 - ", "https://github.com/PowerShell/PowerShell/releases" -Color Yellow, White + Exit-WithCode 1 + } +} + +function Install-Poetry() { + Write-Info -Text ">>> ", "Installing Poetry ... " -Color Green, Gray + $python = "python" + if (Get-Command "pyenv" -ErrorAction SilentlyContinue) { + if (-not (Test-Path -PathType Leaf -Path "$($RepoRoot)\.python-version")) { + $result = & pyenv global + if ($result -eq "no global version configured") { + Write-Info "!!! Using pyenv but having no local or global version of Python set." -Color Red, Yellow + Exit-WithCode 1 + } + } + $python = & pyenv which python + + } + + $env:POETRY_HOME="$RepoRoot\.poetry" + (Invoke-WebRequest -Uri https://install.python-poetry.org/ -UseBasicParsing).Content | & $($python) - +} + +function Set-Cwd() { + Set-Location -Path $RepoRoot +} + +function Restore-Cwd() { + $tmp_current_dir = Get-Location + if ("$tmp_current_dir" -ne "$CurrentDir") { + Write-Info -Text ">>> ", "Restoring current directory" -Color Green, Gray + Set-Location -Path $CurrentDir + } +} + +function Initialize-Environment { + Write-Info -Text ">>> ", "Reading Poetry ... " -Color Green, Gray -NoNewline + if (-not(Test-Path -PathType Container -Path "$( $env:POETRY_HOME )\bin")) + { + Write-Info -Text "NOT FOUND" -Color Yellow + Install-Poetry + Write-Info -Text "INSTALLED" -Color Cyan + } + else + { + Write-Info -Text "OK" -Color Green + } + + if (-not(Test-Path -PathType Leaf -Path "$( $repo_root )\poetry.lock")) + { + Write-Info -Text ">>> ", "Installing virtual environment and creating lock." -Color Green, Gray + } + else + { + Write-Info -Text ">>> ", "Installing virtual environment from lock." -Color Green, Gray + } + $startTime = [int][double]::Parse((Get-Date -UFormat %s)) + & "$env:POETRY_HOME\bin\poetry" config virtualenvs.in-project true --local + & "$env:POETRY_HOME\bin\poetry" config virtualenvs.create true --local + & "$env:POETRY_HOME\bin\poetry" install --no-root $poetry_verbosity --ansi + if ($LASTEXITCODE -ne 0) + { + Write-Info -Text "!!! ", "Poetry command failed." -Color Red, Yellow + Restore-Cwd + Exit-WithCode 1 + } + if (Test-Path -PathType Container -Path "$( $repo_root )\.git") + { + Write-Info -Text ">>> ", "Installing pre-commit hooks ..." -Color Green, White + & "$env:POETRY_HOME\bin\poetry" run pre-commit install + if ($LASTEXITCODE -ne 0) + { + Write-Info -Text "!!! ", "Installation of pre-commit hooks failed." -Color Red, Yellow + } + } + $endTime = [int][double]::Parse((Get-Date -UFormat %s)) + Restore-Cwd + try + { + if (Test-CommandExists "New-BurntToastNotification") + { + $app_logo = "$repo_root\tools\icons\ayon.ico" + New-BurntToastNotification -AppLogo "$app_logo" -Text "AYON", "Virtual environment created.", "All done in $( $endTime - $startTime ) secs." + } + } + catch {} + Write-Info -Text ">>> ", "Virtual environment created." -Color Green, White +} + +function Invoke-Ruff { + param ( + [switch] $Fix + ) + $Poetry = "$RepoRoot\.poetry\bin\poetry.exe" + $RuffArgs = @( "run", "ruff", "check" ) + if ($Fix) { + $RuffArgs += "--fix" + } + & $Poetry $RuffArgs +} + +function Invoke-Codespell { + param ( + [switch] $Fix + ) + $Poetry = "$RepoRoot\.poetry\bin\poetry.exe" + $CodespellArgs = @( "run", "codespell" ) + if ($Fix) { + $CodespellArgs += "--fix" + } + & $Poetry $CodespellArgs +} + +function Write-Help { + <# + .SYNOPSIS + Write-Help function to write help messages. + #> + Write-Host "" + Write-Host "AYON Addon management script" + Write-Host "" + Write-Info -Text "Usage: ", "./manage.ps1 ", "[command]" -Color Gray, White, Cyan + Write-Host "" + Write-Host "Runtime targets:" + Write-Info -Text " create-env ", "Install Poetry and update venv by lock file" -Color White, Cyan + Write-Info -Text " ruff-check ", "Run Ruff check for the repository" -Color White, Cyan + Write-Info -Text " ruff-fix ", "Run Ruff fix for the repository" -Color White, Cyan + Write-Info -Text " codespell ", "Run codespell check for the repository" -Color White, Cyan + Write-Host "" +} + +function Resolve-Function { + if ($null -eq $FunctionName) { + Write-Help + return + } + $FunctionName = $FunctionName.ToLower() -replace "\W" + if ($FunctionName -eq "createenv") { + Set-Cwd + Initialize-Environment + } elseif ($FunctionName -eq "ruffcheck") { + Set-Cwd + Invoke-Ruff + } elseif ($FunctionName -eq "rufffix") { + Set-Cwd + Invoke-Ruff -Fix + } elseif ($FunctionName -eq "codespell") { + Set-Cwd + Invoke-CodeSpell + } else { + Write-Host "Unknown function ""$FunctionName""" + Write-Help + } +} + +# ----------------------------------------------------- + +Show-PSWarning +Write-AsciiArt + +Resolve-Function From 11f5d9d5717aab775f05149bbb1c1ea99d32b48e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 25 Mar 2024 15:24:57 +0100 Subject: [PATCH 141/150] Fix docstring --- client/ayon_core/tools/sceneinventory/model.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/ayon_core/tools/sceneinventory/model.py b/client/ayon_core/tools/sceneinventory/model.py index dd5f369fed..330b174218 100644 --- a/client/ayon_core/tools/sceneinventory/model.py +++ b/client/ayon_core/tools/sceneinventory/model.py @@ -508,8 +508,7 @@ class FilterProxyModel(QtCore.QSortFilterProxyModel): def _is_outdated(self, row, parent): """Return whether row is outdated. - A row is considered outdated if it has no "version" or the "isOutdated" - value is True. + A row is considered outdated if `isOutdated` data is true or not set. """ def outdated(node): From 44641bc63071bca836bf1079254028df5326dddd Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 25 Mar 2024 15:37:22 +0100 Subject: [PATCH 142/150] Fix displaying HeroVersionType in version delegate --- client/ayon_core/tools/utils/lib.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/tools/utils/lib.py b/client/ayon_core/tools/utils/lib.py index 8bcfe8b985..4b7ca5425e 100644 --- a/client/ayon_core/tools/utils/lib.py +++ b/client/ayon_core/tools/utils/lib.py @@ -123,6 +123,7 @@ def paint_image_with_color(image, color): def format_version(value): """Formats integer to displayable version name""" + value = int(value) # convert e.g. HeroVersionType to its version value label = "v{0:03d}".format(abs(value)) if value < 0: return "[{}]".format(label) From a046c0145649c0b21cbf0bb057529bd88c3e073d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Mon, 25 Mar 2024 15:42:02 +0100 Subject: [PATCH 143/150] :pencil2: fix text --- tools/manage.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/manage.ps1 b/tools/manage.ps1 index b2187cbaa2..23c52d57be 100644 --- a/tools/manage.ps1 +++ b/tools/manage.ps1 @@ -243,7 +243,7 @@ function Write-Help { Write-Host "" Write-Info -Text "Usage: ", "./manage.ps1 ", "[command]" -Color Gray, White, Cyan Write-Host "" - Write-Host "Runtime targets:" + Write-Host "Commands:" Write-Info -Text " create-env ", "Install Poetry and update venv by lock file" -Color White, Cyan Write-Info -Text " ruff-check ", "Run Ruff check for the repository" -Color White, Cyan Write-Info -Text " ruff-fix ", "Run Ruff fix for the repository" -Color White, Cyan From 26b411b2d1f2d06d902cc4efb36428e2343f4b4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Mon, 25 Mar 2024 15:42:24 +0100 Subject: [PATCH 144/150] :recycle: add linux script --- scripts/setup_env.sh => tools/manage.sh | 122 +++++++++++++++++++++--- 1 file changed, 110 insertions(+), 12 deletions(-) rename scripts/setup_env.sh => tools/manage.sh (55%) diff --git a/scripts/setup_env.sh b/tools/manage.sh similarity index 55% rename from scripts/setup_env.sh rename to tools/manage.sh index 16298cb8bc..f40df80790 100644 --- a/scripts/setup_env.sh +++ b/tools/manage.sh @@ -83,16 +83,19 @@ realpath () { echo $(cd $(dirname "$1"); pwd)/$(basename "$1") } -main () { - detect_python || return 1 - +############################################################################## +# Create Virtual Environment +# Globals: +# repo_root +# POETRY_HOME +# poetry_verbosity +# Arguments: +# Path to resolve +# Returns: +# None +############################################################################### +create_env () { # Directories - repo_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}"))) - - if [[ -z $POETRY_HOME ]]; then - export POETRY_HOME="$repo_root/.poetry" - fi - pushd "$repo_root" > /dev/null || return > /dev/null echo -e "${BIGreen}>>>${RST} Reading Poetry ... \c" @@ -115,10 +118,105 @@ main () { return 1 fi - echo -e "${BIGreen}>>>${RST} Installing pre-commit hooks ..." - "$POETRY_HOME/bin/poetry" run pre-commit install + echo -e "${BIGreen}>>>${RST} Cleaning cache files ..." + clean_pyc + + "$POETRY_HOME/bin/poetry" run python -m pip install --disable-pip-version-check --force-reinstall pip + + if [ -d "$repo_root/.git" ]; then + echo -e "${BIGreen}>>>${RST} Installing pre-commit hooks ..." + "$POETRY_HOME/bin/poetry" run pre-commit install + fi +} + +print_art() { + echo -e "${BGreen}" + cat <<-EOF + + ▄██▄ + ▄███▄ ▀██▄ ▀██▀ ▄██▀ ▄██▀▀▀██▄ ▀███▄ █▄ + ▄▄ ▀██▄ ▀██▄ ▄██▀ ██▀ ▀██▄ ▄ ▀██▄ ███ + ▄██▀ ██▄ ▀ ▄▄ ▀ ██ ▄██ ███ ▀██▄ ███ + ▄██▀ ▀██▄ ██ ▀██▄ ▄██▀ ███ ▀██ ▀█▀ + ▄██▀ ▀██▄ ▀█ ▀██▄▄▄▄██▀ █▀ ▀██▄ + + · · - =[ by YNPUT ]:[ http://ayon.ynput.io ]= - · · + +EOF + echo -e "${RST}" +} + +default_help() { + print_art + echo -e "${BWhite}AYON Addon management script${RST}" + echo "" + echo -e "Usage: ${BWhite}./manage.sh${RST} ${BICyan}[command]${RST}" + echo "" + echo "${BWhite}Commands:${RST}" + echo " ${BWhite}create-env${RST} ${BCyan}Install Poetry and update venv by lock file${RST}" + echo " ${BWhite}ruff-check${RST} ${BCyan}Run Ruff check for the repository${RST}" + echo " ${BWhite}ruff-fix${RST} ${BCyan}Run Ruff fix for the repository${RST}" + echo " ${BWhite}codespell${RST} ${BCyan}Run codespell check for the repository${RST}" + echo "" +} + +run_ruff () { + echo -e "${BIGreen}>>>${RST} Running Ruff check ..." + "$POETRY_HOME/bin/poetry" run ruff +} + +run_ruff_check () { + echo -e "${BIGreen}>>>${RST} Running Ruff fix ..." + "$POETRY_HOME/bin/poetry" run ruff --fix +} + +run_codespell () { + echo -e "${BIGreen}>>>${RST} Running codespell check ..." + "$POETRY_HOME/bin/poetry" run codespell +} + +main () { + detect_python || return 1 + + # Directories + repo_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}"))) + + if [[ -z $POETRY_HOME ]]; then + export POETRY_HOME="$repo_root/.poetry" + fi + + pushd "$repo_root" > /dev/null || return > /dev/null + + # Use first argument, lower and keep only characters + function_name="$(echo "$1" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z]*//g')" + + case $function_name in + "createenv") + create_env || return_code=$? + exit $return_code + ;; + "ruffcheck") + run_ruff || return_code=$? + exit $return_code + ;; + "rufffix") + run_ruff_check || return_code=$? + exit $return_code + ;; + "codespell") + run_codespell || return_code=$? + exit $return_code + ;; + esac + + if [ "$function_name" != "" ]; then + echo -e "${BIRed}!!!${RST} Unknown function name: $function_name" + fi + + default_help + exit $return_code } return_code=0 -main || return_code=$? +main "$@" || return_code=$? exit $return_code From 1f275ecf4145dbd7b29ff78c85f25f8a4d1ffd7a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 15:44:04 +0100 Subject: [PATCH 145/150] removed mindbender from harmony readme --- client/ayon_core/hosts/harmony/api/README.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/hosts/harmony/api/README.md b/client/ayon_core/hosts/harmony/api/README.md index 6d1e400476..7ac185638a 100644 --- a/client/ayon_core/hosts/harmony/api/README.md +++ b/client/ayon_core/hosts/harmony/api/README.md @@ -204,7 +204,7 @@ class CreateComposite(harmony.Creator): name = "compositeDefault" label = "Composite" - product_type = "mindbender.template" + product_type = "template" def __init__(self, *args, **kwargs): super(CreateComposite, self).__init__(*args, **kwargs) @@ -221,7 +221,7 @@ class CreateRender(harmony.Creator): name = "writeDefault" label = "Write" - product_type = "mindbender.imagesequence" + product_type = "render" node_type = "WRITE" def __init__(self, *args, **kwargs): @@ -304,7 +304,7 @@ class ExtractImage(pyblish.api.InstancePlugin): label = "Extract Image Sequence" order = pyblish.api.ExtractorOrder hosts = ["harmony"] - families = ["mindbender.imagesequence"] + families = ["render"] def process(self, instance): project_path = harmony.send( @@ -582,8 +582,16 @@ class ImageSequenceLoader(load.LoaderPlugin): """Load images Stores the imported asset in a container named after the asset. """ - product_types = {"mindbender.imagesequence"} + product_types = { + "shot", + "render", + "image", + "plate", + "reference", + "review", + } representations = ["*"] + extensions = {"jpeg", "png", "jpg"} def load(self, context, name=None, namespace=None, data=None): files = [] From afd2e3518c36353f3ed206ffc4f9456c7b64ba03 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Mon, 25 Mar 2024 16:20:41 +0100 Subject: [PATCH 146/150] :recycle: fix linux version --- .hound.yml | 6 +++--- tools/manage.ps1 | 0 tools/manage.sh | 14 +++++++------- 3 files changed, 10 insertions(+), 10 deletions(-) mode change 100644 => 100755 tools/manage.ps1 mode change 100644 => 100755 tools/manage.sh diff --git a/.hound.yml b/.hound.yml index df9cdab64a..de5adb3154 100644 --- a/.hound.yml +++ b/.hound.yml @@ -1,3 +1,3 @@ -flake8: - enabled: true - config_file: setup.cfg +flake8: + enabled: true + config_file: setup.cfg diff --git a/tools/manage.ps1 b/tools/manage.ps1 old mode 100644 new mode 100755 diff --git a/tools/manage.sh b/tools/manage.sh old mode 100644 new mode 100755 index f40df80790..923953bf96 --- a/tools/manage.sh +++ b/tools/manage.sh @@ -152,22 +152,22 @@ default_help() { echo "" echo -e "Usage: ${BWhite}./manage.sh${RST} ${BICyan}[command]${RST}" echo "" - echo "${BWhite}Commands:${RST}" - echo " ${BWhite}create-env${RST} ${BCyan}Install Poetry and update venv by lock file${RST}" - echo " ${BWhite}ruff-check${RST} ${BCyan}Run Ruff check for the repository${RST}" - echo " ${BWhite}ruff-fix${RST} ${BCyan}Run Ruff fix for the repository${RST}" - echo " ${BWhite}codespell${RST} ${BCyan}Run codespell check for the repository${RST}" + echo -e "${BWhite}Commands:${RST}" + echo -e " ${BWhite}create-env${RST} ${BCyan}Install Poetry and update venv by lock file${RST}" + echo -e " ${BWhite}ruff-check${RST} ${BCyan}Run Ruff check for the repository${RST}" + echo -e " ${BWhite}ruff-fix${RST} ${BCyan}Run Ruff fix for the repository${RST}" + echo -e " ${BWhite}codespell${RST} ${BCyan}Run codespell check for the repository${RST}" echo "" } run_ruff () { echo -e "${BIGreen}>>>${RST} Running Ruff check ..." - "$POETRY_HOME/bin/poetry" run ruff + "$POETRY_HOME/bin/poetry" run ruff check } run_ruff_check () { echo -e "${BIGreen}>>>${RST} Running Ruff fix ..." - "$POETRY_HOME/bin/poetry" run ruff --fix + "$POETRY_HOME/bin/poetry" run ruff check --fix } run_codespell () { From 28db94fd7eb70d3b55c9e57984da9747d6d883d3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 17:33:10 +0100 Subject: [PATCH 147/150] fix work with templates in workfiles tool --- .../ayon_core/tools/workfiles/models/workfiles.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/tools/workfiles/models/workfiles.py b/client/ayon_core/tools/workfiles/models/workfiles.py index 479c8ea849..e645d020af 100644 --- a/client/ayon_core/tools/workfiles/models/workfiles.py +++ b/client/ayon_core/tools/workfiles/models/workfiles.py @@ -302,10 +302,11 @@ class WorkareaModel: file_template = anatomy.get_template_item( "work", template_key, "file" - ).template + ) + file_template_str = file_template.template - template_has_version = "{version" in file_template - template_has_comment = "{comment" in file_template + template_has_version = "{version" in file_template_str + template_has_comment = "{comment" in file_template_str comment_hints, comment = self._get_comments_from_root( file_template, @@ -315,7 +316,8 @@ class WorkareaModel: current_filename, ) last_version = self._get_last_workfile_version( - workdir, file_template, fill_data, extensions) + workdir, file_template_str, fill_data, extensions + ) return { "template_key": template_key, @@ -340,17 +342,18 @@ class WorkareaModel: ): anatomy = self._controller.project_anatomy fill_data = self._prepare_fill_data(folder_id, task_id) + template_key = self._get_template_key(fill_data) workdir = self._get_workdir(anatomy, template_key, fill_data) file_template = anatomy.get_template_item( "work", template_key, "file" - ).template + ) if use_last_version: version = self._get_last_workfile_version( - workdir, file_template, fill_data, self._extensions + workdir, file_template.template, fill_data, self._extensions ) fill_data["version"] = version fill_data["ext"] = extension.lstrip(".") From e8d3e569d1b6c405fedd55add7ff5e7b8c986de4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 17:48:55 +0100 Subject: [PATCH 148/150] added some docstrings and comments --- .../tools/workfiles/models/workfiles.py | 73 ++++++++++++++++--- 1 file changed, 62 insertions(+), 11 deletions(-) diff --git a/client/ayon_core/tools/workfiles/models/workfiles.py b/client/ayon_core/tools/workfiles/models/workfiles.py index e645d020af..5f59b99b22 100644 --- a/client/ayon_core/tools/workfiles/models/workfiles.py +++ b/client/ayon_core/tools/workfiles/models/workfiles.py @@ -25,7 +25,14 @@ from ayon_core.tools.workfiles.abstract import ( class CommentMatcher(object): - """Use anatomy and work file data to parse comments from filenames""" + """Use anatomy and work file data to parse comments from filenames. + + Args: + extensions (set[str]): Set of extensions. + file_template (AnatomyStringTemplate): File template. + data (dict[str, Any]): Data to fill the template with. + + """ def __init__(self, extensions, file_template, data): self.fname_regex = None @@ -199,6 +206,22 @@ class WorkareaModel: def _get_last_workfile_version( self, workdir, file_template, fill_data, extensions ): + """ + + Todos: + Validate if logic of this function is correct. It does return + last version + 1 which might be wrong. + + Args: + workdir (str): Workdir path. + file_template (str): File template. + fill_data (dict[str, Any]): Fill data. + extensions (set[str]): Extensions. + + Returns: + int: Next workfile version. + + """ version = get_last_workfile_with_version( workdir, file_template, fill_data, extensions )[1] @@ -225,8 +248,21 @@ class WorkareaModel: root, current_filename, ): + """Get comments from root directory. + + Args: + file_template (AnatomyStringTemplate): File template. + extensions (set[str]): Extensions. + fill_data (dict[str, Any]): Fill data. + root (str): Root directory. + current_filename (str): Current filename. + + Returns: + Tuple[list[str], Union[str, None]]: Comment hints and current + comment. + + """ current_comment = None - comment_hints = set() filenames = [] if root and os.path.exists(root): for filename in os.listdir(root): @@ -239,10 +275,11 @@ class WorkareaModel: filenames.append(filename) if not filenames: - return comment_hints, current_comment + return [], current_comment matcher = CommentMatcher(extensions, file_template, fill_data) + comment_hints = set() for filename in filenames: comment = matcher.parse_comment(filename) if comment: @@ -259,18 +296,18 @@ class WorkareaModel: return directory_template.format_strict(fill_data).normalized() def get_workarea_save_as_data(self, folder_id, task_id): - folder = None - task = None + folder_entity = None + task_entity = None if folder_id: - folder = self._controller.get_folder_entity( + folder_entity = self._controller.get_folder_entity( self.project_name, folder_id ) - if task_id: - task = self._controller.get_task_entity( - self.project_name, task_id - ) + if folder_entity and task_id: + task_entity = self._controller.get_task_entity( + self.project_name, task_id + ) - if not folder or not task: + if not folder_entity or not task_entity: return { "template_key": None, "template_has_version": None, @@ -340,6 +377,20 @@ class WorkareaModel: version, comment, ): + """Fill workarea filepath based on context. + + Args: + folder_id (str): Folder id. + task_id (str): Task id. + extension (str): File extension. + use_last_version (bool): Use last version. + version (int): Version number. + comment (str): Comment. + + Returns: + WorkareaFilepathResult: Workarea filepath result. + + """ anatomy = self._controller.project_anatomy fill_data = self._prepare_fill_data(folder_id, task_id) From 93c22be6bf6e987babd450935374c18cb568b0d6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 25 Mar 2024 20:06:19 +0100 Subject: [PATCH 149/150] Fix syntax error --- .../hosts/maya/plugins/publish/validate_look_shading_group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_look_shading_group.py b/client/ayon_core/hosts/maya/plugins/publish/validate_look_shading_group.py index 7d249a6021..e70a805de4 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_look_shading_group.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_look_shading_group.py @@ -16,7 +16,7 @@ class ValidateShadingEngine(pyblish.api.InstancePlugin, Shading engines should be named "{surface_shader}SG" """ -`` + order = ValidateContentsOrder families = ["look"] hosts = ["maya"] From c7a1ab2a65ef24e568d5661d314eccf9d0ddd3d7 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 25 Mar 2024 21:44:57 +0100 Subject: [PATCH 150/150] Add version check for Hou 20+ --- client/ayon_core/hosts/houdini/plugins/load/load_image.py | 4 ++++ 1 file changed, 4 insertions(+) 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 72bb873eff..0429b1c3fe 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_image.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_image.py @@ -166,6 +166,10 @@ class ImageLoader(load.LoaderPlugin): dict: Parm to value mapping if colorspace data is defined. """ + # Using OCIO colorspace on COP2 File node is only supported in Hou 20+ + major, _, _ = hou.applicationVersion() + if major < 20: + return {} data = representation.get("data", {}).get("colorspaceData", {}) if not data: