From 97f0d581b8c390711d9c7665133824d06eb2dfd2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 4 Aug 2022 14:23:11 +0200 Subject: [PATCH 001/274] Added reference keys docs --- website/docs/admin_settings_project_anatomy.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/website/docs/admin_settings_project_anatomy.md b/website/docs/admin_settings_project_anatomy.md index 106faeb806..dbc4d255af 100644 --- a/website/docs/admin_settings_project_anatomy.md +++ b/website/docs/admin_settings_project_anatomy.md @@ -100,6 +100,11 @@ We have a few required anatomy templates for OpenPype to work properly, however +### Anatomy reference keys + +Anatomy templates have ability to use "referenced keys". Best example is `path` in publish or work templates which are just referencing to `folder` and `file` (`{@folder}/{@file}`) so any changes in folder or file template are propagated to the path template. The other advantage is to simplify version and frame formatting with paddings. In default templates you can notice that keys `{@version}` or `{@frame}` are used in templates. They are referencing to `Anatomy` -> `Templates` -> `Version` or `Frame` which handle version and frame formatting with padding. + +So if you set `project_anatomy/templates/defaults/version_padding` to `5` the `{@version}` key will be transformed to `v{version:0>5}` and version number in paths will have 5 numbers -> `v00001`. ## Attributes From d43a5388f746a4b4775a023e7356a9505a6c4dfd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 4 Aug 2022 14:23:22 +0200 Subject: [PATCH 002/274] added optional keys --- website/docs/admin_settings_project_anatomy.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/website/docs/admin_settings_project_anatomy.md b/website/docs/admin_settings_project_anatomy.md index dbc4d255af..d11d651103 100644 --- a/website/docs/admin_settings_project_anatomy.md +++ b/website/docs/admin_settings_project_anatomy.md @@ -106,6 +106,12 @@ Anatomy templates have ability to use "referenced keys". Best example is `path` So if you set `project_anatomy/templates/defaults/version_padding` to `5` the `{@version}` key will be transformed to `v{version:0>5}` and version number in paths will have 5 numbers -> `v00001`. +### Optional keys + +In some cases are not all keys available and should be just ignored. For example `{frame}` should be available only for sequences but we have single publish template. To handle these cases it is possible to use optional marks which will ignore segment of template if can't be filled because of missing keys. To mark these segments use `<` and `>`. +. +Template `{project[code]}_{asset}_{subset}<_{output}><.{@frame}>.{ext}` can handle all 4 possible situations when `output` and `frame` keys are available or not. The optional segments can contain additional text, like in the example dot (`.`) for frame and underscore (`_`) for output, those are also ignored if the keys are not available. Optional segments without formatting keys are kept untouched: `
` -> stays as `
`. It is possible to nest optional segments inside optional segments `<{asset}<.{@frame}>
>` which may result in empty string if `asset` key is not available. + ## Attributes From bfaac0c688cb9a1cff522783ac668610df1d6926 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 4 Aug 2022 14:23:42 +0200 Subject: [PATCH 003/274] added some basic information about attributes --- website/docs/admin_settings_project_anatomy.md | 11 +++++++++++ .../docs/assets/settings/anatomy_attributes.png | Bin 0 -> 14753 bytes 2 files changed, 11 insertions(+) create mode 100644 website/docs/assets/settings/anatomy_attributes.png diff --git a/website/docs/admin_settings_project_anatomy.md b/website/docs/admin_settings_project_anatomy.md index d11d651103..ddce934f55 100644 --- a/website/docs/admin_settings_project_anatomy.md +++ b/website/docs/admin_settings_project_anatomy.md @@ -114,6 +114,17 @@ Template `{project[code]}_{asset}_{subset}<_{output}><.{@frame}>.{ext}` can hand ## Attributes +Project attributes are used as default values for new assets under project, except `Applications` and `Active project` which are project specific. Values of attributes that are **not** project specific are always used from assets. So if `tools` are not loading as expected it is because the asset have different value. + +![anatomy_attributes](assets/settings/anatomy_attributes.png) + +**Most of attributes don't need detailed explanation.** + +| Attribute | Description | +| --- | --- | +| `Applications` | List of applications that can be used in the project. At the moment only possible filter of applications. | +| `Tools` | List of application tools. This value can be overridden per asset. | +| `Active project` | Project won't be visible in tools if enabled.
- To revert check `Show Inactive projects` checkbox in project settings. | ## Task Types diff --git a/website/docs/assets/settings/anatomy_attributes.png b/website/docs/assets/settings/anatomy_attributes.png new file mode 100644 index 0000000000000000000000000000000000000000..777b1c36acc734afdf43c22a9d250ba313094b23 GIT binary patch literal 14753 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 Date: Thu, 4 Aug 2022 14:24:01 +0200 Subject: [PATCH 004/274] at least a little bit enhanced task types --- website/docs/admin_settings_project_anatomy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/admin_settings_project_anatomy.md b/website/docs/admin_settings_project_anatomy.md index ddce934f55..361710fc9c 100644 --- a/website/docs/admin_settings_project_anatomy.md +++ b/website/docs/admin_settings_project_anatomy.md @@ -129,7 +129,7 @@ Project attributes are used as default values for new assets under project, exce ## Task Types -Current state of default Task descriptors. +Available task types on a project. Each task on an asset is referencing to a task type on project which allows access to task type attributes. At this moment only `short_name` is available (can be used in templates as `{task[short_name]}`). ![tasks](assets/settings/anatomy_tasks.png) From 6d3d52c05c630b9f559ff9a86f0e8cc574007fc7 Mon Sep 17 00:00:00 2001 From: Kaa Maurice Date: Thu, 11 Aug 2022 14:46:43 +0200 Subject: [PATCH 005/274] Blender Validators settings schemas and defaults --- .../defaults/project_settings/blender.json | 62 +++++++++- .../schema_project_blender.json | 4 + .../schemas/schema_blender_publish.json | 114 ++++++++++++++++++ 3 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 openpype/settings/entities/schemas/projects_schema/schemas/schema_blender_publish.json diff --git a/openpype/settings/defaults/project_settings/blender.json b/openpype/settings/defaults/project_settings/blender.json index a7262dcb5d..a596d13865 100644 --- a/openpype/settings/defaults/project_settings/blender.json +++ b/openpype/settings/defaults/project_settings/blender.json @@ -2,5 +2,65 @@ "workfile_builder": { "create_first_version": false, "custom_templates": [] + }, + "publish": { + "ValidateCameraZeroKeyframe": { + "enabled": true, + "optional": true, + "active": true + }, + "ValidateMeshHasUvs": { + "enabled": true, + "optional": true, + "active": true + }, + "ValidateTransformZero": { + "enabled": true, + "optional": false, + "active": true + }, + "ExtractBlend": { + "enabled": true, + "optional": true, + "active": true, + "pack_images": true, + "families": [ + "model", + "camera", + "rig", + "action", + "layout" + ] + }, + "ExtractBlendAnimation": { + "enabled": true, + "optional": true, + "active": true + }, + "ExtractCamera": { + "enabled": true, + "optional": true, + "active": true + }, + "ExtractFBX": { + "enabled": true, + "optional": true, + "active": false + }, + "ExtractAnimationFBX": { + "enabled": true, + "optional": true, + "active": false + }, + "ExtractABC": { + "enabled": true, + "optional": true, + "active": false + }, + "ExtractLayout": { + "enabled": true, + "optional": true, + "active": false + } } -} \ No newline at end of file +} diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json index af09329a03..4c72ebda2f 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json @@ -12,6 +12,10 @@ "workfile_builder/builder_on_start", "workfile_builder/profiles" ] + }, + { + "type": "schema", + "name": "schema_blender_publish" } ] } diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_blender_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_blender_publish.json new file mode 100644 index 0000000000..6111ae4a74 --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_blender_publish.json @@ -0,0 +1,114 @@ +{ + "type": "dict", + "collapsible": true, + "key": "publish", + "label": "Publish plugins", + "children": [ + { + "type": "label", + "label": "Validators" + }, + { + "type": "schema_template", + "name": "template_publish_plugin", + "template_data": [ + { + "key": "ValidateCameraZeroKeyframe", + "label": "Validate Camera Zero Keyframe" + } + ] + }, + + { + "type": "collapsible-wrap", + "label": "Model", + "children": [ + { + "type": "label", + "label": "Validators" + }, + { + "type": "schema_template", + "name": "template_publish_plugin", + "template_data": [ + { + "key": "ValidateMeshHasUvs", + "label": "Validate Mesh Has UVs" + }, + { + "key": "ValidateTransformZero", + "label": "Validate Transform Zero" + } + ] + } + ] + }, + { + "type": "splitter" + }, + { + "type": "label", + "label": "Extractors" + }, + { + "type": "dict", + "collapsible": true, + "key": "ExtractBlend", + "label": "Extract Blend", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "boolean", + "key": "active", + "label": "Active" + }, + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "task-types-enum" + } + ] + }, + { + "type": "schema_template", + "name": "template_publish_plugin", + "template_data": [ + { + "key": "ExtractFBX", + "label": "Extract FBX (model and rig)", + }, + { + "key": "ExtractABC", + "label": "Extract ABC (model and pointcache)" + }, + { + "key": "ExtractBlendAnimation", + "label": "Extract Animation as Blend" + }, + { + "key": "ExtractAnimationFBX", + "label": "Extract Animation as FBX" + }, + { + "key": "ExtractCamera", + "label": "Extract FBX Camera as FBX" + }, + { + "key": "ExtractLayout", + "label": "Extract Layout as JSON" + } + ] + } + ] +} From 6cba799c460dc3c9745bf68fc6edcd3c6ab345e0 Mon Sep 17 00:00:00 2001 From: Kaa Maurice Date: Thu, 11 Aug 2022 15:39:03 +0200 Subject: [PATCH 006/274] refactor blender Validators --- .../publish/validate_camera_zero_keyframe.py | 19 ++++++++++-------- .../plugins/publish/validate_mesh_has_uv.py | 17 ++++++++-------- .../validate_mesh_no_negative_scale.py | 19 ++++++++---------- .../publish/validate_no_colons_in_name.py | 15 ++++++++------ .../plugins/publish/validate_object_mode.py | 20 +++++++++---------- .../publish/validate_transform_zero.py | 19 ++++++++++++------ .../defaults/project_settings/blender.json | 6 +++++- .../schemas/schema_blender_publish.json | 9 ++------- 8 files changed, 66 insertions(+), 58 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py b/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py index 39b9b67511..bfd7224b80 100644 --- a/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py +++ b/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py @@ -1,9 +1,11 @@ from typing import List import mathutils +import bpy import pyblish.api -import openpype.hosts.blender.api.action +from openpype.api import ValidateContentsOrder +from openpype.hosts.blender.api.action import SelectInvalidAction class ValidateCameraZeroKeyframe(pyblish.api.InstancePlugin): @@ -14,21 +16,21 @@ class ValidateCameraZeroKeyframe(pyblish.api.InstancePlugin): in Unreal and Blender. """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ["blender"] families = ["camera"] category = "geometry" version = (0, 1, 0) label = "Zero Keyframe" - actions = [openpype.hosts.blender.api.action.SelectInvalidAction] + actions = [SelectInvalidAction] _identity = mathutils.Matrix() - @classmethod - def get_invalid(cls, instance) -> List: + @staticmethod + def get_invalid(instance) -> List: invalid = [] - for obj in [obj for obj in instance]: - if obj.type == "CAMERA": + for obj in set(instance): + if isinstance(obj, bpy.types.Object) and obj.type == "CAMERA": if obj.animation_data and obj.animation_data.action: action = obj.animation_data.action frames_set = set() @@ -45,4 +47,5 @@ class ValidateCameraZeroKeyframe(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: raise RuntimeError( - f"Object found in instance is not in Object Mode: {invalid}") + f"Camera must have a keyframe at frame 0: {invalid}" + ) diff --git a/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py b/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py index 1c73476fc8..d83ead78cc 100644 --- a/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py +++ b/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py @@ -3,18 +3,19 @@ from typing import List import bpy import pyblish.api -import openpype.hosts.blender.api.action +from openpype.api import ValidateContentsOrder +from openpype.hosts.blender.api.action import SelectInvalidAction class ValidateMeshHasUvs(pyblish.api.InstancePlugin): """Validate that the current mesh has UV's.""" - order = pyblish.api.ValidatorOrder + order = ValidateContentsOrder hosts = ["blender"] families = ["model"] category = "geometry" label = "Mesh Has UV's" - actions = [openpype.hosts.blender.api.action.SelectInvalidAction] + actions = [SelectInvalidAction] optional = True @staticmethod @@ -33,20 +34,20 @@ class ValidateMeshHasUvs(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance) -> List: invalid = [] - # TODO (jasper): only check objects in the collection that will be published? - for obj in [ - obj for obj in instance]: + for obj in set(instance): try: if obj.type == 'MESH': # Make sure we are in object mode. bpy.ops.object.mode_set(mode='OBJECT') if not cls.has_uvs(obj): invalid.append(obj) - except: + except RuntimeError: continue return invalid def process(self, instance): invalid = self.get_invalid(instance) if invalid: - raise RuntimeError(f"Meshes found in instance without valid UV's: {invalid}") + raise RuntimeError( + f"Meshes found in instance without valid UV's: {invalid}" + ) diff --git a/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py b/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py index 00159a2d36..b7687009d7 100644 --- a/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py +++ b/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py @@ -3,29 +3,26 @@ from typing import List import bpy import pyblish.api -import openpype.hosts.blender.api.action +from openpype.api import ValidateContentsOrder +from openpype.hosts.blender.api.action import SelectInvalidAction class ValidateMeshNoNegativeScale(pyblish.api.Validator): """Ensure that meshes don't have a negative scale.""" - order = pyblish.api.ValidatorOrder + order = ValidateContentsOrder hosts = ["blender"] families = ["model"] label = "Mesh No Negative Scale" - actions = [openpype.hosts.blender.api.action.SelectInvalidAction] + actions = [SelectInvalidAction] @staticmethod def get_invalid(instance) -> List: invalid = [] - # TODO (jasper): only check objects in the collection that will be published? - for obj in [ - obj for obj in bpy.data.objects if obj.type == 'MESH' - ]: - if any(v < 0 for v in obj.scale): - invalid.append(obj) - - return invalid + for obj in set(instance): + if isinstance(obj, bpy.types.Object) and obj.type == 'MESH': + if any(v < 0 for v in obj.scale): + invalid.append(obj) def process(self, instance): invalid = self.get_invalid(instance) diff --git a/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py b/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py index 261ff864d5..cb8fa0f34a 100644 --- a/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py +++ b/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py @@ -1,7 +1,10 @@ from typing import List +import bpy + import pyblish.api -import openpype.hosts.blender.api.action +from openpype.api import ValidateContentsOrder +from openpype.hosts.blender.api.action import SelectInvalidAction class ValidateNoColonsInName(pyblish.api.InstancePlugin): @@ -12,20 +15,20 @@ class ValidateNoColonsInName(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ["blender"] families = ["model", "rig"] version = (0, 1, 0) label = "No Colons in names" - actions = [openpype.hosts.blender.api.action.SelectInvalidAction] + actions = [SelectInvalidAction] - @classmethod + @staticmethod def get_invalid(cls, instance) -> List: invalid = [] - for obj in [obj for obj in instance]: + for obj in set(instance): if ':' in obj.name: invalid.append(obj) - if obj.type == 'ARMATURE': + if isinstance(obj, bpy.types.Object) and obj.type == 'ARMATURE': for bone in obj.data.bones: if ':' in bone.name: invalid.append(obj) diff --git a/openpype/hosts/blender/plugins/publish/validate_object_mode.py b/openpype/hosts/blender/plugins/publish/validate_object_mode.py index 90ef0b7c41..36b7a59eb2 100644 --- a/openpype/hosts/blender/plugins/publish/validate_object_mode.py +++ b/openpype/hosts/blender/plugins/publish/validate_object_mode.py @@ -1,7 +1,9 @@ from typing import List +import bpy + import pyblish.api -import openpype.hosts.blender.api.action +from openpype.hosts.blender.api.action import SelectInvalidAction class ValidateObjectIsInObjectMode(pyblish.api.InstancePlugin): @@ -12,20 +14,16 @@ class ValidateObjectIsInObjectMode(pyblish.api.InstancePlugin): families = ["model", "rig", "layout"] category = "geometry" label = "Validate Object Mode" - actions = [openpype.hosts.blender.api.action.SelectInvalidAction] + actions = [SelectInvalidAction] optional = False - @classmethod + @staticmethod def get_invalid(cls, instance) -> List: invalid = [] - for obj in [obj for obj in instance]: - try: - if obj.type == 'MESH' or obj.type == 'ARMATURE': - # Check if the object is in object mode. - if not obj.mode == 'OBJECT': - invalid.append(obj) - except Exception: - continue + for obj in set(instance): + if isinstance(obj, bpy.types.Object): + if not obj.mode == 'OBJECT': + invalid.append(obj) return invalid def process(self, instance): diff --git a/openpype/hosts/blender/plugins/publish/validate_transform_zero.py b/openpype/hosts/blender/plugins/publish/validate_transform_zero.py index 7456dbc423..737c43cc3f 100644 --- a/openpype/hosts/blender/plugins/publish/validate_transform_zero.py +++ b/openpype/hosts/blender/plugins/publish/validate_transform_zero.py @@ -1,9 +1,11 @@ from typing import List import mathutils +import bpy import pyblish.api -import openpype.hosts.blender.api.action +from openpype.api import ValidateContentsOrder +from openpype.hosts.blender.api.action import SelectInvalidAction class ValidateTransformZero(pyblish.api.InstancePlugin): @@ -15,21 +17,24 @@ class ValidateTransformZero(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ["blender"] families = ["model"] category = "geometry" version = (0, 1, 0) label = "Transform Zero" - actions = [openpype.hosts.blender.api.action.SelectInvalidAction] + actions = [SelectInvalidAction] _identity = mathutils.Matrix() @classmethod def get_invalid(cls, instance) -> List: invalid = [] - for obj in [obj for obj in instance]: - if obj.matrix_basis != cls._identity: + for obj in set(instance): + if ( + isinstance(obj, bpy.types.Object) + and obj.matrix_basis != cls._identity + ): invalid.append(obj) return invalid @@ -37,4 +42,6 @@ class ValidateTransformZero(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: raise RuntimeError( - f"Object found in instance is not in Object Mode: {invalid}") + "Object found in instance has not" + f" transform to zero: {invalid}" + ) diff --git a/openpype/settings/defaults/project_settings/blender.json b/openpype/settings/defaults/project_settings/blender.json index a596d13865..2720e0286d 100644 --- a/openpype/settings/defaults/project_settings/blender.json +++ b/openpype/settings/defaults/project_settings/blender.json @@ -14,6 +14,11 @@ "optional": true, "active": true }, + "ValidateMeshNoNegativeScale": { + "enabled": true, + "optional": false, + "active": true + }, "ValidateTransformZero": { "enabled": true, "optional": false, @@ -23,7 +28,6 @@ "enabled": true, "optional": true, "active": true, - "pack_images": true, "families": [ "model", "camera", diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_blender_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_blender_publish.json index 6111ae4a74..4dab373efd 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_blender_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_blender_publish.json @@ -18,15 +18,10 @@ } ] }, - { "type": "collapsible-wrap", "label": "Model", "children": [ - { - "type": "label", - "label": "Validators" - }, { "type": "schema_template", "name": "template_publish_plugin", @@ -76,7 +71,7 @@ "key": "families", "label": "Families", "type": "list", - "object_type": "task-types-enum" + "object_type": "text" } ] }, @@ -86,7 +81,7 @@ "template_data": [ { "key": "ExtractFBX", - "label": "Extract FBX (model and rig)", + "label": "Extract FBX (model and rig)" }, { "key": "ExtractABC", From b8376b4a42a4ff333e6305b88ee94b3b13e6fb0c Mon Sep 17 00:00:00 2001 From: Kaa Maurice Date: Thu, 11 Aug 2022 15:44:54 +0200 Subject: [PATCH 007/274] added validator no negative scale to the schema --- .../projects_schema/schemas/schema_blender_publish.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_blender_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_blender_publish.json index 4dab373efd..58428ad60a 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_blender_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_blender_publish.json @@ -30,6 +30,10 @@ "key": "ValidateMeshHasUvs", "label": "Validate Mesh Has UVs" }, + { + "key": "ValidateMeshNoNegativeScale", + "label": "Validate Mesh No Negative Scale" + }, { "key": "ValidateTransformZero", "label": "Validate Transform Zero" From 0f90ca4a7a8a856da60e345ee86a3d7f3758c23a Mon Sep 17 00:00:00 2001 From: Kaa Maurice Date: Thu, 11 Aug 2022 16:09:16 +0200 Subject: [PATCH 008/274] fix and clean Blender validators attrs --- .../blender/plugins/publish/validate_camera_zero_keyframe.py | 2 -- openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py | 2 +- .../blender/plugins/publish/validate_mesh_no_negative_scale.py | 1 + .../hosts/blender/plugins/publish/validate_no_colons_in_name.py | 1 + openpype/hosts/blender/plugins/publish/validate_object_mode.py | 2 +- .../hosts/blender/plugins/publish/validate_transform_zero.py | 2 +- 6 files changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py b/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py index bfd7224b80..ea45318219 100644 --- a/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py +++ b/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py @@ -24,8 +24,6 @@ class ValidateCameraZeroKeyframe(pyblish.api.InstancePlugin): label = "Zero Keyframe" actions = [SelectInvalidAction] - _identity = mathutils.Matrix() - @staticmethod def get_invalid(instance) -> List: invalid = [] diff --git a/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py b/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py index d83ead78cc..4995eedad4 100644 --- a/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py +++ b/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py @@ -13,7 +13,7 @@ class ValidateMeshHasUvs(pyblish.api.InstancePlugin): order = ValidateContentsOrder hosts = ["blender"] families = ["model"] - category = "geometry" + category = "uv" label = "Mesh Has UV's" actions = [SelectInvalidAction] optional = True diff --git a/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py b/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py index b7687009d7..449e711663 100644 --- a/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py +++ b/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py @@ -13,6 +13,7 @@ class ValidateMeshNoNegativeScale(pyblish.api.Validator): order = ValidateContentsOrder hosts = ["blender"] families = ["model"] + category = "geometry" label = "Mesh No Negative Scale" actions = [SelectInvalidAction] diff --git a/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py b/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py index cb8fa0f34a..f1889e5837 100644 --- a/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py +++ b/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py @@ -18,6 +18,7 @@ class ValidateNoColonsInName(pyblish.api.InstancePlugin): order = ValidateContentsOrder hosts = ["blender"] families = ["model", "rig"] + category = "cleanup" version = (0, 1, 0) label = "No Colons in names" actions = [SelectInvalidAction] diff --git a/openpype/hosts/blender/plugins/publish/validate_object_mode.py b/openpype/hosts/blender/plugins/publish/validate_object_mode.py index 36b7a59eb2..65b0bf7655 100644 --- a/openpype/hosts/blender/plugins/publish/validate_object_mode.py +++ b/openpype/hosts/blender/plugins/publish/validate_object_mode.py @@ -12,7 +12,7 @@ class ValidateObjectIsInObjectMode(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder - 0.01 hosts = ["blender"] families = ["model", "rig", "layout"] - category = "geometry" + category = "cleanup" label = "Validate Object Mode" actions = [SelectInvalidAction] optional = False diff --git a/openpype/hosts/blender/plugins/publish/validate_transform_zero.py b/openpype/hosts/blender/plugins/publish/validate_transform_zero.py index 737c43cc3f..7443e3c64e 100644 --- a/openpype/hosts/blender/plugins/publish/validate_transform_zero.py +++ b/openpype/hosts/blender/plugins/publish/validate_transform_zero.py @@ -20,7 +20,7 @@ class ValidateTransformZero(pyblish.api.InstancePlugin): order = ValidateContentsOrder hosts = ["blender"] families = ["model"] - category = "geometry" + category = "cleanup" version = (0, 1, 0) label = "Transform Zero" actions = [SelectInvalidAction] From 551f34a873c89e739dc0b5d28a74eeec3f79dac2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 12 Aug 2022 14:25:29 +0200 Subject: [PATCH 009/274] Add subsetGroup column to scene inventory --- openpype/tools/sceneinventory/model.py | 11 +++++++++-- openpype/tools/sceneinventory/window.py | 3 ++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py index 63fbe04c5c..97cc11ff23 100644 --- a/openpype/tools/sceneinventory/model.py +++ b/openpype/tools/sceneinventory/model.py @@ -34,7 +34,8 @@ from .lib import ( class InventoryModel(TreeModel): """The model for the inventory""" - Columns = ["Name", "version", "count", "family", "loader", "objectName"] + Columns = ["Name", "version", "count", "family", + "subsetGroup", "loader", "objectName"] OUTDATED_COLOR = QtGui.QColor(235, 30, 30) CHILD_OUTDATED_COLOR = QtGui.QColor(200, 160, 30) @@ -157,8 +158,13 @@ class InventoryModel(TreeModel): # Family icon return item.get("familyIcon", None) + column_name = self.Columns[index.column()] + + if column_name == "subsetGroup" and item.get("subsetGroup"): + return qtawesome.icon("fa.object-group", + color=get_default_entity_icon_color()) + if item.get("isGroupNode"): - column_name = self.Columns[index.column()] if column_name == "active_site": provider = item.get("active_site_provider") return self._site_icons.get(provider) @@ -423,6 +429,7 @@ class InventoryModel(TreeModel): group_node["familyIcon"] = family_icon group_node["count"] = len(group_items) group_node["isGroupNode"] = True + group_node["subsetGroup"] = subset["data"].get("subsetGroup") if self.sync_enabled: progress = get_progress_for_repre( diff --git a/openpype/tools/sceneinventory/window.py b/openpype/tools/sceneinventory/window.py index 054c2a2daa..02addbccfe 100644 --- a/openpype/tools/sceneinventory/window.py +++ b/openpype/tools/sceneinventory/window.py @@ -88,7 +88,8 @@ class SceneInventoryWindow(QtWidgets.QDialog): view.setColumnWidth(1, 55) # version view.setColumnWidth(2, 55) # count view.setColumnWidth(3, 150) # family - view.setColumnWidth(4, 100) # namespace + view.setColumnWidth(4, 120) # subsetGroup + view.setColumnWidth(5, 150) # loader # apply delegates version_delegate = VersionDelegate(legacy_io, self) From ca424baf73db7d1df54d3faabacc032a9362b2c5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 12 Aug 2022 16:39:20 +0200 Subject: [PATCH 010/274] Scene Inventory: Maya add actions to select from or to scene --- .../plugins/inventory/select_containers.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 openpype/hosts/maya/plugins/inventory/select_containers.py diff --git a/openpype/hosts/maya/plugins/inventory/select_containers.py b/openpype/hosts/maya/plugins/inventory/select_containers.py new file mode 100644 index 0000000000..4b7c92729f --- /dev/null +++ b/openpype/hosts/maya/plugins/inventory/select_containers.py @@ -0,0 +1,46 @@ +from maya import cmds + +from openpype.pipeline import InventoryAction, registered_host +from openpype.hosts.maya.api.lib import get_container_members + + +class SelectInScene(InventoryAction): + """Select nodes in the scene from selected containers in scene inventory""" + + label = "Select In Scene" + icon = "search" + color = "#888888" + order = 99 + + def process(self, containers): + + all_members = [] + for container in containers: + members = get_container_members(container) + all_members.extend(members) + cmds.select(all_members, replace=True, noExpand=True) + + +class SelectFromScene(InventoryAction): + """Select containers in scene inventory from the current scene selection""" + + label = "Select From Scene" + icon = "search" + color = "#888888" + order = 100 + + def process(self, containers): + + selection = set(cmds.ls(selection=True, long=True, objectsOnly=True)) + host = registered_host() + + to_select = [] + for container in host.ls(): + members = get_container_members(container) + if any(member in selection for member in members): + to_select.append(container["objectName"]) + + return { + "objectNames": to_select, + "options": {"clear": True} + } From a3ee45edd906aae36e09f1e40cf815d2c4f9605e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 12 Aug 2022 18:14:23 +0200 Subject: [PATCH 011/274] Refactor host.ls() to host.get_containers() Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/maya/plugins/inventory/select_containers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/inventory/select_containers.py b/openpype/hosts/maya/plugins/inventory/select_containers.py index 4b7c92729f..13c2322bc0 100644 --- a/openpype/hosts/maya/plugins/inventory/select_containers.py +++ b/openpype/hosts/maya/plugins/inventory/select_containers.py @@ -35,7 +35,7 @@ class SelectFromScene(InventoryAction): host = registered_host() to_select = [] - for container in host.ls(): + for container in host.get_containers(): members = get_container_members(container) if any(member in selection for member in members): to_select.append(container["objectName"]) From b1f29676227726f4367c4f6aa4de9defd305d41e Mon Sep 17 00:00:00 2001 From: Kaa Maurice Date: Mon, 15 Aug 2022 11:00:10 +0200 Subject: [PATCH 012/274] validate mesh has UV safe code --- .../hosts/blender/plugins/publish/validate_mesh_has_uv.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py b/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py index 4995eedad4..d87b4ff1ef 100644 --- a/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py +++ b/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py @@ -36,9 +36,10 @@ class ValidateMeshHasUvs(pyblish.api.InstancePlugin): invalid = [] for obj in set(instance): try: - if obj.type == 'MESH': - # Make sure we are in object mode. - bpy.ops.object.mode_set(mode='OBJECT') + if isinstance(obj, bpy.types.Object) and obj.type == 'MESH': + if obj.mode != 'OBJECT': + # Make sure we are in object mode. + bpy.ops.object.mode_set(mode='OBJECT') if not cls.has_uvs(obj): invalid.append(obj) except RuntimeError: From 9cfa2e12e388be7f6910d97a54c12be5aa452e07 Mon Sep 17 00:00:00 2001 From: Kaa Maurice Date: Mon, 15 Aug 2022 14:20:11 +0200 Subject: [PATCH 013/274] reviews fix and clean - bugfix with staticmethod --- .../publish/validate_camera_zero_keyframe.py | 12 ++++------ .../plugins/publish/validate_mesh_has_uv.py | 24 +++++++------------ .../validate_mesh_no_negative_scale.py | 10 ++++---- .../publish/validate_no_colons_in_name.py | 16 ++++++------- .../plugins/publish/validate_object_mode.py | 17 +++++++------ .../publish/validate_transform_zero.py | 11 ++++----- 6 files changed, 40 insertions(+), 50 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py b/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py index ea45318219..5ba4808875 100644 --- a/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py +++ b/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py @@ -1,11 +1,10 @@ from typing import List -import mathutils import bpy import pyblish.api -from openpype.api import ValidateContentsOrder -from openpype.hosts.blender.api.action import SelectInvalidAction +import openpype.api +import openpype.hosts.blender.api.action class ValidateCameraZeroKeyframe(pyblish.api.InstancePlugin): @@ -16,18 +15,17 @@ class ValidateCameraZeroKeyframe(pyblish.api.InstancePlugin): in Unreal and Blender. """ - order = ValidateContentsOrder + order = openpype.api.ValidateContentsOrder hosts = ["blender"] families = ["camera"] - category = "geometry" version = (0, 1, 0) label = "Zero Keyframe" - actions = [SelectInvalidAction] + actions = [openpype.hosts.blender.api.action.SelectInvalidAction] @staticmethod def get_invalid(instance) -> List: invalid = [] - for obj in set(instance): + for obj in instance: if isinstance(obj, bpy.types.Object) and obj.type == "CAMERA": if obj.animation_data and obj.animation_data.action: action = obj.animation_data.action diff --git a/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py b/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py index d87b4ff1ef..1a52b3f851 100644 --- a/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py +++ b/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py @@ -3,19 +3,19 @@ from typing import List import bpy import pyblish.api -from openpype.api import ValidateContentsOrder -from openpype.hosts.blender.api.action import SelectInvalidAction +import openpype.api +import openpype.hosts.blender.api.action class ValidateMeshHasUvs(pyblish.api.InstancePlugin): """Validate that the current mesh has UV's.""" - order = ValidateContentsOrder + order = openpype.api.ValidateContentsOrder hosts = ["blender"] families = ["model"] - category = "uv" + category = "geometry" label = "Mesh Has UV's" - actions = [SelectInvalidAction] + actions = [openpype.hosts.blender.api.action.SelectInvalidAction] optional = True @staticmethod @@ -34,16 +34,10 @@ class ValidateMeshHasUvs(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance) -> List: invalid = [] - for obj in set(instance): - try: - if isinstance(obj, bpy.types.Object) and obj.type == 'MESH': - if obj.mode != 'OBJECT': - # Make sure we are in object mode. - bpy.ops.object.mode_set(mode='OBJECT') - if not cls.has_uvs(obj): - invalid.append(obj) - except RuntimeError: - continue + for obj in instance: + if isinstance(obj, bpy.types.Object) and obj.type == 'MESH': + if not cls.has_uvs(obj): + invalid.append(obj) return invalid def process(self, instance): diff --git a/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py b/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py index 449e711663..3c5c7c11eb 100644 --- a/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py +++ b/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py @@ -3,24 +3,24 @@ from typing import List import bpy import pyblish.api -from openpype.api import ValidateContentsOrder -from openpype.hosts.blender.api.action import SelectInvalidAction +import openpype.api +import openpype.hosts.blender.api.action class ValidateMeshNoNegativeScale(pyblish.api.Validator): """Ensure that meshes don't have a negative scale.""" - order = ValidateContentsOrder + order = openpype.api.ValidateContentsOrder hosts = ["blender"] families = ["model"] category = "geometry" label = "Mesh No Negative Scale" - actions = [SelectInvalidAction] + actions = [openpype.hosts.blender.api.action.SelectInvalidAction] @staticmethod def get_invalid(instance) -> List: invalid = [] - for obj in set(instance): + for obj in instance: if isinstance(obj, bpy.types.Object) and obj.type == 'MESH': if any(v < 0 for v in obj.scale): invalid.append(obj) diff --git a/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py b/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py index f1889e5837..daf35c61ac 100644 --- a/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py +++ b/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py @@ -3,8 +3,8 @@ from typing import List import bpy import pyblish.api -from openpype.api import ValidateContentsOrder -from openpype.hosts.blender.api.action import SelectInvalidAction +import openpype.api +import openpype.hosts.blender.api.action class ValidateNoColonsInName(pyblish.api.InstancePlugin): @@ -15,18 +15,17 @@ class ValidateNoColonsInName(pyblish.api.InstancePlugin): """ - order = ValidateContentsOrder + order = openpype.api.ValidateContentsOrder hosts = ["blender"] families = ["model", "rig"] - category = "cleanup" version = (0, 1, 0) label = "No Colons in names" - actions = [SelectInvalidAction] + actions = [openpype.hosts.blender.api.action.SelectInvalidAction] @staticmethod - def get_invalid(cls, instance) -> List: + def get_invalid(instance) -> List: invalid = [] - for obj in set(instance): + for obj in instance: if ':' in obj.name: invalid.append(obj) if isinstance(obj, bpy.types.Object) and obj.type == 'ARMATURE': @@ -40,4 +39,5 @@ class ValidateNoColonsInName(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: raise RuntimeError( - f"Objects found with colon in name: {invalid}") + f"Objects found with colon in name: {invalid}" + ) diff --git a/openpype/hosts/blender/plugins/publish/validate_object_mode.py b/openpype/hosts/blender/plugins/publish/validate_object_mode.py index 65b0bf7655..ac60e00f89 100644 --- a/openpype/hosts/blender/plugins/publish/validate_object_mode.py +++ b/openpype/hosts/blender/plugins/publish/validate_object_mode.py @@ -3,7 +3,7 @@ from typing import List import bpy import pyblish.api -from openpype.hosts.blender.api.action import SelectInvalidAction +import openpype.hosts.blender.api.action class ValidateObjectIsInObjectMode(pyblish.api.InstancePlugin): @@ -12,22 +12,21 @@ class ValidateObjectIsInObjectMode(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder - 0.01 hosts = ["blender"] families = ["model", "rig", "layout"] - category = "cleanup" label = "Validate Object Mode" - actions = [SelectInvalidAction] + actions = [openpype.hosts.blender.api.action.SelectInvalidAction] optional = False @staticmethod - def get_invalid(cls, instance) -> List: + def get_invalid(instance) -> List: invalid = [] - for obj in set(instance): - if isinstance(obj, bpy.types.Object): - if not obj.mode == 'OBJECT': - invalid.append(obj) + for obj in instance: + if isinstance(obj, bpy.types.Object) and obj.mode != "OBJECT": + invalid.append(obj) return invalid def process(self, instance): invalid = self.get_invalid(instance) if invalid: raise RuntimeError( - f"Object found in instance is not in Object Mode: {invalid}") + f"Object found in instance is not in Object Mode: {invalid}" + ) diff --git a/openpype/hosts/blender/plugins/publish/validate_transform_zero.py b/openpype/hosts/blender/plugins/publish/validate_transform_zero.py index 7443e3c64e..6e03094794 100644 --- a/openpype/hosts/blender/plugins/publish/validate_transform_zero.py +++ b/openpype/hosts/blender/plugins/publish/validate_transform_zero.py @@ -4,8 +4,8 @@ import mathutils import bpy import pyblish.api -from openpype.api import ValidateContentsOrder -from openpype.hosts.blender.api.action import SelectInvalidAction +import openpype.api +import openpype.hosts.blender.api.action class ValidateTransformZero(pyblish.api.InstancePlugin): @@ -17,20 +17,19 @@ class ValidateTransformZero(pyblish.api.InstancePlugin): """ - order = ValidateContentsOrder + order = openpype.api.ValidateContentsOrder hosts = ["blender"] families = ["model"] - category = "cleanup" version = (0, 1, 0) label = "Transform Zero" - actions = [SelectInvalidAction] + actions = [openpype.hosts.blender.api.action.SelectInvalidAction] _identity = mathutils.Matrix() @classmethod def get_invalid(cls, instance) -> List: invalid = [] - for obj in set(instance): + for obj in instance: if ( isinstance(obj, bpy.types.Object) and obj.matrix_basis != cls._identity From 403f5ddfc9cc754a13a3419b06260a34a8f682c6 Mon Sep 17 00:00:00 2001 From: Kaa Maurice Date: Mon, 15 Aug 2022 15:11:25 +0200 Subject: [PATCH 014/274] fix mesh uv validator with editmode --- .../blender/plugins/publish/validate_mesh_has_uv.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py b/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py index 1a52b3f851..83146c641e 100644 --- a/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py +++ b/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py @@ -26,7 +26,10 @@ class ValidateMeshHasUvs(pyblish.api.InstancePlugin): for uv_layer in obj.data.uv_layers: for polygon in obj.data.polygons: for loop_index in polygon.loop_indices: - if not uv_layer.data[loop_index].uv: + if ( + loop_index >= len(uv_layer.data) + or not uv_layer.data[loop_index].uv + ): return False return True @@ -36,6 +39,11 @@ class ValidateMeshHasUvs(pyblish.api.InstancePlugin): invalid = [] for obj in instance: if isinstance(obj, bpy.types.Object) and obj.type == 'MESH': + if obj.mode != "OBJECT": + cls.log.warning( + f"Mesh object {obj.name} should be in 'OBJECT' mode" + " to be properly checked." + ) if not cls.has_uvs(obj): invalid.append(obj) return invalid From 5cb99f5209d7aff8b81982dcb5ba0b604f6557b1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 15 Aug 2022 15:55:13 +0200 Subject: [PATCH 015/274] Move set render settings menu entry --- openpype/hosts/maya/api/menu.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/maya/api/menu.py b/openpype/hosts/maya/api/menu.py index b7ab529a55..b4511571fb 100644 --- a/openpype/hosts/maya/api/menu.py +++ b/openpype/hosts/maya/api/menu.py @@ -99,13 +99,6 @@ def install(): cmds.menuItem(divider=True) - cmds.menuItem( - "Set Render Settings", - command=lambda *args: lib_rendersettings.RenderSettings().set_default_renderer_settings() # noqa - ) - - cmds.menuItem(divider=True) - cmds.menuItem( "Work Files...", command=lambda *args: host_tools.show_workfiles( @@ -127,6 +120,12 @@ def install(): "Set Colorspace", command=lambda *args: lib.set_colorspace(), ) + + cmds.menuItem( + "Set Render Settings", + command=lambda *args: lib_rendersettings.RenderSettings().set_default_renderer_settings() # noqa + ) + cmds.menuItem(divider=True, parent=MENU_NAME) cmds.menuItem( "Build First Workfile", From 4504078481da5cf0ebf7128cf7fdc3062d3c926e Mon Sep 17 00:00:00 2001 From: Kaa Maurice Date: Mon, 15 Aug 2022 16:28:33 +0200 Subject: [PATCH 016/274] publisher collect current workfile as publishable representation --- .../plugins/publish/collect_current_file.py | 71 +++++++++++++++++-- 1 file changed, 67 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/collect_current_file.py b/openpype/hosts/blender/plugins/publish/collect_current_file.py index 72976c490b..1ca28f67f6 100644 --- a/openpype/hosts/blender/plugins/publish/collect_current_file.py +++ b/openpype/hosts/blender/plugins/publish/collect_current_file.py @@ -1,6 +1,23 @@ +import os import bpy import pyblish.api +from openpype.pipeline import legacy_io +from openpype.hosts.blender.api import workio + + +class SaveWorkfiledAction(pyblish.api.Action): + """Save Workfile.""" + label = "Save Workfile" + on = "failed" + icon = "save" + + def process(self, context, plugin): + current_file = workio.current_file() + if current_file: + workio.save_file(current_file) + else: + bpy.ops.wm.avalon_workfiles() class CollectBlenderCurrentFile(pyblish.api.ContextPlugin): @@ -8,12 +25,58 @@ class CollectBlenderCurrentFile(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder - 0.5 label = "Blender Current File" - hosts = ['blender'] + hosts = ["blender"] + actions = [SaveWorkfiledAction] def process(self, context): """Inject the current working file""" - current_file = bpy.data.filepath - context.data['currentFile'] = current_file + current_file = workio.current_file() + has_unsaved_changes = workio.has_unsaved_changes() - assert current_file != '', "Current file is empty. " \ + context.data["currentFile"] = current_file + + assert current_file, ( + "Current file is empty. Save the file before continuing." + ) + + assert not has_unsaved_changes, ( + "Current file has unsaved changes. " "Save the file before continuing." + ) + + folder, file = os.path.split(current_file) + filename, ext = os.path.splitext(file) + + task = legacy_io.Session["AVALON_TASK"] + + data = {} + + # create instance + instance = context.create_instance(name=filename) + subset = "workfile" + task.capitalize() + + data.update({ + "subset": subset, + "asset": os.getenv("AVALON_ASSET", None), + "label": subset, + "publish": True, + "family": "workfile", + "families": ["workfile"], + "setMembers": [current_file], + "frameStart": bpy.context.scene.frame_start, + "frameEnd": bpy.context.scene.frame_end, + }) + + data["representations"] = [{ + "name": ext.lstrip("."), + "ext": ext.lstrip("."), + "files": file, + "stagingDir": folder, + }] + + instance.data.update(data) + + self.log.info("Collected instance: {}".format(file)) + self.log.info("Scene path: {}".format(current_file)) + self.log.info("staging Dir: {}".format(folder)) + self.log.info("subset: {}".format(subset)) From 2cebaf718cc3c7df21a3543e90bac91ae56e68d8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 15 Aug 2022 21:04:51 +0200 Subject: [PATCH 017/274] global: moving collect audio to global --- .../plugins/publish/collect_audio.py | 113 ------------------ openpype/plugins/publish/collect_audio.py | 51 ++++++++ 2 files changed, 51 insertions(+), 113 deletions(-) delete mode 100644 openpype/hosts/celaction/plugins/publish/collect_audio.py create mode 100644 openpype/plugins/publish/collect_audio.py diff --git a/openpype/hosts/celaction/plugins/publish/collect_audio.py b/openpype/hosts/celaction/plugins/publish/collect_audio.py deleted file mode 100644 index c6e3bf2c03..0000000000 --- a/openpype/hosts/celaction/plugins/publish/collect_audio.py +++ /dev/null @@ -1,113 +0,0 @@ -import os -import collections -from pprint import pformat - -import pyblish.api - -from openpype.client import ( - get_subsets, - get_last_versions, - get_representations -) -from openpype.pipeline import legacy_io - - -class AppendCelactionAudio(pyblish.api.ContextPlugin): - - label = "Colect Audio for publishing" - order = pyblish.api.CollectorOrder + 0.1 - - def process(self, context): - self.log.info('Collecting Audio Data') - asset_doc = context.data["assetEntity"] - - # get all available representations - subsets = self.get_subsets( - asset_doc, - representations=["audio", "wav"] - ) - self.log.info(f"subsets is: {pformat(subsets)}") - - if not subsets.get("audioMain"): - raise AttributeError("`audioMain` subset does not exist") - - reprs = subsets.get("audioMain", {}).get("representations", []) - self.log.info(f"reprs is: {pformat(reprs)}") - - repr = next((r for r in reprs), None) - if not repr: - raise "Missing `audioMain` representation" - self.log.info(f"representation is: {repr}") - - audio_file = repr.get('data', {}).get('path', "") - - if os.path.exists(audio_file): - context.data["audioFile"] = audio_file - self.log.info( - 'audio_file: {}, has been added to context'.format(audio_file)) - else: - self.log.warning("Couldn't find any audio file on Ftrack.") - - def get_subsets(self, asset_doc, representations): - """ - Query subsets with filter on name. - - The method will return all found subsets and its defined version - and subsets. Version could be specified with number. Representation - can be filtered. - - Arguments: - asset_doct (dict): Asset (shot) mongo document - representations (list): list for all representations - - Returns: - dict: subsets with version and representations in keys - """ - - # Query all subsets for asset - project_name = legacy_io.active_project() - subset_docs = get_subsets( - project_name, asset_ids=[asset_doc["_id"]], fields=["_id"] - ) - # Collect all subset ids - subset_ids = [ - subset_doc["_id"] - for subset_doc in subset_docs - ] - - # Check if we found anything - assert subset_ids, ( - "No subsets found. Check correct filter. " - "Try this for start `r'.*'`: asset: `{}`" - ).format(asset_doc["name"]) - - last_versions_by_subset_id = get_last_versions( - project_name, subset_ids, fields=["_id", "parent"] - ) - - version_docs_by_id = {} - for version_doc in last_versions_by_subset_id.values(): - version_docs_by_id[version_doc["_id"]] = version_doc - - repre_docs = get_representations( - project_name, - version_ids=version_docs_by_id.keys(), - representation_names=representations - ) - repre_docs_by_version_id = collections.defaultdict(list) - for repre_doc in repre_docs: - version_id = repre_doc["parent"] - repre_docs_by_version_id[version_id].append(repre_doc) - - output_dict = {} - for version_id, repre_docs in repre_docs_by_version_id.items(): - version_doc = version_docs_by_id[version_id] - subset_id = version_doc["parent"] - subset_doc = last_versions_by_subset_id[subset_id] - # Store queried docs by subset name - output_dict[subset_doc["name"]] = { - "representations": repre_docs, - "version": version_doc - } - - return output_dict diff --git a/openpype/plugins/publish/collect_audio.py b/openpype/plugins/publish/collect_audio.py new file mode 100644 index 0000000000..022334e0f3 --- /dev/null +++ b/openpype/plugins/publish/collect_audio.py @@ -0,0 +1,51 @@ +import pyblish.api +from pprint import pformat + +from openpype.client import ( + get_last_version_by_subset_name, + get_representations, +) +from openpype.pipeline import ( + legacy_io, + get_representation_path, +) + + +class CollectAudio(pyblish.api.InstancePlugin): + + label = "Colect Audio" + order = pyblish.api.CollectorOrder + 0.1 + hosts = ["standalonepublisher"] + + def process(self, instance): + self.log.info('Collecting Audio Data') + + project_name = legacy_io.active_project() + asset_name = instance.data["asset"] + # * Add audio to instance if exists. + # Find latest versions document + last_version_doc = get_last_version_by_subset_name( + project_name, "audioMain", asset_name=asset_name, fields=["_id"] + ) + + repre_doc = None + if last_version_doc: + # Try to find it's representation (Expected there is only one) + repre_docs = list(get_representations( + project_name, version_ids=[last_version_doc["_id"]] + )) + if not repre_docs: + self.log.warning( + "Version document does not contain any representations" + ) + else: + repre_doc = repre_docs[0] + + # Add audio to instance if representation was found + if repre_doc: + instance.data["audio"] = [{ + "offset": 0, + "filename": get_representation_path(repre_doc) + }] + + self.log.debug("instance.data: {}".format(pformat(instance.data))) From 7c06a1fe8cbbdd612fb9a6a8f6b8df092f8e810a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 15 Aug 2022 21:06:40 +0200 Subject: [PATCH 018/274] global: improving hosts and families in collect audio --- openpype/plugins/publish/collect_audio.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/collect_audio.py b/openpype/plugins/publish/collect_audio.py index 022334e0f3..f4cad86f94 100644 --- a/openpype/plugins/publish/collect_audio.py +++ b/openpype/plugins/publish/collect_audio.py @@ -15,7 +15,24 @@ class CollectAudio(pyblish.api.InstancePlugin): label = "Colect Audio" order = pyblish.api.CollectorOrder + 0.1 - hosts = ["standalonepublisher"] + families = ["review"] + hosts = [ + "nuke", + "maya", + "shell", + "hiero", + "premiere", + "harmony", + "traypublisher", + "standalonepublisher", + "fusion", + "tvpaint", + "resolve", + "webpublisher", + "aftereffects", + "flame", + "unreal" + ] def process(self, instance): self.log.info('Collecting Audio Data') From 495a65707cfa7384dcd1498863a34942cb85a5ac Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 15 Aug 2022 21:13:09 +0200 Subject: [PATCH 019/274] Global: improving docstring and comments --- openpype/plugins/publish/collect_audio.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/collect_audio.py b/openpype/plugins/publish/collect_audio.py index f4cad86f94..4ba47f739d 100644 --- a/openpype/plugins/publish/collect_audio.py +++ b/openpype/plugins/publish/collect_audio.py @@ -12,7 +12,9 @@ from openpype.pipeline import ( class CollectAudio(pyblish.api.InstancePlugin): + """ Collecting available audio subset to instance + """ label = "Colect Audio" order = pyblish.api.CollectorOrder + 0.1 families = ["review"] @@ -35,11 +37,12 @@ class CollectAudio(pyblish.api.InstancePlugin): ] def process(self, instance): + # * Add audio to instance if exists. self.log.info('Collecting Audio Data') project_name = legacy_io.active_project() asset_name = instance.data["asset"] - # * Add audio to instance if exists. + # Find latest versions document last_version_doc = get_last_version_by_subset_name( project_name, "audioMain", asset_name=asset_name, fields=["_id"] From ba3e20a712c8d91e3d1c68dd1d2547bcffad08eb Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 16 Aug 2022 10:48:51 +0200 Subject: [PATCH 020/274] Don't skip empty value - all keys are considered required attributes --- openpype/hosts/maya/api/pipeline.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index f565f6a308..34138e64bd 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -349,8 +349,6 @@ def containerise(name, ] for key, value in data: - if not value: - continue if isinstance(value, (int, float)): cmds.addAttr(container, longName=key, attributeType="short") From b7d4a2a747ff572c091ca7f6d50b01911d570439 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 16 Aug 2022 10:49:50 +0200 Subject: [PATCH 021/274] Simplify logic since all values should be strings --- openpype/hosts/maya/api/pipeline.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 34138e64bd..d4067ea659 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -349,14 +349,8 @@ def containerise(name, ] for key, value in data: - - if isinstance(value, (int, float)): - cmds.addAttr(container, longName=key, attributeType="short") - cmds.setAttr(container + "." + key, value) - - else: - cmds.addAttr(container, longName=key, dataType="string") - cmds.setAttr(container + "." + key, value, type="string") + cmds.addAttr(container, longName=key, dataType="string") + cmds.setAttr(container + "." + key, value, type="string") main_container = cmds.ls(AVALON_CONTAINERS, type="objectSet") if not main_container: From a556f2393276048e4eaeeeeb11b82d5df9fbab59 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 16 Aug 2022 12:33:39 +0200 Subject: [PATCH 022/274] Force `str` type for the values Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/maya/api/pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index d4067ea659..84963c55a4 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -350,7 +350,7 @@ def containerise(name, for key, value in data: cmds.addAttr(container, longName=key, dataType="string") - cmds.setAttr(container + "." + key, value, type="string") + cmds.setAttr(container + "." + key, str(value), type="string") main_container = cmds.ls(AVALON_CONTAINERS, type="objectSet") if not main_container: From 64ce18b0e49b4fdefafa93ddc2508a3ca424e9b6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 16 Aug 2022 12:34:29 +0200 Subject: [PATCH 023/274] Remove redundant `str` conversion since that's now done after --- openpype/hosts/maya/api/pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 84963c55a4..2ee8fb8e5d 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -344,7 +344,7 @@ def containerise(name, ("id", AVALON_CONTAINER_ID), ("name", name), ("namespace", namespace), - ("loader", str(loader)), + ("loader", loader), ("representation", context["representation"]["_id"]), ] From 7d3be59f59757c37988163b5332621d743e13c67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 16 Aug 2022 16:02:05 +0200 Subject: [PATCH 024/274] :sparkles: collect workfile --- .../plugins/publish/collect_current_file.py | 50 +++++++++++++++++-- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/collect_current_file.py b/openpype/hosts/houdini/plugins/publish/collect_current_file.py index c0b987ebbc..1383c274a2 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_current_file.py +++ b/openpype/hosts/houdini/plugins/publish/collect_current_file.py @@ -1,27 +1,28 @@ import os import hou +from openpype.pipeline import legacy_io import pyblish.api class CollectHoudiniCurrentFile(pyblish.api.ContextPlugin): """Inject the current working file into context""" - order = pyblish.api.CollectorOrder - 0.5 + order = pyblish.api.CollectorOrder - 0.01 label = "Houdini Current File" hosts = ["houdini"] def process(self, context): """Inject the current working file""" - filepath = hou.hipFile.path() - if not os.path.exists(filepath): + current_file = hou.hipFile.path() + if not os.path.exists(current_file): # By default Houdini will even point a new scene to a path. # However if the file is not saved at all and does not exist, # we assume the user never set it. filepath = "" - elif os.path.basename(filepath) == "untitled.hip": + elif os.path.basename(current_file) == "untitled.hip": # Due to even a new file being called 'untitled.hip' we are unable # to confirm the current scene was ever saved because the file # could have existed already. We will allow it if the file exists, @@ -33,4 +34,43 @@ class CollectHoudiniCurrentFile(pyblish.api.ContextPlugin): "saved correctly." ) - context.data["currentFile"] = filepath + context.data["currentFile"] = current_file + + folder, file = os.path.split(current_file) + filename, ext = os.path.splitext(file) + + task = legacy_io.Session["AVALON_TASK"] + + data = {} + + # create instance + instance = context.create_instance(name=filename) + subset = 'workfile' + task.capitalize() + + data.update({ + "subset": subset, + "asset": os.getenv("AVALON_ASSET", None), + "label": subset, + "publish": True, + "family": 'workfile', + "families": ['workfile'], + "setMembers": [current_file], + "frameStart": context.data['frameStart'], + "frameEnd": context.data['frameEnd'], + "handleStart": context.data['handleStart'], + "handleEnd": context.data['handleEnd'] + }) + + data['representations'] = [{ + 'name': ext.lstrip("."), + 'ext': ext.lstrip("."), + 'files': file, + "stagingDir": folder, + }] + + instance.data.update(data) + + self.log.info('Collected instance: {}'.format(file)) + self.log.info('Scene path: {}'.format(current_file)) + self.log.info('staging Dir: {}'.format(folder)) + self.log.info('subset: {}'.format(subset)) From 74934a51b9ce7d581473426ef9206fad1ab4b486 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 16 Aug 2022 17:21:37 +0200 Subject: [PATCH 025/274] Nuke: removing audio inclusion from precollect write --- .../nuke/plugins/publish/precollect_writes.py | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/precollect_writes.py b/openpype/hosts/nuke/plugins/publish/precollect_writes.py index e37cc8a80a..17c4bc30cf 100644 --- a/openpype/hosts/nuke/plugins/publish/precollect_writes.py +++ b/openpype/hosts/nuke/plugins/publish/precollect_writes.py @@ -201,34 +201,6 @@ class CollectNukeWrites(pyblish.api.InstancePlugin): if not instance.data["review"]: instance.data["useSequenceForReview"] = False - project_name = legacy_io.active_project() - asset_name = instance.data["asset"] - # * Add audio to instance if exists. - # Find latest versions document - last_version_doc = get_last_version_by_subset_name( - project_name, "audioMain", asset_name=asset_name, fields=["_id"] - ) - - repre_doc = None - if last_version_doc: - # Try to find it's representation (Expected there is only one) - repre_docs = list(get_representations( - project_name, version_ids=[last_version_doc["_id"]] - )) - if not repre_docs: - self.log.warning( - "Version document does not contain any representations" - ) - else: - repre_doc = repre_docs[0] - - # Add audio to instance if representation was found - if repre_doc: - instance.data["audio"] = [{ - "offset": 0, - "filename": get_representation_path(repre_doc) - }] - self.log.debug("instance.data: {}".format(pformat(instance.data))) def is_prerender(self, families): From 737edadfd5166eb20197d818eefe2a1a22041b0d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 16 Aug 2022 17:23:42 +0200 Subject: [PATCH 026/274] global: preparation for settings attribute --- openpype/plugins/publish/collect_audio.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/collect_audio.py b/openpype/plugins/publish/collect_audio.py index 4ba47f739d..7e3b42f375 100644 --- a/openpype/plugins/publish/collect_audio.py +++ b/openpype/plugins/publish/collect_audio.py @@ -36,6 +36,8 @@ class CollectAudio(pyblish.api.InstancePlugin): "unreal" ] + audio_subset_name = "audioMain" + def process(self, instance): # * Add audio to instance if exists. self.log.info('Collecting Audio Data') @@ -45,7 +47,10 @@ class CollectAudio(pyblish.api.InstancePlugin): # Find latest versions document last_version_doc = get_last_version_by_subset_name( - project_name, "audioMain", asset_name=asset_name, fields=["_id"] + project_name, + self.audio_subset_name, + asset_name=asset_name, + fields=["_id"] ) repre_doc = None From 266975d6942156e71565a3847e4e62a9490dee71 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 16 Aug 2022 17:36:39 +0200 Subject: [PATCH 027/274] settings: adding collect audio plugin --- .../defaults/project_settings/global.json | 4 ++++ .../schemas/schema_global_publish.json | 21 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 0ff9363ba7..9258343440 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -3,6 +3,10 @@ "CollectAnatomyInstanceData": { "follow_workfile_version": false }, + "CollectAudio": { + "enabled": true, + "audio_subset_name": "audioMain" + }, "CollectSceneVersion": { "hosts": [ "aftereffects", diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index e1aa230b49..2efee92832 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -18,6 +18,27 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "checkbox_key": "enabled", + "key": "CollectAudio", + "label": "Collect Audio", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "key": "audio_subset_name", + "label": "Name of audio variant", + "type": "text", + "placeholder": "audioMain" + } + ] + }, { "type": "dict", "collapsible": true, From 87546e5a3c480be02d4ab5d316175ce11e28829a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 16 Aug 2022 18:49:00 +0200 Subject: [PATCH 028/274] :wrench: add repair action --- .../publish/validate_workfile_paths.py | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 openpype/hosts/houdini/plugins/publish/validate_workfile_paths.py diff --git a/openpype/hosts/houdini/plugins/publish/validate_workfile_paths.py b/openpype/hosts/houdini/plugins/publish/validate_workfile_paths.py new file mode 100644 index 0000000000..604d4af392 --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/validate_workfile_paths.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +import os +import openpype.api +import pyblish.api +import hou + + +class ValidateWorkfilePaths(pyblish.api.InstancePlugin): + """Validate workfile paths so they are absolute.""" + + order = pyblish.api.ValidatorOrder + families = ["workfile"] + hosts = ["houdini"] + label = "Validate Workfile Paths" + actions = [openpype.api.RepairAction] + optional = True + + node_types = ["file", "alembic"] + prohibited_vars = ["$HIP", "$JOB"] + + def process(self, instance): + invalid = self.get_invalid() + self.log.info( + "node types to check: {}".format(", ".join(self.node_types))) + self.log.info( + "prohibited vars: {}".format(", ".join(self.prohibited_vars)) + ) + if invalid: + for param in invalid: + self.log.error("{}: {}".format( + param.path(), + param.unexpandedString())) + + raise RuntimeError("Invalid paths found") + + @classmethod + def get_invalid(cls): + invalid = [] + for param, _ in hou.fileReferences(): + # skip nodes we are not interested in + if param.node().type().name() not in cls.node_types: + continue + + if any( + v for v in cls.prohibited_vars + if v in param.unexpandedString()): + invalid.append(param) + + return invalid + + @classmethod + def repair(cls, instance): + """Replace $HIP and $JOB vars for published path.""" + # determine path of published scene + anatomy = instance.context.data['anatomy'] + template_data = instance.data.get("anatomyData") + rep = instance.data.get("representations")[0].get("name") + template_data["representation"] = rep + template_data["ext"] = rep + template_data["comment"] = None + anatomy_filled = anatomy.format(template_data) + template_filled = anatomy_filled["publish"]["path"] + filepath = os.path.normpath(template_filled) + hip_dir = os.path.dirname(filepath) + invalid = cls.get_invalid() + for param in invalid: + cls.log.info("processing: {}".format(param.path())) + # replace $HIP + invalid_path = param.unexpandedString() + param.set(invalid_path.replace("$HIP", hip_dir)) + # replace $JOB + param.set(invalid_path.replace("$JOB", hip_dir)) + cls.log.info("Replacing {} for {}".format(invalid_path, hip_dir)) From e364c025e6b3b29e6c0dfecf8a029e42bb83d0fb Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 17 Aug 2022 10:34:58 +0200 Subject: [PATCH 029/274] Tweak labels for clearer distinction between the two actions --- openpype/hosts/maya/plugins/inventory/select_containers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/inventory/select_containers.py b/openpype/hosts/maya/plugins/inventory/select_containers.py index 4b7c92729f..d4a7ff401d 100644 --- a/openpype/hosts/maya/plugins/inventory/select_containers.py +++ b/openpype/hosts/maya/plugins/inventory/select_containers.py @@ -7,7 +7,7 @@ from openpype.hosts.maya.api.lib import get_container_members class SelectInScene(InventoryAction): """Select nodes in the scene from selected containers in scene inventory""" - label = "Select In Scene" + label = "Select in scene" icon = "search" color = "#888888" order = 99 @@ -21,10 +21,10 @@ class SelectInScene(InventoryAction): cmds.select(all_members, replace=True, noExpand=True) -class SelectFromScene(InventoryAction): +class HighlightBySceneSelection(InventoryAction): """Select containers in scene inventory from the current scene selection""" - label = "Select From Scene" + label = "Highlight by scene selection" icon = "search" color = "#888888" order = 100 From 98ac7f538e6482007115aff917cdf4ccd39fbc83 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 17 Aug 2022 11:59:15 +0200 Subject: [PATCH 030/274] condition for case where audio is already collected --- openpype/plugins/publish/collect_audio.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/plugins/publish/collect_audio.py b/openpype/plugins/publish/collect_audio.py index 7e3b42f375..6aed3f82fe 100644 --- a/openpype/plugins/publish/collect_audio.py +++ b/openpype/plugins/publish/collect_audio.py @@ -68,9 +68,10 @@ class CollectAudio(pyblish.api.InstancePlugin): # Add audio to instance if representation was found if repre_doc: - instance.data["audio"] = [{ - "offset": 0, - "filename": get_representation_path(repre_doc) - }] + if not instance.data.get("audio"): + instance.data["audio"] = [{ + "offset": 0, + "filename": get_representation_path(repre_doc) + }] self.log.debug("instance.data: {}".format(pformat(instance.data))) From ed2aedd0feec05d7d53f2e62789f77838a6f2f47 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 18 Aug 2022 10:17:02 +0200 Subject: [PATCH 031/274] processing PR comments --- openpype/plugins/publish/collect_audio.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/openpype/plugins/publish/collect_audio.py b/openpype/plugins/publish/collect_audio.py index 6aed3f82fe..cf074392ee 100644 --- a/openpype/plugins/publish/collect_audio.py +++ b/openpype/plugins/publish/collect_audio.py @@ -15,7 +15,7 @@ class CollectAudio(pyblish.api.InstancePlugin): """ Collecting available audio subset to instance """ - label = "Colect Audio" + label = "Collect Audio" order = pyblish.api.CollectorOrder + 0.1 families = ["review"] hosts = [ @@ -39,8 +39,14 @@ class CollectAudio(pyblish.api.InstancePlugin): audio_subset_name = "audioMain" def process(self, instance): - # * Add audio to instance if exists. - self.log.info('Collecting Audio Data') + if instance.data.get("audio"): + self.log.info( + "Skipping Audio collecion. It is already collected" + ) + return + + # Add audio to instance if exists. + self.log.info('Collecting Audio Data ...') project_name = legacy_io.active_project() asset_name = instance.data["asset"] @@ -68,10 +74,10 @@ class CollectAudio(pyblish.api.InstancePlugin): # Add audio to instance if representation was found if repre_doc: - if not instance.data.get("audio"): - instance.data["audio"] = [{ - "offset": 0, - "filename": get_representation_path(repre_doc) - }] + instance.data["audio"] = [{ + "offset": 0, + "filename": get_representation_path(repre_doc) + }] + self.log.info("Audio Data added to instance ...") self.log.debug("instance.data: {}".format(pformat(instance.data))) From 694a07579287b12b5fcce35fc4a405700ca3f64c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 18 Aug 2022 11:45:01 +0200 Subject: [PATCH 032/274] Refactor `subsetGroup` column name to `group` Co-authored-by: Milan Kolar --- openpype/tools/sceneinventory/model.py | 6 +++--- openpype/tools/sceneinventory/window.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py index 97cc11ff23..1a3b7c7055 100644 --- a/openpype/tools/sceneinventory/model.py +++ b/openpype/tools/sceneinventory/model.py @@ -35,7 +35,7 @@ class InventoryModel(TreeModel): """The model for the inventory""" Columns = ["Name", "version", "count", "family", - "subsetGroup", "loader", "objectName"] + "group", "loader", "objectName"] OUTDATED_COLOR = QtGui.QColor(235, 30, 30) CHILD_OUTDATED_COLOR = QtGui.QColor(200, 160, 30) @@ -160,7 +160,7 @@ class InventoryModel(TreeModel): column_name = self.Columns[index.column()] - if column_name == "subsetGroup" and item.get("subsetGroup"): + if column_name == "group" and item.get("group"): return qtawesome.icon("fa.object-group", color=get_default_entity_icon_color()) @@ -429,7 +429,7 @@ class InventoryModel(TreeModel): group_node["familyIcon"] = family_icon group_node["count"] = len(group_items) group_node["isGroupNode"] = True - group_node["subsetGroup"] = subset["data"].get("subsetGroup") + group_node["group"] = subset["data"].get("subsetGroup") if self.sync_enabled: progress = get_progress_for_repre( diff --git a/openpype/tools/sceneinventory/window.py b/openpype/tools/sceneinventory/window.py index 02addbccfe..1f4585b650 100644 --- a/openpype/tools/sceneinventory/window.py +++ b/openpype/tools/sceneinventory/window.py @@ -88,7 +88,7 @@ class SceneInventoryWindow(QtWidgets.QDialog): view.setColumnWidth(1, 55) # version view.setColumnWidth(2, 55) # count view.setColumnWidth(3, 150) # family - view.setColumnWidth(4, 120) # subsetGroup + view.setColumnWidth(4, 120) # group view.setColumnWidth(5, 150) # loader # apply delegates From 4a86093a824450a74e17fb8aa77d25ed23ede8e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 18 Aug 2022 16:15:19 +0200 Subject: [PATCH 033/274] :recycle: resolve current path to absolute --- .../publish/validate_workfile_paths.py | 23 ++++--------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/validate_workfile_paths.py b/openpype/hosts/houdini/plugins/publish/validate_workfile_paths.py index 604d4af392..9e087fe51c 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_workfile_paths.py +++ b/openpype/hosts/houdini/plugins/publish/validate_workfile_paths.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -import os import openpype.api import pyblish.api import hou @@ -50,24 +49,10 @@ class ValidateWorkfilePaths(pyblish.api.InstancePlugin): @classmethod def repair(cls, instance): - """Replace $HIP and $JOB vars for published path.""" - # determine path of published scene - anatomy = instance.context.data['anatomy'] - template_data = instance.data.get("anatomyData") - rep = instance.data.get("representations")[0].get("name") - template_data["representation"] = rep - template_data["ext"] = rep - template_data["comment"] = None - anatomy_filled = anatomy.format(template_data) - template_filled = anatomy_filled["publish"]["path"] - filepath = os.path.normpath(template_filled) - hip_dir = os.path.dirname(filepath) invalid = cls.get_invalid() for param in invalid: cls.log.info("processing: {}".format(param.path())) - # replace $HIP - invalid_path = param.unexpandedString() - param.set(invalid_path.replace("$HIP", hip_dir)) - # replace $JOB - param.set(invalid_path.replace("$JOB", hip_dir)) - cls.log.info("Replacing {} for {}".format(invalid_path, hip_dir)) + cls.log.info("Replacing {} for {}".format( + param.unexpandedString(), + hou.text.expandString(param.unexpandedString()))) + param.set(hou.text.expandString(param.unexpandedString())) From afd13c31698eac6b0d2d4347547361ca9be7e002 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 19 Aug 2022 18:59:37 +0200 Subject: [PATCH 034/274] :wrench: add settings --- .../defaults/project_settings/houdini.json | 12 +++++ .../schema_project_houdini.json | 18 +------ .../schemas/schema_houdini_publish.json | 50 +++++++++++++++++++ 3 files changed, 64 insertions(+), 16 deletions(-) create mode 100644 openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_publish.json diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index 911bf82d9b..b7d2104ba1 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -47,6 +47,18 @@ } }, "publish": { + "ValidateWorkfilePaths": { + "enabled": true, + "optional": true, + "node_types": [ + "file", + "alembic" + ], + "prohibited_vars": [ + "$HIP", + "$JOB" + ] + }, "ValidateContainers": { "enabled": true, "optional": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json index cad99dde22..d8728c0f4b 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json @@ -10,22 +10,8 @@ "name": "schema_houdini_create" }, { - "type": "dict", - "collapsible": true, - "key": "publish", - "label": "Publish plugins", - "children": [ - { - "type": "schema_template", - "name": "template_publish_plugin", - "template_data": [ - { - "key": "ValidateContainers", - "label": "ValidateContainers" - } - ] - } - ] + "type": "schema", + "name": "schema_houdini_publish" } ] } diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_publish.json new file mode 100644 index 0000000000..aa6eaf5164 --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_publish.json @@ -0,0 +1,50 @@ +{ + "type": "dict", + "collapsible": true, + "key": "publish", + "label": "Publish plugins", + "children": [ + { + "type": "dict", + "collapsible": true, + "checkbox_key": "enabled", + "key": "ValidateWorkfilePaths", + "label": "Validate Workfile Paths", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "key": "node_types", + "label": "Node types", + "type": "list", + "object_type": "text" + }, + { + "key": "prohibited_vars", + "label": "Prohibited variables", + "type": "list", + "object_type": "text" + } + ] + }, + { + "type": "schema_template", + "name": "template_publish_plugin", + "template_data": [ + { + "key": "ValidateContainers", + "label": "ValidateContainers" + } + ] + } + ] +} \ No newline at end of file From 24d733ecf36f6694f4861b112f1c0ecb1b27072a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 19 Aug 2022 19:09:42 +0200 Subject: [PATCH 035/274] :dog: fix hound --- .../hosts/houdini/plugins/publish/validate_workfile_paths.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/validate_workfile_paths.py b/openpype/hosts/houdini/plugins/publish/validate_workfile_paths.py index 9e087fe51c..79b3e894e5 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_workfile_paths.py +++ b/openpype/hosts/houdini/plugins/publish/validate_workfile_paths.py @@ -26,9 +26,8 @@ class ValidateWorkfilePaths(pyblish.api.InstancePlugin): ) if invalid: for param in invalid: - self.log.error("{}: {}".format( - param.path(), - param.unexpandedString())) + self.log.error( + "{}: {}".format(param.path(), param.unexpandedString())) raise RuntimeError("Invalid paths found") From 149b65b8b05750966ab5d93a5751e7085cf81652 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 23 Aug 2022 16:14:38 +0200 Subject: [PATCH 036/274] global: audio PR comments --- openpype/plugins/publish/collect_audio.py | 45 +++++++++++++++++------ 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/openpype/plugins/publish/collect_audio.py b/openpype/plugins/publish/collect_audio.py index cf074392ee..3a765d345d 100644 --- a/openpype/plugins/publish/collect_audio.py +++ b/openpype/plugins/publish/collect_audio.py @@ -12,10 +12,12 @@ from openpype.pipeline import ( class CollectAudio(pyblish.api.InstancePlugin): - """ Collecting available audio subset to instance + """Collect asset's last published audio. + The audio subset name searched for is defined in: + project settings > Collect Audio """ - label = "Collect Audio" + label = "Collect Asset Audio" order = pyblish.api.CollectorOrder + 0.1 families = ["review"] hosts = [ @@ -46,10 +48,33 @@ class CollectAudio(pyblish.api.InstancePlugin): return # Add audio to instance if exists. - self.log.info('Collecting Audio Data ...') + self.log.info(( + "Searching for audio subset '{subset}'" + " in asset '{asset}'" + ).format( + subset=self.audio_subset_name, + asset=instance.data["asset"] + )) + + repre_doc = self._get_repre_doc(instance) + + # Add audio to instance if representation was found + if repre_doc: + instance.data["audio"] = [{ + "offset": 0, + "filename": get_representation_path(repre_doc) + }] + self.log.info("Audio Data added to instance ...") + + def _get_repre_doc(self, instance): + cache = instance.context.data.get("__cache_asset_audio", {}) + asset_name = instance.data["asset"] + + # first try to get it from cache + if asset_name in cache: + return cache[asset_name] project_name = legacy_io.active_project() - asset_name = instance.data["asset"] # Find latest versions document last_version_doc = get_last_version_by_subset_name( @@ -72,12 +97,8 @@ class CollectAudio(pyblish.api.InstancePlugin): else: repre_doc = repre_docs[0] - # Add audio to instance if representation was found - if repre_doc: - instance.data["audio"] = [{ - "offset": 0, - "filename": get_representation_path(repre_doc) - }] - self.log.info("Audio Data added to instance ...") + # update cache + cache[asset_name] = repre_doc + instance.context.data["__cache_asset_audio"].update(cache) - self.log.debug("instance.data: {}".format(pformat(instance.data))) + return repre_doc From d2d90ed2e098587cd466243f5d666093ed1db55f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 23 Aug 2022 17:30:43 +0200 Subject: [PATCH 037/274] hound catch --- openpype/plugins/publish/collect_audio.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/plugins/publish/collect_audio.py b/openpype/plugins/publish/collect_audio.py index 3a765d345d..e2fb766ec4 100644 --- a/openpype/plugins/publish/collect_audio.py +++ b/openpype/plugins/publish/collect_audio.py @@ -1,5 +1,4 @@ import pyblish.api -from pprint import pformat from openpype.client import ( get_last_version_by_subset_name, From f9182cb0f9979179f4b0647e915d630e635b81e7 Mon Sep 17 00:00:00 2001 From: Kaa Maurice Date: Tue, 23 Aug 2022 17:58:19 +0200 Subject: [PATCH 038/274] remove unsaved changes check --- .../blender/plugins/publish/collect_current_file.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/collect_current_file.py b/openpype/hosts/blender/plugins/publish/collect_current_file.py index 1ca28f67f6..c3097a0694 100644 --- a/openpype/hosts/blender/plugins/publish/collect_current_file.py +++ b/openpype/hosts/blender/plugins/publish/collect_current_file.py @@ -13,11 +13,7 @@ class SaveWorkfiledAction(pyblish.api.Action): icon = "save" def process(self, context, plugin): - current_file = workio.current_file() - if current_file: - workio.save_file(current_file) - else: - bpy.ops.wm.avalon_workfiles() + bpy.ops.wm.avalon_workfiles() class CollectBlenderCurrentFile(pyblish.api.ContextPlugin): @@ -31,7 +27,6 @@ class CollectBlenderCurrentFile(pyblish.api.ContextPlugin): def process(self, context): """Inject the current working file""" current_file = workio.current_file() - has_unsaved_changes = workio.has_unsaved_changes() context.data["currentFile"] = current_file @@ -39,11 +34,6 @@ class CollectBlenderCurrentFile(pyblish.api.ContextPlugin): "Current file is empty. Save the file before continuing." ) - assert not has_unsaved_changes, ( - "Current file has unsaved changes. " - "Save the file before continuing." - ) - folder, file = os.path.split(current_file) filename, ext = os.path.splitext(file) From 65d785d100c986128a88f1dc2c77b5321b85d7da Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 24 Aug 2022 00:29:08 +0200 Subject: [PATCH 039/274] Draft stash for refactoring maya submit deadline to use `AbstractSubmitDeadline` base. - This does *NOT* work currently! --- .../deadline/abstract_submit_deadline.py | 22 + .../plugins/publish/submit_maya_deadline.py | 1297 ++++++++--------- 2 files changed, 623 insertions(+), 696 deletions(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index 0bad981fdf..577378335e 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -359,6 +359,27 @@ class DeadlineJobInfo(object): def OutputDirectory(self, val): # noqa: N802 self._outputDirectory.append(val) + # Asset Dependency + # ---------------------------------------------- + _assetDependency = attr.ib(factory=list) + + @property + def AssetDependency(self): # noqa: N802 + """Return all OutputDirectory values formatted for Deadline. + + Returns: + dict: as `{'OutputDirectory0': 'dir'}` + + """ + out = {} + for index, v in enumerate(self._assetDependency): + out["AssetDependency{}".format(index)] = v + return out + + @OutputDirectory.setter + def AssetDependency(self, val): # noqa: N802 + self._assetDependency.append(val) + # Tile Job # ---------------------------------------------- TileJob = attr.ib(default=None) # Default: false @@ -396,6 +417,7 @@ class DeadlineJobInfo(object): serialized.update(self.OutputFilename) serialized.update(self.OutputFilenameTile) serialized.update(self.OutputDirectory) + serialized.update(self.AssetDependency) return serialized diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 7966861358..6dfa48a9f8 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -18,7 +18,6 @@ Attributes: from __future__ import print_function import os -import json import getpass import copy import re @@ -27,252 +26,32 @@ from datetime import datetime import itertools from collections import OrderedDict +import attr import clique -import requests from maya import cmds -import pyblish.api - -from openpype.lib import requests_post from openpype.hosts.maya.api import lib from openpype.pipeline import legacy_io -# Documentation for keys available at: -# https://docs.thinkboxsoftware.com -# /products/deadline/8.0/1_User%20Manual/manual -# /manual-submission.html#job-info-file-options - -payload_skeleton_template = { - "JobInfo": { - "BatchName": None, # Top-level group name - "Name": None, # Job name, as seen in Monitor - "UserName": None, - "Plugin": "MayaBatch", - "Frames": "{start}-{end}x{step}", - "Comment": None, - "Priority": 50, - }, - "PluginInfo": { - "SceneFile": None, # Input - "OutputFilePath": None, # Output directory and filename - "OutputFilePrefix": None, - "Version": cmds.about(version=True), # Mandatory for Deadline - "UsingRenderLayers": True, - "RenderLayer": None, # Render only this layer - "Renderer": None, - "ProjectPath": None, # Resolve relative references - "RenderSetupIncludeLights": None, # Include all lights flag. - }, - "AuxFiles": [] # Mandatory for Deadline, may be empty -} +from openpype_modules.deadline import abstract_submit_deadline +from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo -def _format_tiles( - filename, index, tiles_x, tiles_y, - width, height, prefix): - """Generate tile entries for Deadline tile job. - - Returns two dictionaries - one that can be directly used in Deadline - job, second that can be used for Deadline Assembly job configuration - file. - - This will format tile names: - - Example:: - { - "OutputFilename0Tile0": "_tile_1x1_4x4_Main_beauty.1001.exr", - "OutputFilename0Tile1": "_tile_2x1_4x4_Main_beauty.1001.exr" - } - - And add tile prefixes like: - - Example:: - Image prefix is: - `maya///_` - - Result for tile 0 for 4x4 will be: - `maya///_tile_1x1_4x4__` - - Calculating coordinates is tricky as in Job they are defined as top, - left, bottom, right with zero being in top-left corner. But Assembler - configuration file takes tile coordinates as X, Y, Width and Height and - zero is bottom left corner. - - Args: - filename (str): Filename to process as tiles. - index (int): Index of that file if it is sequence. - tiles_x (int): Number of tiles in X. - tiles_y (int): Number if tikes in Y. - width (int): Width resolution of final image. - height (int): Height resolution of final image. - prefix (str): Image prefix. - - Returns: - (dict, dict): Tuple of two dictionaires - first can be used to - extend JobInfo, second has tiles x, y, width and height - used for assembler configuration. - - """ - tile = 0 - out = {"JobInfo": {}, "PluginInfo": {}} - cfg = OrderedDict() - w_space = width / tiles_x - h_space = height / tiles_y - - cfg["TilesCropped"] = "False" - - for tile_x in range(1, tiles_x + 1): - for tile_y in reversed(range(1, tiles_y + 1)): - tile_prefix = "_tile_{}x{}_{}x{}_".format( - tile_x, tile_y, - tiles_x, - tiles_y - ) - out_tile_index = "OutputFilename{}Tile{}".format( - str(index), tile - ) - new_filename = "{}/{}{}".format( - os.path.dirname(filename), - tile_prefix, - os.path.basename(filename) - ) - out["JobInfo"][out_tile_index] = new_filename - out["PluginInfo"]["RegionPrefix{}".format(str(tile))] = \ - "/{}".format(tile_prefix).join(prefix.rsplit("/", 1)) - - out["PluginInfo"]["RegionTop{}".format(tile)] = int(height) - (tile_y * h_space) # noqa: E501 - out["PluginInfo"]["RegionBottom{}".format(tile)] = int(height) - ((tile_y - 1) * h_space) - 1 # noqa: E501 - out["PluginInfo"]["RegionLeft{}".format(tile)] = (tile_x - 1) * w_space # noqa: E501 - out["PluginInfo"]["RegionRight{}".format(tile)] = (tile_x * w_space) - 1 # noqa: E501 - - cfg["Tile{}".format(tile)] = new_filename - cfg["Tile{}Tile".format(tile)] = new_filename - cfg["Tile{}FileName".format(tile)] = new_filename - cfg["Tile{}X".format(tile)] = (tile_x - 1) * w_space - - cfg["Tile{}Y".format(tile)] = int(height) - (tile_y * h_space) - - cfg["Tile{}Width".format(tile)] = w_space - cfg["Tile{}Height".format(tile)] = h_space - - tile += 1 - return out, cfg +@attr.s +class DeadlinePluginInfo(): + SceneFile = attr.ib(default=None) # Input + OutputFilePath = attr.ib(default=None) # Output directory and filename + OutputFilePrefix = attr.ib(default=None) + Version = attr.ib(default=None) # Mandatory for Deadline + UsingRenderLayers = attr.ib(default=True) + RenderLayer = attr.ib(default=None) # Render only this layer + Renderer = attr.ib(default=None) + ProjectPath = attr.ib(default=None) # Resolve relative references + RenderSetupIncludeLights = attr.ib(default=None) # Include all lights flag -def get_renderer_variables(renderlayer, root): - """Retrieve the extension which has been set in the VRay settings. - - Will return None if the current renderer is not VRay - For Maya 2016.5 and up the renderSetup creates renderSetupLayer node which - start with `rs`. Use the actual node name, do NOT use the `nice name` - - Args: - renderlayer (str): the node name of the renderlayer. - root (str): base path to render - - Returns: - dict - - """ - renderer = lib.get_renderer(renderlayer or lib.get_current_renderlayer()) - render_attrs = lib.RENDER_ATTRS.get(renderer, lib.RENDER_ATTRS["default"]) - - padding = cmds.getAttr("{}.{}".format(render_attrs["node"], - render_attrs["padding"])) - - filename_0 = cmds.renderSettings( - fullPath=True, - gin="#" * int(padding), - lut=True, - layer=renderlayer or lib.get_current_renderlayer())[0] - filename_0 = re.sub('_', '_beauty', - filename_0, flags=re.IGNORECASE) - prefix_attr = "defaultRenderGlobals.imageFilePrefix" - - scene = cmds.file(query=True, sceneName=True) - scene, _ = os.path.splitext(os.path.basename(scene)) - - if renderer == "vray": - renderlayer = renderlayer.split("_")[-1] - # Maya's renderSettings function does not return V-Ray file extension - # so we get the extension from vraySettings - extension = cmds.getAttr("vraySettings.imageFormatStr") - - # When V-Ray image format has not been switched once from default .png - # the getAttr command above returns None. As such we explicitly set - # it to `.png` - if extension is None: - extension = "png" - - if extension in ["exr (multichannel)", "exr (deep)"]: - extension = "exr" - - prefix_attr = "vraySettings.fileNamePrefix" - filename_prefix = cmds.getAttr(prefix_attr) - # we need to determine path for vray as maya `renderSettings` query - # does not work for vray. - - filename_0 = re.sub('', scene, filename_prefix, flags=re.IGNORECASE) # noqa: E501 - filename_0 = re.sub('', renderlayer, filename_0, flags=re.IGNORECASE) # noqa: E501 - filename_0 = "{}.{}.{}".format( - filename_0, "#" * int(padding), extension) - filename_0 = os.path.normpath(os.path.join(root, filename_0)) - elif renderer == "renderman": - prefix_attr = "rmanGlobals.imageFileFormat" - # NOTE: This is guessing extensions from renderman display types. - # Some of them are just framebuffers, d_texture format can be - # set in display setting. We set those now to None, but it - # should be handled more gracefully. - display_types = { - "d_deepexr": "exr", - "d_it": None, - "d_null": None, - "d_openexr": "exr", - "d_png": "png", - "d_pointcloud": "ptc", - "d_targa": "tga", - "d_texture": None, - "d_tiff": "tif" - } - - extension = display_types.get( - cmds.listConnections("rmanDefaultDisplay.displayType")[0], - "exr" - ) or "exr" - - filename_prefix = "{}/{}".format( - cmds.getAttr("rmanGlobals.imageOutputDir"), - cmds.getAttr("rmanGlobals.imageFileFormat") - ) - - renderlayer = renderlayer.split("_")[-1] - - filename_0 = re.sub('', scene, filename_prefix, flags=re.IGNORECASE) # noqa: E501 - filename_0 = re.sub('', renderlayer, filename_0, flags=re.IGNORECASE) # noqa: E501 - filename_0 = re.sub('', "#" * int(padding), filename_0, flags=re.IGNORECASE) # noqa: E501 - filename_0 = re.sub('', extension, filename_0, flags=re.IGNORECASE) # noqa: E501 - filename_0 = os.path.normpath(os.path.join(root, filename_0)) - elif renderer == "redshift": - # mapping redshift extension dropdown values to strings - ext_mapping = ["iff", "exr", "tif", "png", "tga", "jpg"] - extension = ext_mapping[ - cmds.getAttr("redshiftOptions.imageFormat") - ] - else: - # Get the extension, getAttr defaultRenderGlobals.imageFormat - # returns an index number. - filename_base = os.path.basename(filename_0) - extension = os.path.splitext(filename_base)[-1].strip(".") - - filename_prefix = cmds.getAttr(prefix_attr) - return {"ext": extension, - "filename_prefix": filename_prefix, - "padding": padding, - "filename_0": filename_0} - - -class MayaSubmitDeadline(pyblish.api.InstancePlugin): +class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): """Submit available render layers to Deadline. Renders are submitted to a Deadline Web Service as @@ -284,15 +63,12 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): """ - label = "Submit to Deadline" - order = pyblish.api.IntegratorOrder + 0.1 + label = "Submit Render to Deadline" hosts = ["maya"] families = ["renderlayer"] targets = ["local"] - use_published = True tile_assembler_plugin = "OpenPypeTileAssembler" - asset_dependencies = False priority = 50 tile_priority = 50 limit_groups = [] @@ -300,32 +76,173 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): pluginInfo = {} group = "none" - def process(self, instance): - """Plugin entry point.""" - instance.data["toBeRenderedOn"] = "deadline" + def get_job_info(self): + job_info = DeadlineJobInfo(Plugin="MayaBatch") + + # todo: test whether this works for existing production cases + # where custom jobInfo was stored in the project settings + for key, value in self.jobInfo.items(): + setattr(job_info, key, value) + + instance = self._instance context = instance.context - self._instance = instance - self.payload_skeleton = copy.deepcopy(payload_skeleton_template) + filepath = context.data["currentFile"] + filename = os.path.basename(filepath) - # get default deadline webservice url from deadline module - self.deadline_url = instance.context.data.get("defaultDeadline") - # if custom one is set in instance, use that - if instance.data.get("deadlineUrl"): - self.deadline_url = instance.data.get("deadlineUrl") - assert self.deadline_url, "Requires Deadline Webservice URL" + job_info.Name = "%s - %s" % (filename, instance.name) + job_info.BatchName = filename + job_info.Plugin = instance.data.get("mayaRenderPlugin", "MayaBatch") + job_info.UserName = context.data.get( + "deadlineUser", getpass.getuser()) - # just using existing names from Setting - self._job_info = self.jobInfo + # Deadline requires integers in frame range + frames = "{start}-{end}x{step}".format( + start=int(instance.data["frameStartHandle"]), + end=int(instance.data["frameEndHandle"]), + step=int(instance.data["byFrameStep"]), + ) + job_info.Frames = frames - self._plugin_info = self.pluginInfo + job_info.Pool = instance.data.get("primaryPool") + job_info.SecondaryPool = instance.data.get("secondaryPool") + job_info.ChunkSize = instance.data.get("chunkSize", 10) + job_info.Comment = context.data.get("comment") + job_info.Priority = instance.data.get("priority", self.priority) + + if self.group != "none" and self.group: + job_info.Group = self.group + + if self.limit_groups: + job_info.LimitGroups = ",".join(self.limit_groups) + + self.payload_skeleton["JobInfo"]["Name"] = jobname + self.payload_skeleton["JobInfo"]["BatchName"] = src_filename + + # Optional, enable double-click to preview rendered + # frames from Deadline Monitor + self.payload_skeleton["JobInfo"]["OutputDirectory0"] = \ + os.path.dirname(output_filename_0).replace("\\", "/") + self.payload_skeleton["JobInfo"]["OutputFilename0"] = \ + output_filename_0.replace("\\", "/") + + # Add options from RenderGlobals------------------------------------- + render_globals = instance.data.get("renderGlobals", {}) + self.payload_skeleton["JobInfo"].update(render_globals) + + keys = [ + "FTRACK_API_KEY", + "FTRACK_API_USER", + "FTRACK_SERVER", + "OPENPYPE_SG_USER", + "AVALON_PROJECT", + "AVALON_ASSET", + "AVALON_TASK", + "AVALON_APP_NAME", + "OPENPYPE_DEV", + "OPENPYPE_LOG_NO_COLORS", + "OPENPYPE_VERSION" + ] + # Add mongo url if it's enabled + if self._instance.context.data.get("deadlinePassMongoUrl"): + keys.append("OPENPYPE_MONGO") + + environment = dict({key: os.environ[key] for key in keys + if key in os.environ}, **legacy_io.Session) + + + # TODO: Taken from old publish class - test whether still needed + environment["OPENPYPE_LOG_NO_COLORS"] = "1" + environment["OPENPYPE_MAYA_VERSION"] = cmds.about(v=True) + # to recognize job from PYPE for turning Event On/Off + environment["OPENPYPE_RENDER_JOB"] = "1" + + for key in keys: + val = environment.get(key) + if val: + job_info.EnvironmentKeyValue = "{key}={value}".format( + key=key, + value=val + ) + # to recognize job from PYPE for turning Event On/Off + job_info.EnvironmentKeyValue = "OPENPYPE_RENDER_JOB=1" + + for i, filepath in enumerate(instance.data["files"]): + dirname = os.path.dirname(filepath) + fname = os.path.basename(filepath) + job_info.OutputDirectory = dirname.replace("\\", "/") + job_info.OutputFilename = fname + + # Adding file dependencies. + if self.asset_dependencies: + dependencies = instance.context.data["fileDependencies"] + dependencies.append(context.data["currentFile"]) + for dependency in dependencies: + job_info.AssetDependency = dependency + + # Add list of expected files to job + # --------------------------------- + exp = instance.data.get("expectedFiles") + + def _get_output_filename(files): + col, rem = clique.assemble(files) + if not col and rem: + # we couldn't find any collections but have + # individual files. + assert len(rem) == 1, ( + "Found multiple non related files " + "to render, don't know what to do " + "with them.") + return rem[0] + else: + return col[0].format('{head}{padding}{tail}') + + if isinstance(exp[0], dict): + # we have aovs and we need to iterate over them + for _aov, files in exp[0].items(): + output_file = _get_output_filename(files) + job_info.OutputFilename = output_file + else: + output_file = _get_output_filename(exp) + job_info.OutputFilename = output_file + + return job_info + + def get_plugin_info(self): + + instance = self._instance + context = instance.context + + renderlayer = instance.data['setMembers'] # rs_beauty + + self.payload_skeleton["PluginInfo"]["RenderLayer"] = renderlayer + self.payload_skeleton["PluginInfo"]["RenderSetupIncludeLights"] = instance.data.get("renderSetupIncludeLights") # noqa + + # Output driver to render + plugin_info = DeadlinePluginInfo( + SceneFile=context.data["currentFile"], + Version=cmds.about(version=True), + ) + + return attr.asdict(plugin_info) + + def process_submission(self): + # Override to NOT submit by default when calling super process() method + pass + + def process(self, instance): + super(MayaSubmitDeadline, self).process(instance) + + # TODO: Avoid the need for this logic here, needed for submit publish + # Store output dir for unified publisher (filesequence) + output_dir = os.path.dirname(instance.data["files"][0]) + instance.data["outputDir"] = output_dir + instance.data["toBeRenderedOn"] = "deadline" self.limit_groups = self.limit context = instance.context workspace = context.data["workspaceDir"] - anatomy = context.data['anatomy'] - instance.data["toBeRenderedOn"] = "deadline" filepath = None patches = ( @@ -336,80 +253,24 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): "scene_patches", {}) ) - # Handle render/export from published scene or not ------------------ - if self.use_published: - patched_files = [] - for i in context: - if "workfile" not in i.data["families"]: - continue - assert i.data["publish"] is True, ( - "Workfile (scene) must be published along") - template_data = i.data.get("anatomyData") - rep = i.data.get("representations")[0].get("name") - template_data["representation"] = rep - template_data["ext"] = rep - template_data["comment"] = None - anatomy_filled = anatomy.format(template_data) - template_filled = anatomy_filled["publish"]["path"] - filepath = os.path.normpath(template_filled) - self.log.info("Using published scene for render {}".format( - filepath)) + # todo: on self.use_published originally use template_data["representation"] using .get("name") instead of .get("ext") + # todo: on self.use_published replace path for publishRenderMetadataFolder + # todo: on self.use_published apply scene patches to workfile instance + # rep = i.data.get("representations")[0].get("name") - if not os.path.exists(filepath): - self.log.error("published scene does not exist!") - raise - # now we need to switch scene in expected files - # because token will now point to published - # scene file and that might differ from current one - new_scene = os.path.splitext( - os.path.basename(filepath))[0] - orig_scene = os.path.splitext( - os.path.basename(context.data["currentFile"]))[0] - exp = instance.data.get("expectedFiles") + # if instance.data.get("publishRenderMetadataFolder"): + # instance.data["publishRenderMetadataFolder"] = \ + # instance.data["publishRenderMetadataFolder"].replace( + # orig_scene, new_scene) + # self.log.info("Scene name was switched {} -> {}".format( + # orig_scene, new_scene + # )) + # # patch workfile is needed + # if filepath not in patched_files: + # patched_file = self._patch_workfile(filepath, patches) + # patched_files.append(patched_file) - if isinstance(exp[0], dict): - # we have aovs and we need to iterate over them - new_exp = {} - for aov, files in exp[0].items(): - replaced_files = [] - for f in files: - replaced_files.append( - f.replace(orig_scene, new_scene) - ) - new_exp[aov] = replaced_files - instance.data["expectedFiles"] = [new_exp] - else: - new_exp = [] - for f in exp: - new_exp.append( - f.replace(orig_scene, new_scene) - ) - instance.data["expectedFiles"] = [new_exp] - - if instance.data.get("publishRenderMetadataFolder"): - instance.data["publishRenderMetadataFolder"] = \ - instance.data["publishRenderMetadataFolder"].replace( - orig_scene, new_scene) - self.log.info("Scene name was switched {} -> {}".format( - orig_scene, new_scene - )) - # patch workfile is needed - if filepath not in patched_files: - patched_file = self._patch_workfile(filepath, patches) - patched_files.append(patched_file) - - all_instances = [] - for result in context.data["results"]: - if (result["instance"] is not None and - result["instance"] not in all_instances): # noqa: E128 - all_instances.append(result["instance"]) - - # fallback if nothing was set - if not filepath: - self.log.warning("Falling back to workfile") - filepath = context.data["currentFile"] - - self.log.debug(filepath) + filepath = self.scene_path # collect by super().process # Gather needed data ------------------------------------------------ default_render_file = instance.context.data.get('project_settings')\ @@ -417,10 +278,8 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): .get('RenderSettings')\ .get('default_render_image_folder') filename = os.path.basename(filepath) - comment = context.data.get("comment", "") dirname = os.path.join(workspace, default_render_file) renderlayer = instance.data['setMembers'] # rs_beauty - deadline_user = context.data.get("user", getpass.getuser()) # Always use the original work file name for the Job name even when # rendering is done from the published Work File. The original work @@ -454,116 +313,34 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): pass # Fill in common data to payload ------------------------------------ - payload_data = {} - payload_data["filename"] = filename - payload_data["filepath"] = filepath - payload_data["jobname"] = jobname - payload_data["deadline_user"] = deadline_user - payload_data["comment"] = comment - payload_data["output_filename_0"] = output_filename_0 - payload_data["render_variables"] = render_variables - payload_data["renderlayer"] = renderlayer - payload_data["workspace"] = workspace - payload_data["dirname"] = dirname - - self.log.info("--- Submission data:") - for k, v in payload_data.items(): - self.log.info("- {}: {}".format(k, v)) - self.log.info("-" * 20) - - frame_pattern = self.payload_skeleton["JobInfo"]["Frames"] - self.payload_skeleton["JobInfo"]["Frames"] = frame_pattern.format( - start=int(self._instance.data["frameStartHandle"]), - end=int(self._instance.data["frameEndHandle"]), - step=int(self._instance.data["byFrameStep"])) - - self.payload_skeleton["JobInfo"]["Plugin"] = self._instance.data.get( - "mayaRenderPlugin", "MayaBatch") - - self.payload_skeleton["JobInfo"]["BatchName"] = src_filename - # Job name, as seen in Monitor - self.payload_skeleton["JobInfo"]["Name"] = jobname - # Arbitrary username, for visualisation in Monitor - self.payload_skeleton["JobInfo"]["UserName"] = deadline_user - # Set job priority - self.payload_skeleton["JobInfo"]["Priority"] = \ - self._instance.data.get("priority", self.priority) - - if self.group != "none" and self.group: - self.payload_skeleton["JobInfo"]["Group"] = self.group - - if self.limit_groups: - self.payload_skeleton["JobInfo"]["LimitGroups"] = \ - ",".join(self.limit_groups) - # Optional, enable double-click to preview rendered - # frames from Deadline Monitor - self.payload_skeleton["JobInfo"]["OutputDirectory0"] = \ - os.path.dirname(output_filename_0).replace("\\", "/") - self.payload_skeleton["JobInfo"]["OutputFilename0"] = \ - output_filename_0.replace("\\", "/") - - self.payload_skeleton["JobInfo"]["Comment"] = comment - self.payload_skeleton["PluginInfo"]["RenderLayer"] = renderlayer - - self.payload_skeleton["PluginInfo"]["RenderSetupIncludeLights"] = instance.data.get("renderSetupIncludeLights") # noqa - # Adding file dependencies. - dependencies = instance.context.data["fileDependencies"] - dependencies.append(filepath) - if self.asset_dependencies: - for dependency in dependencies: - key = "AssetDependency" + str(dependencies.index(dependency)) - self.payload_skeleton["JobInfo"][key] = dependency - - # Handle environments ----------------------------------------------- - # We need those to pass them to pype for it to set correct context - keys = [ - "FTRACK_API_KEY", - "FTRACK_API_USER", - "FTRACK_SERVER", - "OPENPYPE_SG_USER", - "AVALON_PROJECT", - "AVALON_ASSET", - "AVALON_TASK", - "AVALON_APP_NAME", - "OPENPYPE_DEV", - "OPENPYPE_LOG_NO_COLORS", - "OPENPYPE_VERSION" - ] - # Add mongo url if it's enabled - if instance.context.data.get("deadlinePassMongoUrl"): - keys.append("OPENPYPE_MONGO") - - environment = dict({key: os.environ[key] for key in keys - if key in os.environ}, **legacy_io.Session) - environment["OPENPYPE_LOG_NO_COLORS"] = "1" - environment["OPENPYPE_MAYA_VERSION"] = cmds.about(v=True) - # to recognize job from PYPE for turning Event On/Off - environment["OPENPYPE_RENDER_JOB"] = "1" - self.payload_skeleton["JobInfo"].update({ - "EnvironmentKeyValue%d" % index: "{key}={value}".format( - key=key, - value=environment[key] - ) for index, key in enumerate(environment) - }) - # Add options from RenderGlobals------------------------------------- - render_globals = instance.data.get("renderGlobals", {}) - self.payload_skeleton["JobInfo"].update(render_globals) + payload_data = { + "filename": filename, + "filepath": filepath, + "jobname": jobname, + "comment": comment, + "output_filename_0": output_filename_0, + "render_variables": render_variables, + "renderlayer": renderlayer, + "workspace": workspace, + "dirname": dirname, + } # Submit preceding export jobs ------------------------------------- export_job = None assert not all(x in instance.data["families"] for x in ['vrayscene', 'assscene']), ( "Vray Scene and Ass Scene options are mutually exclusive") - if "vrayscene" in instance.data["families"]: - export_job = self._submit_export(payload_data, "vray") - if "assscene" in instance.data["families"]: - export_job = self._submit_export(payload_data, "arnold") - - # Prepare main render job ------------------------------------------- if "vrayscene" in instance.data["families"]: + vray_export_payload = self._get_vray_export_payload(payload_data) + export_job = self.submit(vray_export_payload) + payload = self._get_vray_render_payload(payload_data) + elif "assscene" in instance.data["families"]: + ass_export_payload = self._get_arnold_export_payload(payload_data) + export_job = self.submit(ass_export_payload) + payload = self._get_arnold_render_payload(payload_data) else: payload = self._get_maya_payload(payload_data) @@ -572,267 +349,222 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): if export_job: payload["JobInfo"]["JobDependency0"] = export_job - # Add list of expected files to job --------------------------------- - exp = instance.data.get("expectedFiles") - exp_index = 0 - output_filenames = {} - - if isinstance(exp[0], dict): - # we have aovs and we need to iterate over them - for _aov, files in exp[0].items(): - col, rem = clique.assemble(files) - if not col and rem: - # we couldn't find any collections but have - # individual files. - assert len(rem) == 1, ("Found multiple non related files " - "to render, don't know what to do " - "with them.") - output_file = rem[0] - if not instance.data.get("tileRendering"): - payload['JobInfo']['OutputFilename' + str(exp_index)] = output_file # noqa: E501 - else: - output_file = col[0].format('{head}{padding}{tail}') - if not instance.data.get("tileRendering"): - payload['JobInfo']['OutputFilename' + str(exp_index)] = output_file # noqa: E501 - - output_filenames['OutputFilename' + str(exp_index)] = output_file # noqa: E501 - exp_index += 1 - else: - col, rem = clique.assemble(exp) - if not col and rem: - # we couldn't find any collections but have - # individual files. - assert len(rem) == 1, ("Found multiple non related files " - "to render, don't know what to do " - "with them.") - - output_file = rem[0] - if not instance.data.get("tileRendering"): - payload['JobInfo']['OutputFilename' + str(exp_index)] = output_file # noqa: E501 - else: - output_file = col[0].format('{head}{padding}{tail}') - if not instance.data.get("tileRendering"): - payload['JobInfo']['OutputFilename' + str(exp_index)] = output_file # noqa: E501 - - output_filenames['OutputFilename' + str(exp_index)] = output_file - plugin = payload["JobInfo"]["Plugin"] self.log.info("using render plugin : {}".format(plugin)) # Store output dir for unified publisher (filesequence) instance.data["outputDir"] = os.path.dirname(output_filename_0) - self.preflight_check(instance) - # add jobInfo and pluginInfo variables from Settings - payload["JobInfo"].update(self._job_info) - payload["PluginInfo"].update(self._plugin_info) + payload["JobInfo"].update(self.jobInfo) + payload["PluginInfo"].update(self.pluginInfo) - # Prepare tiles data ------------------------------------------------ if instance.data.get("tileRendering"): - # if we have sequence of files, we need to create tile job for - # every frame + # Prepare tiles data + self._tile_render(instance, payload) + else: + # Submit main render job + self.submit(payload) - payload["JobInfo"]["TileJob"] = True - payload["JobInfo"]["TileJobTilesInX"] = instance.data.get("tilesX") - payload["JobInfo"]["TileJobTilesInY"] = instance.data.get("tilesY") - payload["PluginInfo"]["ImageHeight"] = instance.data.get("resolutionHeight") # noqa: E501 - payload["PluginInfo"]["ImageWidth"] = instance.data.get("resolutionWidth") # noqa: E501 - payload["PluginInfo"]["RegionRendering"] = True + def _tile_render(self, instance, payload): - assembly_payload = { - "AuxFiles": [], - "JobInfo": { - "BatchName": payload["JobInfo"]["BatchName"], - "Frames": 1, - "Name": "{} - Tile Assembly Job".format( - payload["JobInfo"]["Name"]), - "OutputDirectory0": - payload["JobInfo"]["OutputDirectory0"].replace( - "\\", "/"), - "Plugin": self.tile_assembler_plugin, - "MachineLimit": 1 - }, - "PluginInfo": { - "CleanupTiles": 1, - "ErrorOnMissing": True - } + # As collected by super process() + job_info = self.job_info + plugin_info = self.pluginInfo + + # if we have sequence of files, we need to create tile job for + # every frame + + job_info.TileJob = True + job_info.TileJobTilesInX = instance.data.get("tilesX") + job_info.TileJobTilesInY = instance.data.get("tilesY") + + plugin_info["ImageHeight"] = instance.data.get("resolutionHeight") + plugin_info["ImageWidth"] = instance.data.get("resolutionWidth") + plugin_info["RegionRendering"] = True + + assembly_payload = { + "AuxFiles": [], + "JobInfo": { + "BatchName": payload["JobInfo"]["BatchName"], + "Frames": 1, + "Name": "{} - Tile Assembly Job".format( + payload["JobInfo"]["Name"]), + "OutputDirectory0": + payload["JobInfo"]["OutputDirectory0"].replace( + "\\", "/"), + "Plugin": self.tile_assembler_plugin, + "MachineLimit": 1 + }, + "PluginInfo": { + "CleanupTiles": 1, + "ErrorOnMissing": True } - assembly_payload["JobInfo"].update(output_filenames) - assembly_payload["JobInfo"]["Priority"] = self._instance.data.get( - "tile_priority", self.tile_priority) - assembly_payload["JobInfo"]["UserName"] = deadline_user + } + assembly_payload["JobInfo"]["Priority"] = self._instance.data.get( + "tile_priority", self.tile_priority) - frame_payloads = [] - assembly_payloads = [] + frame_payloads = [] + assembly_payloads = [] - R_FRAME_NUMBER = re.compile(r".+\.(?P[0-9]+)\..+") # noqa: N806, E501 - REPL_FRAME_NUMBER = re.compile(r"(.+\.)([0-9]+)(\..+)") # noqa: N806, E501 + R_FRAME_NUMBER = re.compile( + r".+\.(?P[0-9]+)\..+") # noqa: N806, E501 + REPL_FRAME_NUMBER = re.compile( + r"(.+\.)([0-9]+)(\..+)") # noqa: N806, E501 - if isinstance(exp[0], dict): - # we have aovs and we need to iterate over them - # get files from `beauty` - files = exp[0].get("beauty") - # assembly files are used for assembly jobs as we need to put - # together all AOVs - assembly_files = list( - itertools.chain.from_iterable( - [f for _, f in exp[0].items()])) - if not files: - # if beauty doesn't exists, use first aov we found - files = exp[0].get(list(exp[0].keys())[0]) - else: - files = exp - assembly_files = files + exp = instance.data["expectedFiles"] + if isinstance(exp[0], dict): + # we have aovs and we need to iterate over them + # get files from `beauty` + files = exp[0].get("beauty") + # assembly files are used for assembly jobs as we need to put + # together all AOVs + assembly_files = list( + itertools.chain.from_iterable( + [f for _, f in exp[0].items()])) + if not files: + # if beauty doesn't exists, use first aov we found + files = exp[0].get(list(exp[0].keys())[0]) + else: + files = exp + assembly_files = files - frame_jobs = {} + frame_jobs = {} - file_index = 1 - for file in files: - frame = re.search(R_FRAME_NUMBER, file).group("frame") - new_payload = copy.deepcopy(payload) - new_payload["JobInfo"]["Name"] = \ - "{} (Frame {} - {} tiles)".format( - payload["JobInfo"]["Name"], - frame, - instance.data.get("tilesX") * instance.data.get("tilesY") # noqa: E501 + file_index = 1 + for file in files: + frame = re.search(R_FRAME_NUMBER, file).group("frame") + new_payload = copy.deepcopy(payload) + new_payload["JobInfo"]["Name"] = \ + "{} (Frame {} - {} tiles)".format( + payload["JobInfo"]["Name"], + frame, + instance.data.get("tilesX") * instance.data.get("tilesY") + # noqa: E501 ) - self.log.info( - "... preparing job {}".format( - new_payload["JobInfo"]["Name"])) - new_payload["JobInfo"]["TileJobFrame"] = frame + self.log.info( + "... preparing job {}".format( + new_payload["JobInfo"]["Name"])) + new_payload["JobInfo"]["TileJobFrame"] = frame - tiles_data = _format_tiles( + tiles_data = _format_tiles( + file, 0, + instance.data.get("tilesX"), + instance.data.get("tilesY"), + instance.data.get("resolutionWidth"), + instance.data.get("resolutionHeight"), + payload["PluginInfo"]["OutputFilePrefix"] + )[0] + new_payload["JobInfo"].update(tiles_data["JobInfo"]) + new_payload["PluginInfo"].update(tiles_data["PluginInfo"]) + + self.log.info("hashing {} - {}".format(file_index, file)) + job_hash = hashlib.sha256( + ("{}_{}".format(file_index, file)).encode("utf-8")) + frame_jobs[frame] = job_hash.hexdigest() + new_payload["JobInfo"]["ExtraInfo0"] = job_hash.hexdigest() + new_payload["JobInfo"]["ExtraInfo1"] = file + + frame_payloads.append(new_payload) + file_index += 1 + + file_index = 1 + for file in assembly_files: + frame = re.search(R_FRAME_NUMBER, file).group("frame") + new_assembly_payload = copy.deepcopy(assembly_payload) + new_assembly_payload["JobInfo"]["Name"] = \ + "{} (Frame {})".format( + assembly_payload["JobInfo"]["Name"], + frame) + new_assembly_payload["JobInfo"]["OutputFilename0"] = re.sub( + REPL_FRAME_NUMBER, + "\\1{}\\3".format("#" * len(frame)), file) + + new_assembly_payload["PluginInfo"]["Renderer"] = \ + self._instance.data["renderer"] # noqa: E501 + new_assembly_payload["JobInfo"]["ExtraInfo0"] = frame_jobs[ + frame] # noqa: E501 + new_assembly_payload["JobInfo"]["ExtraInfo1"] = file + assembly_payloads.append(new_assembly_payload) + file_index += 1 + + self.log.info( + "Submitting tile job(s) [{}] ...".format(len(frame_payloads))) + + url = "{}/api/jobs".format(self.deadline_url) + tiles_count = instance.data.get("tilesX") * instance.data.get( + "tilesY") # noqa: E501 + + for tile_job in frame_payloads: + response = self.submit(tile_job) + + job_id = response.json()["_id"] + hash = response.json()["Props"]["Ex0"] + + for assembly_job in assembly_payloads: + if assembly_job["JobInfo"]["ExtraInfo0"] == hash: + assembly_job["JobInfo"]["JobDependency0"] = job_id + + for assembly_job in assembly_payloads: + file = assembly_job["JobInfo"]["ExtraInfo1"] + # write assembly job config files + now = datetime.now() + + config_file = os.path.join( + os.path.dirname(output_filename_0), + "{}_config_{}.txt".format( + os.path.splitext(file)[0], + now.strftime("%Y_%m_%d_%H_%M_%S") + ) + ) + + try: + if not os.path.isdir(os.path.dirname(config_file)): + os.makedirs(os.path.dirname(config_file)) + except OSError: + # directory is not available + self.log.warning( + "Path is unreachable: `{}`".format( + os.path.dirname(config_file))) + + # add config file as job auxFile + assembly_job["AuxFiles"] = [config_file] + + with open(config_file, "w") as cf: + print("TileCount={}".format(tiles_count), file=cf) + print("ImageFileName={}".format(file), file=cf) + print("ImageWidth={}".format( + instance.data.get("resolutionWidth")), file=cf) + print("ImageHeight={}".format( + instance.data.get("resolutionHeight")), file=cf) + + tiles = _format_tiles( file, 0, instance.data.get("tilesX"), instance.data.get("tilesY"), instance.data.get("resolutionWidth"), instance.data.get("resolutionHeight"), payload["PluginInfo"]["OutputFilePrefix"] - )[0] - new_payload["JobInfo"].update(tiles_data["JobInfo"]) - new_payload["PluginInfo"].update(tiles_data["PluginInfo"]) + )[1] + sorted(tiles) + for k, v in tiles.items(): + print("{}={}".format(k, v), file=cf) - self.log.info("hashing {} - {}".format(file_index, file)) - job_hash = hashlib.sha256( - ("{}_{}".format(file_index, file)).encode("utf-8")) - frame_jobs[frame] = job_hash.hexdigest() - new_payload["JobInfo"]["ExtraInfo0"] = job_hash.hexdigest() - new_payload["JobInfo"]["ExtraInfo1"] = file + job_idx = 1 + instance.data["assemblySubmissionJobs"] = [] + for ass_job in assembly_payloads: + self.log.info("submitting assembly job {} of {}".format( + job_idx, len(assembly_payloads) + )) + response = self.submit(ass_job) - frame_payloads.append(new_payload) - file_index += 1 + instance.data["assemblySubmissionJobs"].append( + response.json()["_id"]) + job_idx += 1 - file_index = 1 - for file in assembly_files: - frame = re.search(R_FRAME_NUMBER, file).group("frame") - new_assembly_payload = copy.deepcopy(assembly_payload) - new_assembly_payload["JobInfo"]["Name"] = \ - "{} (Frame {})".format( - assembly_payload["JobInfo"]["Name"], - frame) - new_assembly_payload["JobInfo"]["OutputFilename0"] = re.sub( - REPL_FRAME_NUMBER, - "\\1{}\\3".format("#" * len(frame)), file) - - new_assembly_payload["PluginInfo"]["Renderer"] = self._instance.data["renderer"] # noqa: E501 - new_assembly_payload["JobInfo"]["ExtraInfo0"] = frame_jobs[frame] # noqa: E501 - new_assembly_payload["JobInfo"]["ExtraInfo1"] = file - assembly_payloads.append(new_assembly_payload) - file_index += 1 - - self.log.info( - "Submitting tile job(s) [{}] ...".format(len(frame_payloads))) - - url = "{}/api/jobs".format(self.deadline_url) - tiles_count = instance.data.get("tilesX") * instance.data.get("tilesY") # noqa: E501 - - for tile_job in frame_payloads: - response = requests_post(url, json=tile_job) - if not response.ok: - raise Exception(response.text) - - job_id = response.json()["_id"] - hash = response.json()["Props"]["Ex0"] - - for assembly_job in assembly_payloads: - if assembly_job["JobInfo"]["ExtraInfo0"] == hash: - assembly_job["JobInfo"]["JobDependency0"] = job_id - - for assembly_job in assembly_payloads: - file = assembly_job["JobInfo"]["ExtraInfo1"] - # write assembly job config files - now = datetime.now() - - config_file = os.path.join( - os.path.dirname(output_filename_0), - "{}_config_{}.txt".format( - os.path.splitext(file)[0], - now.strftime("%Y_%m_%d_%H_%M_%S") - ) - ) - - try: - if not os.path.isdir(os.path.dirname(config_file)): - os.makedirs(os.path.dirname(config_file)) - except OSError: - # directory is not available - self.log.warning( - "Path is unreachable: `{}`".format( - os.path.dirname(config_file))) - - # add config file as job auxFile - assembly_job["AuxFiles"] = [config_file] - - with open(config_file, "w") as cf: - print("TileCount={}".format(tiles_count), file=cf) - print("ImageFileName={}".format(file), file=cf) - print("ImageWidth={}".format( - instance.data.get("resolutionWidth")), file=cf) - print("ImageHeight={}".format( - instance.data.get("resolutionHeight")), file=cf) - - tiles = _format_tiles( - file, 0, - instance.data.get("tilesX"), - instance.data.get("tilesY"), - instance.data.get("resolutionWidth"), - instance.data.get("resolutionHeight"), - payload["PluginInfo"]["OutputFilePrefix"] - )[1] - sorted(tiles) - for k, v in tiles.items(): - print("{}={}".format(k, v), file=cf) - - job_idx = 1 - instance.data["assemblySubmissionJobs"] = [] - for ass_job in assembly_payloads: - self.log.info("submitting assembly job {} of {}".format( - job_idx, len(assembly_payloads) - )) - self.log.debug(json.dumps(ass_job, indent=4, sort_keys=True)) - response = requests_post(url, json=ass_job) - if not response.ok: - raise Exception(response.text) - - instance.data["assemblySubmissionJobs"].append( - response.json()["_id"]) - job_idx += 1 - - instance.data["jobBatchName"] = payload["JobInfo"]["BatchName"] - self.log.info("Setting batch name on instance: {}".format( - instance.data["jobBatchName"])) - else: - # Submit job to farm -------------------------------------------- - self.log.info("Submitting ...") - self.log.debug(json.dumps(payload, indent=4, sort_keys=True)) - - # E.g. http://192.168.0.1:8082/api/jobs - url = "{}/api/jobs".format(self.deadline_url) - response = requests_post(url, json=payload) - if not response.ok: - raise Exception(response.text) - instance.data["deadlineSubmissionJob"] = response.json() + instance.data["jobBatchName"] = payload["JobInfo"]["BatchName"] + self.log.info("Setting batch name on instance: {}".format( + instance.data["jobBatchName"])) def _get_maya_payload(self, data): payload = copy.deepcopy(self.payload_skeleton) @@ -1045,39 +777,6 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): payload["PluginInfo"].update(plugin_info) return payload - def _submit_export(self, data, format): - if format == "vray": - payload = self._get_vray_export_payload(data) - self.log.info("Submitting vrscene export job.") - elif format == "arnold": - payload = self._get_arnold_export_payload(data) - self.log.info("Submitting ass export job.") - - url = "{}/api/jobs".format(self.deadline_url) - response = requests_post(url, json=payload) - if not response.ok: - self.log.error("Submition failed!") - self.log.error(response.status_code) - self.log.error(response.content) - self.log.debug(payload) - raise RuntimeError(response.text) - - dependency = response.json() - return dependency["_id"] - - def preflight_check(self, instance): - """Ensure the startFrame, endFrame and byFrameStep are integers.""" - for key in ("frameStartHandle", "frameEndHandle", "byFrameStep"): - value = instance.data[key] - - if int(value) == value: - continue - - self.log.warning( - "%f=%d was rounded off to nearest integer" - % (value, int(value)) - ) - def format_vray_output_filename(self, filename, template, dir=False): """Format the expected output file of the Export job. @@ -1160,3 +859,209 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): "Applied {} patch to scene.".format( patches[i]["name"])) return file + + +def _format_tiles( + filename, index, tiles_x, tiles_y, + width, height, prefix): + """Generate tile entries for Deadline tile job. + + Returns two dictionaries - one that can be directly used in Deadline + job, second that can be used for Deadline Assembly job configuration + file. + + This will format tile names: + + Example:: + { + "OutputFilename0Tile0": "_tile_1x1_4x4_Main_beauty.1001.exr", + "OutputFilename0Tile1": "_tile_2x1_4x4_Main_beauty.1001.exr" + } + + And add tile prefixes like: + + Example:: + Image prefix is: + `maya///_` + + Result for tile 0 for 4x4 will be: + `maya///_tile_1x1_4x4__` + + Calculating coordinates is tricky as in Job they are defined as top, + left, bottom, right with zero being in top-left corner. But Assembler + configuration file takes tile coordinates as X, Y, Width and Height and + zero is bottom left corner. + + Args: + filename (str): Filename to process as tiles. + index (int): Index of that file if it is sequence. + tiles_x (int): Number of tiles in X. + tiles_y (int): Number if tikes in Y. + width (int): Width resolution of final image. + height (int): Height resolution of final image. + prefix (str): Image prefix. + + Returns: + (dict, dict): Tuple of two dictionaires - first can be used to + extend JobInfo, second has tiles x, y, width and height + used for assembler configuration. + + """ + tile = 0 + out = {"JobInfo": {}, "PluginInfo": {}} + cfg = OrderedDict() + w_space = width / tiles_x + h_space = height / tiles_y + + cfg["TilesCropped"] = "False" + + for tile_x in range(1, tiles_x + 1): + for tile_y in reversed(range(1, tiles_y + 1)): + tile_prefix = "_tile_{}x{}_{}x{}_".format( + tile_x, tile_y, + tiles_x, + tiles_y + ) + out_tile_index = "OutputFilename{}Tile{}".format( + str(index), tile + ) + new_filename = "{}/{}{}".format( + os.path.dirname(filename), + tile_prefix, + os.path.basename(filename) + ) + out["JobInfo"][out_tile_index] = new_filename + out["PluginInfo"]["RegionPrefix{}".format(str(tile))] = \ + "/{}".format(tile_prefix).join(prefix.rsplit("/", 1)) + + out["PluginInfo"]["RegionTop{}".format(tile)] = int(height) - (tile_y * h_space) # noqa: E501 + out["PluginInfo"]["RegionBottom{}".format(tile)] = int(height) - ((tile_y - 1) * h_space) - 1 # noqa: E501 + out["PluginInfo"]["RegionLeft{}".format(tile)] = (tile_x - 1) * w_space # noqa: E501 + out["PluginInfo"]["RegionRight{}".format(tile)] = (tile_x * w_space) - 1 # noqa: E501 + + cfg["Tile{}".format(tile)] = new_filename + cfg["Tile{}Tile".format(tile)] = new_filename + cfg["Tile{}FileName".format(tile)] = new_filename + cfg["Tile{}X".format(tile)] = (tile_x - 1) * w_space + + cfg["Tile{}Y".format(tile)] = int(height) - (tile_y * h_space) + + cfg["Tile{}Width".format(tile)] = w_space + cfg["Tile{}Height".format(tile)] = h_space + + tile += 1 + return out, cfg + + +def get_renderer_variables(renderlayer, root): + """Retrieve the extension which has been set in the VRay settings. + + Will return None if the current renderer is not VRay + For Maya 2016.5 and up the renderSetup creates renderSetupLayer node which + start with `rs`. Use the actual node name, do NOT use the `nice name` + + Args: + renderlayer (str): the node name of the renderlayer. + root (str): base path to render + + Returns: + dict + + """ + renderer = lib.get_renderer(renderlayer or lib.get_current_renderlayer()) + render_attrs = lib.RENDER_ATTRS.get(renderer, lib.RENDER_ATTRS["default"]) + + padding = cmds.getAttr("{}.{}".format(render_attrs["node"], + render_attrs["padding"])) + + filename_0 = cmds.renderSettings( + fullPath=True, + gin="#" * int(padding), + lut=True, + layer=renderlayer or lib.get_current_renderlayer())[0] + filename_0 = re.sub('_', '_beauty', + filename_0, flags=re.IGNORECASE) + prefix_attr = "defaultRenderGlobals.imageFilePrefix" + + scene = cmds.file(query=True, sceneName=True) + scene, _ = os.path.splitext(os.path.basename(scene)) + + if renderer == "vray": + renderlayer = renderlayer.split("_")[-1] + # Maya's renderSettings function does not return V-Ray file extension + # so we get the extension from vraySettings + extension = cmds.getAttr("vraySettings.imageFormatStr") + + # When V-Ray image format has not been switched once from default .png + # the getAttr command above returns None. As such we explicitly set + # it to `.png` + if extension is None: + extension = "png" + + if extension in ["exr (multichannel)", "exr (deep)"]: + extension = "exr" + + prefix_attr = "vraySettings.fileNamePrefix" + filename_prefix = cmds.getAttr(prefix_attr) + # we need to determine path for vray as maya `renderSettings` query + # does not work for vray. + + filename_0 = re.sub('', scene, filename_prefix, flags=re.IGNORECASE) # noqa: E501 + filename_0 = re.sub('', renderlayer, filename_0, flags=re.IGNORECASE) # noqa: E501 + filename_0 = "{}.{}.{}".format( + filename_0, "#" * int(padding), extension) + filename_0 = os.path.normpath(os.path.join(root, filename_0)) + elif renderer == "renderman": + prefix_attr = "rmanGlobals.imageFileFormat" + # NOTE: This is guessing extensions from renderman display types. + # Some of them are just framebuffers, d_texture format can be + # set in display setting. We set those now to None, but it + # should be handled more gracefully. + display_types = { + "d_deepexr": "exr", + "d_it": None, + "d_null": None, + "d_openexr": "exr", + "d_png": "png", + "d_pointcloud": "ptc", + "d_targa": "tga", + "d_texture": None, + "d_tiff": "tif" + } + + extension = display_types.get( + cmds.listConnections("rmanDefaultDisplay.displayType")[0], + "exr" + ) or "exr" + + filename_prefix = "{}/{}".format( + cmds.getAttr("rmanGlobals.imageOutputDir"), + cmds.getAttr("rmanGlobals.imageFileFormat") + ) + + renderlayer = renderlayer.split("_")[-1] + + filename_0 = re.sub('', scene, filename_prefix, flags=re.IGNORECASE) # noqa: E501 + filename_0 = re.sub('', renderlayer, filename_0, flags=re.IGNORECASE) # noqa: E501 + filename_0 = re.sub('', "#" * int(padding), filename_0, flags=re.IGNORECASE) # noqa: E501 + filename_0 = re.sub('', extension, filename_0, flags=re.IGNORECASE) # noqa: E501 + filename_0 = os.path.normpath(os.path.join(root, filename_0)) + elif renderer == "redshift": + # mapping redshift extension dropdown values to strings + ext_mapping = ["iff", "exr", "tif", "png", "tga", "jpg"] + extension = ext_mapping[ + cmds.getAttr("redshiftOptions.imageFormat") + ] + else: + # Get the extension, getAttr defaultRenderGlobals.imageFormat + # returns an index number. + filename_base = os.path.basename(filename_0) + extension = os.path.splitext(filename_base)[-1].strip(".") + + filename_prefix = cmds.getAttr(prefix_attr) + return {"ext": extension, + "filename_prefix": filename_prefix, + "padding": padding, + "filename_0": filename_0} + + From aaa95efb3c80080166de20c9f48bbfe6a8c11548 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 24 Aug 2022 13:45:53 +0200 Subject: [PATCH 040/274] Removed submodule vendor/configs/OpenColorIO-Configs --- vendor/configs/OpenColorIO-Configs | 1 - 1 file changed, 1 deletion(-) delete mode 160000 vendor/configs/OpenColorIO-Configs diff --git a/vendor/configs/OpenColorIO-Configs b/vendor/configs/OpenColorIO-Configs deleted file mode 160000 index 0bb079c08b..0000000000 --- a/vendor/configs/OpenColorIO-Configs +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0bb079c08be410030669cbf5f19ff869b88af953 From 4c16b8930fd38d6c9f792c5983d6e2e88f1587ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Wed, 24 Aug 2022 14:14:47 +0200 Subject: [PATCH 041/274] Update openpype/plugins/publish/collect_audio.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/plugins/publish/collect_audio.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/collect_audio.py b/openpype/plugins/publish/collect_audio.py index e2fb766ec4..0825c281ad 100644 --- a/openpype/plugins/publish/collect_audio.py +++ b/openpype/plugins/publish/collect_audio.py @@ -66,7 +66,10 @@ class CollectAudio(pyblish.api.InstancePlugin): self.log.info("Audio Data added to instance ...") def _get_repre_doc(self, instance): - cache = instance.context.data.get("__cache_asset_audio", {}) + cache = instance.context.data.get("__cache_asset_audio") + if cache is None: + cache = {} + instance.context.data["__cache_asset_audio"] = cache asset_name = instance.data["asset"] # first try to get it from cache From 6ff16a6d25f2b81f5634d526059734225c556ede Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Wed, 24 Aug 2022 14:14:58 +0200 Subject: [PATCH 042/274] Update openpype/plugins/publish/collect_audio.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/plugins/publish/collect_audio.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/plugins/publish/collect_audio.py b/openpype/plugins/publish/collect_audio.py index 0825c281ad..7d53b24e54 100644 --- a/openpype/plugins/publish/collect_audio.py +++ b/openpype/plugins/publish/collect_audio.py @@ -101,6 +101,5 @@ class CollectAudio(pyblish.api.InstancePlugin): # update cache cache[asset_name] = repre_doc - instance.context.data["__cache_asset_audio"].update(cache) return repre_doc From da5353aa2dd1d9e774a72dbc57a6ac5d7368afdd Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 24 Aug 2022 14:20:35 +0200 Subject: [PATCH 043/274] git: update gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index ea5b20eb69..4b773e97ed 100644 --- a/.gitignore +++ b/.gitignore @@ -107,3 +107,6 @@ website/.docusaurus mypy.ini tools/run_eventserver.* + +# Developer tools +tools/dev_* From 0f95f87d773ddcbe979fe28d5f0196f1befad38e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 24 Aug 2022 14:59:14 +0200 Subject: [PATCH 044/274] More draft refactoring - still not functional (WIP commit for my own sanity) --- .../plugins/publish/submit_maya_deadline.py | 185 ++++-------------- 1 file changed, 35 insertions(+), 150 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 6dfa48a9f8..5a7d0b98c6 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -31,7 +31,6 @@ import clique from maya import cmds -from openpype.hosts.maya.api import lib from openpype.pipeline import legacy_io from openpype_modules.deadline import abstract_submit_deadline @@ -87,11 +86,15 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): instance = self._instance context = instance.context - filepath = context.data["currentFile"] - filename = os.path.basename(filepath) + # Always use the original work file name for the Job name even when + # rendering is done from the published Work File. The original work + # file name is clearer because it can also have subversion strings, + # etc. which are stripped for the published file. + src_filepath = context.data["currentFile"] + src_filename = os.path.basename(src_filepath) - job_info.Name = "%s - %s" % (filename, instance.name) - job_info.BatchName = filename + job_info.Name = "%s - %s" % (src_filename, instance.name) + job_info.BatchName = src_filename job_info.Plugin = instance.data.get("mayaRenderPlugin", "MayaBatch") job_info.UserName = context.data.get( "deadlineUser", getpass.getuser()) @@ -116,9 +119,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): if self.limit_groups: job_info.LimitGroups = ",".join(self.limit_groups) - self.payload_skeleton["JobInfo"]["Name"] = jobname - self.payload_skeleton["JobInfo"]["BatchName"] = src_filename - # Optional, enable double-click to preview rendered # frames from Deadline Monitor self.payload_skeleton["JobInfo"]["OutputDirectory0"] = \ @@ -227,11 +227,17 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): return attr.asdict(plugin_info) def process_submission(self): - # Override to NOT submit by default when calling super process() method - pass - def process(self, instance): - super(MayaSubmitDeadline, self).process(instance) + instance = self._instance + context = instance.context + + # Generated by AbstractSubmitDeadline. The `job_info`, `plugin_info` + # and `aux_files` are the skeleton payloads that are the basis for + # all the maya submissions + job_info = self.job_info + plugin_info = self.plugin_info + aux_files = self.aux_files + filepath = self.scene_path # publish if `use_publish` else workfile # TODO: Avoid the need for this logic here, needed for submit publish # Store output dir for unified publisher (filesequence) @@ -241,21 +247,19 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): self.limit_groups = self.limit - context = instance.context - workspace = context.data["workspaceDir"] - - filepath = None - patches = ( - context.data["project_settings"].get( - "deadline", {}).get( - "publish", {}).get( - "MayaSubmitDeadline", {}).get( - "scene_patches", {}) - ) + # Patch workfile (only when use_published is enabled) + if self.use_published: + patches = ( + context.data["project_settings"].get( + "deadline", {}).get( + "publish", {}).get( + "MayaSubmitDeadline", {}).get( + "scene_patches", {}) + ) + self._patch_workfile(filepath, patches) # todo: on self.use_published originally use template_data["representation"] using .get("name") instead of .get("ext") # todo: on self.use_published replace path for publishRenderMetadataFolder - # todo: on self.use_published apply scene patches to workfile instance # rep = i.data.get("representations")[0].get("name") # if instance.data.get("publishRenderMetadataFolder"): @@ -270,9 +274,8 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # patched_file = self._patch_workfile(filepath, patches) # patched_files.append(patched_file) - filepath = self.scene_path # collect by super().process - # Gather needed data ------------------------------------------------ + workspace = context.data["workspaceDir"] default_render_file = instance.context.data.get('project_settings')\ .get('maya')\ .get('RenderSettings')\ @@ -281,14 +284,10 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): dirname = os.path.join(workspace, default_render_file) renderlayer = instance.data['setMembers'] # rs_beauty - # Always use the original work file name for the Job name even when - # rendering is done from the published Work File. The original work - # file name is clearer because it can also have subversion strings, - # etc. which are stripped for the published file. - src_filename = os.path.basename(context.data["currentFile"]) - jobname = "%s - %s" % (src_filename, instance.name) - # Get the variables depending on the renderer + # TODO: Find replacement logic for `get_renderer_variables` through + # what is collected for the render or is implemented in maya + # api `lib_renderproducts` render_variables = get_renderer_variables(renderlayer, dirname) filename_0 = render_variables["filename_0"] if self.use_published: @@ -842,8 +841,8 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): str: Patched file path or None """ - if os.path.splitext(file)[1].lower() != ".ma" or not patches: - return None + if not patches or os.path.splitext(file)[1].lower() != ".ma": + return compiled_regex = [re.compile(p["regex"]) for p in patches] with open(file, "r+") as pf: @@ -931,7 +930,7 @@ def _format_tiles( os.path.basename(filename) ) out["JobInfo"][out_tile_index] = new_filename - out["PluginInfo"]["RegionPrefix{}".format(str(tile))] = \ + out["PluginInfo"]["RegionPrefix{}".format(tile)] = \ "/{}".format(tile_prefix).join(prefix.rsplit("/", 1)) out["PluginInfo"]["RegionTop{}".format(tile)] = int(height) - (tile_y * h_space) # noqa: E501 @@ -951,117 +950,3 @@ def _format_tiles( tile += 1 return out, cfg - - -def get_renderer_variables(renderlayer, root): - """Retrieve the extension which has been set in the VRay settings. - - Will return None if the current renderer is not VRay - For Maya 2016.5 and up the renderSetup creates renderSetupLayer node which - start with `rs`. Use the actual node name, do NOT use the `nice name` - - Args: - renderlayer (str): the node name of the renderlayer. - root (str): base path to render - - Returns: - dict - - """ - renderer = lib.get_renderer(renderlayer or lib.get_current_renderlayer()) - render_attrs = lib.RENDER_ATTRS.get(renderer, lib.RENDER_ATTRS["default"]) - - padding = cmds.getAttr("{}.{}".format(render_attrs["node"], - render_attrs["padding"])) - - filename_0 = cmds.renderSettings( - fullPath=True, - gin="#" * int(padding), - lut=True, - layer=renderlayer or lib.get_current_renderlayer())[0] - filename_0 = re.sub('_', '_beauty', - filename_0, flags=re.IGNORECASE) - prefix_attr = "defaultRenderGlobals.imageFilePrefix" - - scene = cmds.file(query=True, sceneName=True) - scene, _ = os.path.splitext(os.path.basename(scene)) - - if renderer == "vray": - renderlayer = renderlayer.split("_")[-1] - # Maya's renderSettings function does not return V-Ray file extension - # so we get the extension from vraySettings - extension = cmds.getAttr("vraySettings.imageFormatStr") - - # When V-Ray image format has not been switched once from default .png - # the getAttr command above returns None. As such we explicitly set - # it to `.png` - if extension is None: - extension = "png" - - if extension in ["exr (multichannel)", "exr (deep)"]: - extension = "exr" - - prefix_attr = "vraySettings.fileNamePrefix" - filename_prefix = cmds.getAttr(prefix_attr) - # we need to determine path for vray as maya `renderSettings` query - # does not work for vray. - - filename_0 = re.sub('', scene, filename_prefix, flags=re.IGNORECASE) # noqa: E501 - filename_0 = re.sub('', renderlayer, filename_0, flags=re.IGNORECASE) # noqa: E501 - filename_0 = "{}.{}.{}".format( - filename_0, "#" * int(padding), extension) - filename_0 = os.path.normpath(os.path.join(root, filename_0)) - elif renderer == "renderman": - prefix_attr = "rmanGlobals.imageFileFormat" - # NOTE: This is guessing extensions from renderman display types. - # Some of them are just framebuffers, d_texture format can be - # set in display setting. We set those now to None, but it - # should be handled more gracefully. - display_types = { - "d_deepexr": "exr", - "d_it": None, - "d_null": None, - "d_openexr": "exr", - "d_png": "png", - "d_pointcloud": "ptc", - "d_targa": "tga", - "d_texture": None, - "d_tiff": "tif" - } - - extension = display_types.get( - cmds.listConnections("rmanDefaultDisplay.displayType")[0], - "exr" - ) or "exr" - - filename_prefix = "{}/{}".format( - cmds.getAttr("rmanGlobals.imageOutputDir"), - cmds.getAttr("rmanGlobals.imageFileFormat") - ) - - renderlayer = renderlayer.split("_")[-1] - - filename_0 = re.sub('', scene, filename_prefix, flags=re.IGNORECASE) # noqa: E501 - filename_0 = re.sub('', renderlayer, filename_0, flags=re.IGNORECASE) # noqa: E501 - filename_0 = re.sub('', "#" * int(padding), filename_0, flags=re.IGNORECASE) # noqa: E501 - filename_0 = re.sub('', extension, filename_0, flags=re.IGNORECASE) # noqa: E501 - filename_0 = os.path.normpath(os.path.join(root, filename_0)) - elif renderer == "redshift": - # mapping redshift extension dropdown values to strings - ext_mapping = ["iff", "exr", "tif", "png", "tga", "jpg"] - extension = ext_mapping[ - cmds.getAttr("redshiftOptions.imageFormat") - ] - else: - # Get the extension, getAttr defaultRenderGlobals.imageFormat - # returns an index number. - filename_base = os.path.basename(filename_0) - extension = os.path.splitext(filename_base)[-1].strip(".") - - filename_prefix = cmds.getAttr(prefix_attr) - return {"ext": extension, - "filename_prefix": filename_prefix, - "padding": padding, - "filename_0": filename_0} - - From a42f7278c2ec1123b2f19b103933182f2e3cd92d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 24 Aug 2022 17:59:46 +0200 Subject: [PATCH 045/274] define resolve as module --- openpype/hosts/resolve/__init__.py | 6 ++++++ openpype/hosts/resolve/module.py | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 openpype/hosts/resolve/module.py diff --git a/openpype/hosts/resolve/__init__.py b/openpype/hosts/resolve/__init__.py index e69de29bb2..a02e07794d 100644 --- a/openpype/hosts/resolve/__init__.py +++ b/openpype/hosts/resolve/__init__.py @@ -0,0 +1,6 @@ +from .module import ResolveModule + + +__all__ = ( + "ResolveModule", +) diff --git a/openpype/hosts/resolve/module.py b/openpype/hosts/resolve/module.py new file mode 100644 index 0000000000..8f2824df75 --- /dev/null +++ b/openpype/hosts/resolve/module.py @@ -0,0 +1,24 @@ +import os + +from openpype.modules import OpenPypeModule +from openpype.modules.interfaces import IHostModule + +from .utils import RESOLVE_ROOT_DIR + + +class ResolveModule(OpenPypeModule, IHostModule): + name = "resolve" + host_name = "resolve" + + def initialize(self, module_settings): + self.enabled = True + + def get_launch_hook_paths(self, app): + if app.host_name != self.host_name: + return [] + return [ + os.path.join(RESOLVE_ROOT_DIR, "hooks") + ] + + def get_workfile_extensions(self): + return [".drp"] From d623dfa857be9b6650a7c4cd285f73b02be32808 Mon Sep 17 00:00:00 2001 From: Kaa Maurice Date: Thu, 25 Aug 2022 11:54:09 +0200 Subject: [PATCH 046/274] fix validator invalid return --- .../blender/plugins/publish/validate_mesh_no_negative_scale.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py b/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py index 3c5c7c11eb..329a8d80c3 100644 --- a/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py +++ b/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py @@ -24,6 +24,7 @@ class ValidateMeshNoNegativeScale(pyblish.api.Validator): if isinstance(obj, bpy.types.Object) and obj.type == 'MESH': if any(v < 0 for v in obj.scale): invalid.append(obj) + return invalid def process(self, instance): invalid = self.get_invalid(instance) From 6914c626a1dfe5414dc3c1a2881801f2a069f55c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 25 Aug 2022 15:28:23 +0200 Subject: [PATCH 047/274] implemented flame addon --- openpype/hosts/flame/__init__.py | 26 +++++++------------------- openpype/hosts/flame/module.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 19 deletions(-) create mode 100644 openpype/hosts/flame/module.py diff --git a/openpype/hosts/flame/__init__.py b/openpype/hosts/flame/__init__.py index f839357147..1ab0e6ff33 100644 --- a/openpype/hosts/flame/__init__.py +++ b/openpype/hosts/flame/__init__.py @@ -1,22 +1,10 @@ -import os - -HOST_DIR = os.path.dirname( - os.path.abspath(__file__) +from .module import ( + HOST_DIR, + FlameAddon, ) -def add_implementation_envs(env, _app): - # Add requirements to DL_PYTHON_HOOK_PATH - pype_root = os.environ["OPENPYPE_REPOS_ROOT"] - - env["DL_PYTHON_HOOK_PATH"] = os.path.join( - pype_root, "openpype", "hosts", "flame", "startup") - env.pop("QT_AUTO_SCREEN_SCALE_FACTOR", None) - - # Set default values if are not already set via settings - defaults = { - "LOGLEVEL": "DEBUG" - } - for key, value in defaults.items(): - if not env.get(key): - env[key] = value +__all__ = ( + "HOST_DIR", + "FlameAddon", +) diff --git a/openpype/hosts/flame/module.py b/openpype/hosts/flame/module.py new file mode 100644 index 0000000000..2960d7db0f --- /dev/null +++ b/openpype/hosts/flame/module.py @@ -0,0 +1,29 @@ +import os +from openpype.modules import OpenPypeModule +from openpype.modules.interfaces import IHostModule + +HOST_DIR = os.path.dirname(os.path.abspath(__file__)) + + +class FlameAddon(OpenPypeModule, IHostModule): + name = "flame" + host_name = "flame" + + def initialize(self, module_settings): + self.enabled = True + + def add_implementation_envs(self, env, _app): + # Add requirements to DL_PYTHON_HOOK_PATH + env["DL_PYTHON_HOOK_PATH"] = os.path.join(HOST_DIR, "startup") + env.pop("QT_AUTO_SCREEN_SCALE_FACTOR", None) + + # Set default values if are not already set via settings + defaults = { + "LOGLEVEL": "DEBUG" + } + for key, value in defaults.items(): + if not env.get(key): + env[key] = value + + def get_workfile_extensions(self): + return [".otoc"] From c3f70ccb04e965c8ec9b4c3edb12e957496a4a23 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 25 Aug 2022 15:33:29 +0200 Subject: [PATCH 048/274] changed "module" to "addon" --- openpype/hosts/resolve/__init__.py | 2 +- openpype/hosts/resolve/{module.py => addon.py} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename openpype/hosts/resolve/{module.py => addon.py} (91%) diff --git a/openpype/hosts/resolve/__init__.py b/openpype/hosts/resolve/__init__.py index a02e07794d..ebbfdc6a1e 100644 --- a/openpype/hosts/resolve/__init__.py +++ b/openpype/hosts/resolve/__init__.py @@ -1,4 +1,4 @@ -from .module import ResolveModule +from .addon import ResolveModule __all__ = ( diff --git a/openpype/hosts/resolve/module.py b/openpype/hosts/resolve/addon.py similarity index 91% rename from openpype/hosts/resolve/module.py rename to openpype/hosts/resolve/addon.py index 8f2824df75..af09448a43 100644 --- a/openpype/hosts/resolve/module.py +++ b/openpype/hosts/resolve/addon.py @@ -6,7 +6,7 @@ from openpype.modules.interfaces import IHostModule from .utils import RESOLVE_ROOT_DIR -class ResolveModule(OpenPypeModule, IHostModule): +class ResolveAddon(OpenPypeModule, IHostModule): name = "resolve" host_name = "resolve" From 92240f7237d2b820f0d32b058a6df9b333e8efa1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 25 Aug 2022 15:33:46 +0200 Subject: [PATCH 049/274] fix import --- openpype/hosts/resolve/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/resolve/__init__.py b/openpype/hosts/resolve/__init__.py index ebbfdc6a1e..b4a994bbaa 100644 --- a/openpype/hosts/resolve/__init__.py +++ b/openpype/hosts/resolve/__init__.py @@ -1,6 +1,6 @@ -from .addon import ResolveModule +from .addon import ResolveAddon __all__ = ( - "ResolveModule", + "ResolveAddon", ) From d78d73f391ac8ca4daa3f3c07ad301167ff70bb7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 25 Aug 2022 15:37:34 +0200 Subject: [PATCH 050/274] added missing definition of launch hooks --- openpype/hosts/flame/module.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/hosts/flame/module.py b/openpype/hosts/flame/module.py index 2960d7db0f..7e68378f4b 100644 --- a/openpype/hosts/flame/module.py +++ b/openpype/hosts/flame/module.py @@ -25,5 +25,12 @@ class FlameAddon(OpenPypeModule, IHostModule): if not env.get(key): env[key] = value + def get_launch_hook_paths(self, app): + if app.host_name != self.host_name: + return [] + return [ + os.path.join(HOST_DIR, "hooks") + ] + def get_workfile_extensions(self): return [".otoc"] From 2c3604cee6bde2fff46cef817aac0c288eb94d4c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 25 Aug 2022 15:37:54 +0200 Subject: [PATCH 051/274] renemad module.py to addon.py --- openpype/hosts/flame/__init__.py | 2 +- openpype/hosts/flame/{module.py => addon.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename openpype/hosts/flame/{module.py => addon.py} (100%) diff --git a/openpype/hosts/flame/__init__.py b/openpype/hosts/flame/__init__.py index 1ab0e6ff33..b45f107747 100644 --- a/openpype/hosts/flame/__init__.py +++ b/openpype/hosts/flame/__init__.py @@ -1,4 +1,4 @@ -from .module import ( +from .addon import ( HOST_DIR, FlameAddon, ) diff --git a/openpype/hosts/flame/module.py b/openpype/hosts/flame/addon.py similarity index 100% rename from openpype/hosts/flame/module.py rename to openpype/hosts/flame/addon.py From 67fd21edae5d7c3670ee704558ff10505c64a783 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 25 Aug 2022 15:40:32 +0200 Subject: [PATCH 052/274] imlemented fusion addon --- openpype/hosts/fusion/__init__.py | 10 ++++++++++ openpype/hosts/fusion/addon.py | 23 +++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 openpype/hosts/fusion/addon.py diff --git a/openpype/hosts/fusion/__init__.py b/openpype/hosts/fusion/__init__.py index e69de29bb2..ddae01890b 100644 --- a/openpype/hosts/fusion/__init__.py +++ b/openpype/hosts/fusion/__init__.py @@ -0,0 +1,10 @@ +from .addon import ( + FusionAddon, + FUSION_HOST_DIR, +) + + +__all__ = ( + "FusionAddon", + "FUSION_HOST_DIR", +) diff --git a/openpype/hosts/fusion/addon.py b/openpype/hosts/fusion/addon.py new file mode 100644 index 0000000000..97fb262517 --- /dev/null +++ b/openpype/hosts/fusion/addon.py @@ -0,0 +1,23 @@ +import os +from openpype.modules import OpenPypeModule +from openpype.modules.interfaces import IHostModule + +FUSION_HOST_DIR = os.path.dirname(os.path.abspath(__file__)) + + +class FusionAddon(OpenPypeModule, IHostModule): + name = "fusion" + host_name = "fusion" + + def initialize(self, module_settings): + self.enabled = True + + def get_launch_hook_paths(self, app): + if app.host_name != self.host_name: + return [] + return [ + os.path.join(FUSION_HOST_DIR, "hooks") + ] + + def get_workfile_extensions(self): + return [".comp"] From 3ea7510693ec23623ec7760f3479e10b363a2adf Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 25 Aug 2022 15:40:40 +0200 Subject: [PATCH 053/274] removed usage of HOST_WORKFILE_EXTENSIONS --- openpype/hosts/fusion/api/workio.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openpype/hosts/fusion/api/workio.py b/openpype/hosts/fusion/api/workio.py index a1710c6e3a..89752d3e6d 100644 --- a/openpype/hosts/fusion/api/workio.py +++ b/openpype/hosts/fusion/api/workio.py @@ -2,13 +2,11 @@ import sys import os -from openpype.pipeline import HOST_WORKFILE_EXTENSIONS - from .pipeline import get_current_comp def file_extensions(): - return HOST_WORKFILE_EXTENSIONS["fusion"] + return [".comp"] def has_unsaved_changes(): From cf50d1dd1f00fe178d8bfc28a039579222cab1d6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 25 Aug 2022 15:40:56 +0200 Subject: [PATCH 054/274] reuse 'FUSION_HOST_DIR' from fusion public api --- openpype/hosts/fusion/api/pipeline.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/fusion/api/pipeline.py b/openpype/hosts/fusion/api/pipeline.py index 54a6c94b60..987eae214b 100644 --- a/openpype/hosts/fusion/api/pipeline.py +++ b/openpype/hosts/fusion/api/pipeline.py @@ -18,12 +18,11 @@ from openpype.pipeline import ( deregister_inventory_action_path, AVALON_CONTAINER_ID, ) -import openpype.hosts.fusion +from openpype.hosts.fusion import FUSION_HOST_DIR log = Logger.get_logger(__name__) -HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.fusion.__file__)) -PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") +PLUGINS_DIR = os.path.join(FUSION_HOST_DIR, "plugins") PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "load") From 4c8291442695a841be30b5402a3bb9e77fed7bdf Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 25 Aug 2022 16:02:49 +0200 Subject: [PATCH 055/274] implemented Houdini addon --- openpype/hosts/houdini/__init__.py | 44 +++++------------------- openpype/hosts/houdini/addon.py | 55 ++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 36 deletions(-) create mode 100644 openpype/hosts/houdini/addon.py diff --git a/openpype/hosts/houdini/__init__.py b/openpype/hosts/houdini/__init__.py index a3ee38db8d..7fba9baddc 100644 --- a/openpype/hosts/houdini/__init__.py +++ b/openpype/hosts/houdini/__init__.py @@ -1,38 +1,10 @@ -import os +from .addon import ( + HoudiniAddon, + HOUDINI_HOST_DIR. +) -def add_implementation_envs(env, _app): - # Add requirements to HOUDINI_PATH and HOUDINI_MENU_PATH - pype_root = os.environ["OPENPYPE_REPOS_ROOT"] - - startup_path = os.path.join( - pype_root, "openpype", "hosts", "houdini", "startup" - ) - new_houdini_path = [startup_path] - new_houdini_menu_path = [startup_path] - - old_houdini_path = env.get("HOUDINI_PATH") or "" - old_houdini_menu_path = env.get("HOUDINI_MENU_PATH") or "" - - for path in old_houdini_path.split(os.pathsep): - if not path: - continue - - norm_path = os.path.normpath(path) - if norm_path not in new_houdini_path: - new_houdini_path.append(norm_path) - - for path in old_houdini_menu_path.split(os.pathsep): - if not path: - continue - - norm_path = os.path.normpath(path) - if norm_path not in new_houdini_menu_path: - new_houdini_menu_path.append(norm_path) - - # Add ampersand for unknown reason (Maybe is needed in Houdini?) - new_houdini_path.append("&") - new_houdini_menu_path.append("&") - - env["HOUDINI_PATH"] = os.pathsep.join(new_houdini_path) - env["HOUDINI_MENU_PATH"] = os.pathsep.join(new_houdini_menu_path) +__all__ = ( + "HoudiniAddon", + "HOUDINI_HOST_DIR". +) diff --git a/openpype/hosts/houdini/addon.py b/openpype/hosts/houdini/addon.py new file mode 100644 index 0000000000..255d6ed53f --- /dev/null +++ b/openpype/hosts/houdini/addon.py @@ -0,0 +1,55 @@ +import os +from openpype.modules import OpenPypeModule +from openpype.modules.interfaces import IHostModule + +HOUDINI_HOST_DIR = os.path.dirname(os.path.abspath(__file__)) + + +class HoudiniAddon(OpenPypeModule, IHostModule): + name = "houdini" + host_name = "houdini" + + def initialize(self, module_settings): + self.enabled = True + + def add_implementation_envs(self, env, _app): + # Add requirements to HOUDINI_PATH and HOUDINI_MENU_PATH + startup_path = os.path.join(HOUDINI_HOST_DIR, "startup") + new_houdini_path = [startup_path] + new_houdini_menu_path = [startup_path] + + old_houdini_path = env.get("HOUDINI_PATH") or "" + old_houdini_menu_path = env.get("HOUDINI_MENU_PATH") or "" + + for path in old_houdini_path.split(os.pathsep): + if not path: + continue + + norm_path = os.path.normpath(path) + if norm_path not in new_houdini_path: + new_houdini_path.append(norm_path) + + for path in old_houdini_menu_path.split(os.pathsep): + if not path: + continue + + norm_path = os.path.normpath(path) + if norm_path not in new_houdini_menu_path: + new_houdini_menu_path.append(norm_path) + + # Add ampersand for unknown reason (Maybe is needed in Houdini?) + new_houdini_path.append("&") + new_houdini_menu_path.append("&") + + env["HOUDINI_PATH"] = os.pathsep.join(new_houdini_path) + env["HOUDINI_MENU_PATH"] = os.pathsep.join(new_houdini_menu_path) + + def get_launch_hook_paths(self, app): + if app.host_name != self.host_name: + return [] + return [ + os.path.join(HOUDINI_HOST_DIR, "hooks") + ] + + def get_workfile_extensions(self): + return [".hip", ".hiplc", ".hipnc"] From 9b7b217faafefb5bc32873337b41a3cce415c124 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 25 Aug 2022 16:02:56 +0200 Subject: [PATCH 056/274] Nuke: adding sumbitted job ids to instance attribute for downstream --- .../deadline/plugins/publish/submit_nuke_deadline.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py index 336a56ec45..b09d2935ab 100644 --- a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -114,6 +114,13 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): instance.data["deadlineSubmissionJob"] = resp.json() instance.data["publishJobState"] = "Suspended" + # add to list of job Id + if not instance.data.get("bakingSubmissionJobs"): + instance.data["bakingSubmissionJobs"] = [] + + instance.data["bakingSubmissionJobs"].append( + resp.json()["_id"]) + # redefinition of families if "render.farm" in families: instance.data['family'] = 'write' From a2ea726be175448571ff1683338782721bed54bb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 25 Aug 2022 16:02:58 +0200 Subject: [PATCH 057/274] removed usage of HOST_WORKFILE_EXTENSIONS --- openpype/hosts/houdini/api/workio.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/api/workio.py b/openpype/hosts/houdini/api/workio.py index e0213023fd..5f7efff333 100644 --- a/openpype/hosts/houdini/api/workio.py +++ b/openpype/hosts/houdini/api/workio.py @@ -2,11 +2,10 @@ import os import hou -from openpype.pipeline import HOST_WORKFILE_EXTENSIONS def file_extensions(): - return HOST_WORKFILE_EXTENSIONS["houdini"] + return [".hip", ".hiplc", ".hipnc"] def has_unsaved_changes(): From 38ad727b0dca59f1ac9494ead42fd1d19e6f11d4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 25 Aug 2022 16:03:12 +0200 Subject: [PATCH 058/274] reuse HOUDINI_HOST_DIR from public api --- openpype/hosts/houdini/api/pipeline.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/houdini/api/pipeline.py b/openpype/hosts/houdini/api/pipeline.py index b5f5459392..d396fc71c0 100644 --- a/openpype/hosts/houdini/api/pipeline.py +++ b/openpype/hosts/houdini/api/pipeline.py @@ -13,7 +13,7 @@ from openpype.pipeline import ( AVALON_CONTAINER_ID, ) from openpype.pipeline.load import any_outdated_containers -import openpype.hosts.houdini +from openpype.hosts.houdini import HOUDINI_HOST_DIR from openpype.hosts.houdini.api import lib from openpype.lib import ( @@ -28,8 +28,7 @@ log = logging.getLogger("openpype.hosts.houdini") AVALON_CONTAINERS = "/obj/AVALON_CONTAINERS" IS_HEADLESS = not hasattr(hou, "ui") -HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.houdini.__file__)) -PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") +PLUGINS_DIR = os.path.join(HOUDINI_HOST_DIR, "plugins") PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "load") CREATE_PATH = os.path.join(PLUGINS_DIR, "create") @@ -66,7 +65,7 @@ def install(): self._has_been_setup = True # add houdini vendor packages - hou_pythonpath = os.path.join(os.path.dirname(HOST_DIR), "vendor") + hou_pythonpath = os.path.join(os.path.dirname(HOUDINI_HOST_DIR), "vendor") sys.path.append(hou_pythonpath) From 63aee53d4cc7aa5c1fe450a348116878fe6ca6c0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 25 Aug 2022 16:03:23 +0200 Subject: [PATCH 059/274] fix init --- openpype/hosts/houdini/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/__init__.py b/openpype/hosts/houdini/__init__.py index 7fba9baddc..09ba66c834 100644 --- a/openpype/hosts/houdini/__init__.py +++ b/openpype/hosts/houdini/__init__.py @@ -6,5 +6,5 @@ from .addon import ( __all__ = ( "HoudiniAddon", - "HOUDINI_HOST_DIR". + "HOUDINI_HOST_DIR", ) From 81a2766598821067545bfc6daf3bd8f106de13fa Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 25 Aug 2022 16:03:51 +0200 Subject: [PATCH 060/274] fix houdini vender path access --- openpype/hosts/houdini/api/pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/api/pipeline.py b/openpype/hosts/houdini/api/pipeline.py index d396fc71c0..2ae8a4dbf7 100644 --- a/openpype/hosts/houdini/api/pipeline.py +++ b/openpype/hosts/houdini/api/pipeline.py @@ -65,7 +65,7 @@ def install(): self._has_been_setup = True # add houdini vendor packages - hou_pythonpath = os.path.join(os.path.dirname(HOUDINI_HOST_DIR), "vendor") + hou_pythonpath = os.path.join(HOUDINI_HOST_DIR, "vendor") sys.path.append(hou_pythonpath) From ca1ef8a81d3c14fbf84938f2ffb26473da932316 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 25 Aug 2022 16:05:38 +0200 Subject: [PATCH 061/274] fix init (again) --- openpype/hosts/houdini/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/__init__.py b/openpype/hosts/houdini/__init__.py index 09ba66c834..38bf1fcc2d 100644 --- a/openpype/hosts/houdini/__init__.py +++ b/openpype/hosts/houdini/__init__.py @@ -1,6 +1,6 @@ from .addon import ( HoudiniAddon, - HOUDINI_HOST_DIR. + HOUDINI_HOST_DIR, ) From 33661b665cd60b6c4bc0fef13788f40cd906f0c9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 25 Aug 2022 16:10:17 +0200 Subject: [PATCH 062/274] global: submitting job is creating multiple job dependencies if multiple baking streams are submitted --- .../modules/deadline/plugins/publish/submit_publish_job.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 379953c9e4..2647dcf0cb 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -296,6 +296,12 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): for assembly_id in instance.data.get("assemblySubmissionJobs"): payload["JobInfo"]["JobDependency{}".format(job_index)] = assembly_id # noqa: E501 job_index += 1 + elif instance.data.get("bakingSubmissionJobs"): + self.log.info("Adding baking submission jobs as dependencies...") + job_index = 0 + for assembly_id in instance.data["bakingSubmissionJobs"]: + payload["JobInfo"]["JobDependency{}".format(job_index)] = assembly_id # noqa: E501 + job_index += 1 else: payload["JobInfo"]["JobDependency0"] = job["_id"] From b95e79e0d0374a3b95be5e5f21712975470dca93 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 25 Aug 2022 16:13:02 +0200 Subject: [PATCH 063/274] change how cwd is set for houdini process --- openpype/hosts/houdini/hooks/set_paths.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/hooks/set_paths.py b/openpype/hosts/houdini/hooks/set_paths.py index cd2f98fb76..04a33b1643 100644 --- a/openpype/hosts/houdini/hooks/set_paths.py +++ b/openpype/hosts/houdini/hooks/set_paths.py @@ -1,5 +1,4 @@ from openpype.lib import PreLaunchHook -import os class SetPath(PreLaunchHook): @@ -15,4 +14,4 @@ class SetPath(PreLaunchHook): self.log.warning("BUG: Workdir is not filled.") return - os.chdir(workdir) + self.launch_context.kwargs["cwd"] = workdir From 531682f316bc0ec3212ac31edc2881a988385056 Mon Sep 17 00:00:00 2001 From: Felix David Date: Thu, 25 Aug 2022 17:17:56 +0200 Subject: [PATCH 064/274] Kitsu|Change: Drop 'entities root' setting. Closes #3738 --- .../modules/kitsu/utils/update_op_with_zou.py | 109 +++++------------- .../defaults/project_settings/kitsu.json | 4 - .../projects_schema/schema_project_kitsu.json | 17 --- 3 files changed, 28 insertions(+), 102 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index e03cf2b30e..7c97e126e5 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -166,50 +166,21 @@ def update_op_assets( # Substitute item type for general classification (assets or shots) if item_type in ["Asset", "AssetType"]: - substitute_item_type = "assets" + entity_root_asset_name = "Assets" elif item_type in ["Episode", "Sequence"]: - substitute_item_type = "shots" - else: - substitute_item_type = f"{item_type.lower()}s" - entity_parent_folders = [ - f - for f in project_module_settings["entities_root"] - .get(substitute_item_type) - .split("/") - if f - ] + entity_root_asset_name = "Shots" # Root parent folder if exist visual_parent_doc_id = ( asset_doc_ids[parent_zou_id]["_id"] if parent_zou_id else None ) if visual_parent_doc_id is None: - # Find root folder docs - root_folder_docs = get_assets( + # Find root folder doc ("Assets" or "Shots") + root_folder_doc = get_asset_by_name( project_name, - asset_names=[entity_parent_folders[-1]], + asset_name=entity_root_asset_name, fields=["_id", "data.root_of"], ) - # NOTE: Not sure why it's checking for entity type? - # OP3 does not support multiple assets with same names so type - # filtering is irelevant. - # This way mimics previous implementation: - # ``` - # root_folder_doc = dbcon.find_one( - # { - # "type": "asset", - # "name": entity_parent_folders[-1], - # "data.root_of": substitute_item_type, - # }, - # ["_id"], - # ) - # ``` - root_folder_doc = None - for folder_doc in root_folder_docs: - root_of = folder_doc.get("data", {}).get("root_of") - if root_of == substitute_item_type: - root_folder_doc = folder_doc - break if root_folder_doc: visual_parent_doc_id = root_folder_doc["_id"] @@ -240,7 +211,7 @@ def update_op_assets( item_name = item["name"] # Set root folders parents - item_data["parents"] = entity_parent_folders + item_data["parents"] + item_data["parents"] = [entity_root_asset_name] + item_data["parents"] # Update 'data' different in zou DB updated_data = { @@ -396,54 +367,30 @@ def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): zou_ids_and_asset_docs[project["id"]] = project_doc # Create entities root folders - project_module_settings = get_project_settings(project_name)["kitsu"] - for entity_type, root in project_module_settings["entities_root"].items(): - parent_folders = root.split("/") - direct_parent_doc = None - for i, folder in enumerate(parent_folders, 1): - parent_doc = get_asset_by_name( - project_name, folder, fields=["_id", "data.root_of"] - ) - # NOTE: Not sure why it's checking for entity type? - # OP3 does not support multiple assets with same names so type - # filtering is irelevant. - # Also all of the entities could find be queried at once using - # 'get_assets'. - # This way mimics previous implementation: - # ``` - # parent_doc = dbcon.find_one( - # {"type": "asset", "name": folder, "data.root_of": entity_type} - # ) - # ``` - if ( - parent_doc - and parent_doc.get("data", {}).get("root_of") != entity_type - ): - parent_doc = None - - if not parent_doc: - direct_parent_doc = dbcon.insert_one( - { - "name": folder, - "type": "asset", - "schema": "openpype:asset-3.0", - "data": { - "root_of": entity_type, - "parents": parent_folders[:i], - "visualParent": direct_parent_doc.inserted_id - if direct_parent_doc - else None, - "tasks": {}, - }, - } - ) + to_insert = [ + { + "name": r, + "type": "asset", + "schema": "openpype:asset-3.0", + "data": { + "root_of": r, + "tasks": {}, + }, + } + for r in ["Assets", "Shots"] + if not get_asset_by_name( + project_name, r, fields=["_id", "data.root_of"] + ) + ] # Create - to_insert = [ - create_op_asset(item) - for item in all_entities - if item["id"] not in zou_ids_and_asset_docs.keys() - ] + to_insert.extend( + [ + create_op_asset(item) + for item in all_entities + if item["id"] not in zou_ids_and_asset_docs.keys() + ] + ) if to_insert: # Insert doc in DB dbcon.insert_many(to_insert) diff --git a/openpype/settings/defaults/project_settings/kitsu.json b/openpype/settings/defaults/project_settings/kitsu.json index ba02d8d259..3a9723b9c0 100644 --- a/openpype/settings/defaults/project_settings/kitsu.json +++ b/openpype/settings/defaults/project_settings/kitsu.json @@ -1,8 +1,4 @@ { - "entities_root": { - "assets": "Assets", - "shots": "Shots" - }, "entities_naming_pattern": { "episode": "E##", "sequence": "SQ##", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_kitsu.json b/openpype/settings/entities/schemas/projects_schema/schema_project_kitsu.json index 014a1b7886..fb47670e74 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_kitsu.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_kitsu.json @@ -5,23 +5,6 @@ "collapsible": true, "is_file": true, "children": [ - { - "type": "dict", - "key": "entities_root", - "label": "Entities root folder", - "children": [ - { - "type": "text", - "key": "assets", - "label": "Assets:" - }, - { - "type": "text", - "key": "shots", - "label": "Shots (includes Episodes & Sequences if any):" - } - ] - }, { "type": "dict", "key": "entities_naming_pattern", From 5908995349a6e416e3a7db975dc119e407747f85 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Aug 2022 11:00:34 +0200 Subject: [PATCH 065/274] moved helper functions 'get_errored_instances_from_context' and 'get_errored_plugins_from_context' into openpype.pipeline.publish --- openpype/pipeline/publish/__init__.py | 6 ++++ openpype/pipeline/publish/lib.py | 44 +++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/openpype/pipeline/publish/__init__.py b/openpype/pipeline/publish/__init__.py index aa7fe0bdbf..dbd0b696ec 100644 --- a/openpype/pipeline/publish/__init__.py +++ b/openpype/pipeline/publish/__init__.py @@ -14,6 +14,9 @@ from .lib import ( publish_plugins_discover, load_help_content_from_plugin, load_help_content_from_filepath, + + get_errored_instances_from_context, + get_errored_plugins_from_context, ) from .abstract_expected_files import ExpectedFiles @@ -38,6 +41,9 @@ __all__ = ( "load_help_content_from_plugin", "load_help_content_from_filepath", + "get_errored_instances_from_context", + "get_errored_plugins_from_context", + "ExpectedFiles", "RenderInstance", diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 9060a0bf4b..83a1e3ff1a 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -313,3 +313,47 @@ def remote_publish(log, close_plugin_name=None, raise_error=False): # Fatal Error is because of Deadline error_message = "Fatal Error: " + error_format.format(**result) raise RuntimeError(error_message) + + +def get_errored_instances_from_context(context): + """Collect failed instances from pyblish context. + + Args: + context (pyblish.api.Context): Publish context where we're looking + for failed instances. + + Returns: + List[pyblish.lib.Instance]: Instances which failed during processing. + """ + + instances = list() + for result in context.data["results"]: + if result["instance"] is None: + # When instance is None we are on the "context" result + continue + + if result["error"]: + instances.append(result["instance"]) + + return instances + + +def get_errored_plugins_from_context(context): + """Collect failed plugins from pyblish context. + + Args: + context (pyblish.api.Context): Publish context where we're looking + for failed plugins. + + Returns: + List[pyblish.api.Plugin]: Plugins which failed during processing. + """ + + plugins = list() + results = context.data.get("results", []) + for result in results: + if result["success"] is True: + continue + plugins.append(result["plugin"]) + + return plugins From 74a1847cfeaca882a66336c485ffd8cf47415a9c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Aug 2022 11:01:05 +0200 Subject: [PATCH 066/274] removed unused ValidationException --- openpype/api.py | 3 --- openpype/plugin.py | 4 ---- 2 files changed, 7 deletions(-) diff --git a/openpype/api.py b/openpype/api.py index c2227c1a52..0466eb7f78 100644 --- a/openpype/api.py +++ b/openpype/api.py @@ -49,7 +49,6 @@ from .plugin import ( ValidateContentsOrder, ValidateSceneOrder, ValidateMeshOrder, - ValidationException ) # temporary fix, might @@ -94,8 +93,6 @@ __all__ = [ "RepairAction", "RepairContextAction", - "ValidationException", - # get contextual data "version_up", "get_asset", diff --git a/openpype/plugin.py b/openpype/plugin.py index bb9bc2ff85..9b194a13ba 100644 --- a/openpype/plugin.py +++ b/openpype/plugin.py @@ -79,7 +79,3 @@ def contextplugin_should_run(plugin, context): return True return False - - -class ValidationException(Exception): - pass From 08675dcdf5dc9b845696c60383caa90cd2a9760d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Aug 2022 11:02:33 +0200 Subject: [PATCH 067/274] moved default repair actions into publish plugins --- openpype/pipeline/publish/__init__.py | 6 ++ openpype/pipeline/publish/publish_plugins.py | 59 +++++++++++++++++++- 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/publish/__init__.py b/openpype/pipeline/publish/__init__.py index dbd0b696ec..57d9da2167 100644 --- a/openpype/pipeline/publish/__init__.py +++ b/openpype/pipeline/publish/__init__.py @@ -7,6 +7,9 @@ from .publish_plugins import ( KnownPublishError, OpenPypePyblishPluginMixin, OptionalPyblishPluginMixin, + + RepairAction, + RepairContextAction, ) from .lib import ( @@ -36,6 +39,9 @@ __all__ = ( "OpenPypePyblishPluginMixin", "OptionalPyblishPluginMixin", + "RepairAction", + "RepairContextAction", + "DiscoverResult", "publish_plugins_discover", "load_help_content_from_plugin", diff --git a/openpype/pipeline/publish/publish_plugins.py b/openpype/pipeline/publish/publish_plugins.py index 71a2c675b6..00ac2c94ac 100644 --- a/openpype/pipeline/publish/publish_plugins.py +++ b/openpype/pipeline/publish/publish_plugins.py @@ -1,7 +1,15 @@ from abc import ABCMeta + +import pyblish.api from pyblish.plugin import MetaPlugin, ExplicitMetaPlugin + from openpype.lib import BoolDef -from .lib import load_help_content_from_plugin + +from .lib import ( + load_help_content_from_plugin, + get_errored_instances_from_context, + get_errored_plugins_from_data +) class AbstractMetaInstancePlugin(ABCMeta, MetaPlugin): @@ -184,3 +192,52 @@ class OptionalPyblishPluginMixin(OpenPypePyblishPluginMixin): if active is None: active = getattr(self, "active", True) return active + + +class RepairAction(pyblish.api.Action): + """Repairs the action + + To process the repairing this requires a static `repair(instance)` method + is available on the plugin. + """ + + label = "Repair" + on = "failed" # This action is only available on a failed plug-in + icon = "wrench" # Icon from Awesome Icon + + def process(self, context, plugin): + if not hasattr(plugin, "repair"): + raise RuntimeError("Plug-in does not have repair method.") + + # Get the errored instances + self.log.info("Finding failed instances..") + errored_instances = get_errored_instances_from_context(context) + + # Apply pyblish.logic to get the instances for the plug-in + instances = pyblish.api.instances_by_plugin(errored_instances, plugin) + for instance in instances: + plugin.repair(instance) + + +class RepairContextAction(pyblish.api.Action): + """Repairs the action + + To process the repairing this requires a static `repair(instance)` method + is available on the plugin. + """ + + label = "Repair" + on = "failed" # This action is only available on a failed plug-in + + def process(self, context, plugin): + if not hasattr(plugin, "repair"): + raise RuntimeError("Plug-in does not have repair method.") + + # Get the errored instances + self.log.info("Finding failed instances..") + errored_plugins = get_errored_plugins_from_data(context) + + # Apply pyblish.logic to get the instances for the plug-in + if plugin in errored_plugins: + self.log.info("Attempting fix ...") + plugin.repair(context) From e28603bf1dbde45d5080303ec6f0abbee5bdbd1b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Aug 2022 11:24:03 +0200 Subject: [PATCH 068/274] added 'context_plugin_should_run' to pipeline publish --- openpype/pipeline/publish/__init__.py | 4 ++ openpype/pipeline/publish/lib.py | 57 +++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/openpype/pipeline/publish/__init__.py b/openpype/pipeline/publish/__init__.py index 57d9da2167..06323ebf7c 100644 --- a/openpype/pipeline/publish/__init__.py +++ b/openpype/pipeline/publish/__init__.py @@ -20,6 +20,8 @@ from .lib import ( get_errored_instances_from_context, get_errored_plugins_from_context, + + context_plugin_should_run, ) from .abstract_expected_files import ExpectedFiles @@ -50,6 +52,8 @@ __all__ = ( "get_errored_instances_from_context", "get_errored_plugins_from_context", + "context_plugin_should_run", + "ExpectedFiles", "RenderInstance", diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 83a1e3ff1a..b3aa6242cd 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -357,3 +357,60 @@ def get_errored_plugins_from_context(context): plugins.append(result["plugin"]) return plugins + + +def filter_instances_for_context_plugin(plugin, context): + """Filter instances on context by context plugin filters. + + This is for cases when context plugin need similar filtering like instance + plugin have, but for some reason must run on context. + + Args: + plugin (pyblish.api.Plugin): Plugin with filters. + context (pyblish.api.Context): Pyblish context with insances. + + Returns: + Iterator[pyblish.lib.Instance]: Iteration of valid instances. + """ + + if not plugin.families: + return [] + + plugin_families = set(plugin.families) + for instance in context: + # Ignore inactive instances + if ( + not instance.data.get("publish", True) + or not instance.data.get("active", True) + ): + continue + + family = instance.data.get("family") + if family and family in plugin_families: + yield instance + + families = instance.data.get("families", []) + if any(f in plugin_families for f in families): + yield instance + + +def context_plugin_should_run(plugin, context): + """Return whether the ContextPlugin should run on the given context. + + This is a helper function to work around a bug pyblish-base#250 + Whenever a ContextPlugin sets specific families it will still trigger even + when no instances are present that have those families. + + This actually checks it correctly and returns whether it should run. + + Args: + plugin (pyblish.api.Plugin): Plugin with filters. + context (pyblish.api.Context): Pyblish context with insances. + + Returns: + bool: Context plugin should run based on valid instances. + """ + + for instance in filter_instances_for_context_plugin(plugin, context): + return True + return False From fa2234259e55a6a1f49681fc3f7210777a5e45b9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Aug 2022 11:27:20 +0200 Subject: [PATCH 069/274] replaced usages of 'contextplugin_should_run' with 'context_plugin_should_run' --- .../publish/validate_current_renderlayer_renderable.py | 4 ++-- .../hosts/maya/plugins/publish/validate_muster_connection.py | 4 ++-- .../maya/plugins/publish/validate_vray_translator_settings.py | 4 ++-- openpype/pipeline/publish/lib.py | 3 ++- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py b/openpype/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py index 3c3ea68fc6..f072e5e323 100644 --- a/openpype/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py +++ b/openpype/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py @@ -1,7 +1,7 @@ import pyblish.api from maya import cmds -from openpype.plugin import contextplugin_should_run +from openpype.pipeline.publish import context_plugin_should_run class ValidateCurrentRenderLayerIsRenderable(pyblish.api.ContextPlugin): @@ -24,7 +24,7 @@ class ValidateCurrentRenderLayerIsRenderable(pyblish.api.ContextPlugin): def process(self, context): # Workaround bug pyblish-base#250 - if not contextplugin_should_run(self, context): + if not context_plugin_should_run(self, context): return layer = cmds.editRenderLayerGlobals(query=True, currentRenderLayer=True) diff --git a/openpype/hosts/maya/plugins/publish/validate_muster_connection.py b/openpype/hosts/maya/plugins/publish/validate_muster_connection.py index 6dc7bd3bc4..856f1d933d 100644 --- a/openpype/hosts/maya/plugins/publish/validate_muster_connection.py +++ b/openpype/hosts/maya/plugins/publish/validate_muster_connection.py @@ -5,7 +5,7 @@ import appdirs import pyblish.api from openpype.lib import requests_get -from openpype.plugin import contextplugin_should_run +from openpype.pipeline.publish import context_plugin_should_run import openpype.hosts.maya.api.action @@ -26,7 +26,7 @@ class ValidateMusterConnection(pyblish.api.ContextPlugin): def process(self, context): # Workaround bug pyblish-base#250 - if not contextplugin_should_run(self, context): + if not context_plugin_should_run(self, context): return # test if we have environment set (redundant as this plugin shouldn' diff --git a/openpype/hosts/maya/plugins/publish/validate_vray_translator_settings.py b/openpype/hosts/maya/plugins/publish/validate_vray_translator_settings.py index 1deabde4a2..d611777f43 100644 --- a/openpype/hosts/maya/plugins/publish/validate_vray_translator_settings.py +++ b/openpype/hosts/maya/plugins/publish/validate_vray_translator_settings.py @@ -2,7 +2,7 @@ """Validate VRay Translator settings.""" import pyblish.api import openpype.api -from openpype.plugin import contextplugin_should_run +from openpype.pipeline.publish import context_plugin_should_run from maya import cmds @@ -18,7 +18,7 @@ class ValidateVRayTranslatorEnabled(pyblish.api.ContextPlugin): def process(self, context): """Plugin entry point.""" # Workaround bug pyblish-base#250 - if not contextplugin_should_run(self, context): + if not context_plugin_should_run(self, context): return invalid = self.get_invalid(context) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index b3aa6242cd..deecc262c3 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -363,7 +363,8 @@ def filter_instances_for_context_plugin(plugin, context): """Filter instances on context by context plugin filters. This is for cases when context plugin need similar filtering like instance - plugin have, but for some reason must run on context. + plugin have, but for some reason must run on context or should find out + if there is at least one instance with a family. Args: plugin (pyblish.api.Plugin): Plugin with filters. From 66bbb5497af0cb0f4a0c32d51e4b9e2713942e2f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Aug 2022 11:32:18 +0200 Subject: [PATCH 070/274] fix all families filter --- openpype/pipeline/publish/lib.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index deecc262c3..266e0bbf08 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -378,6 +378,7 @@ def filter_instances_for_context_plugin(plugin, context): return [] plugin_families = set(plugin.families) + all_families = "*" in plugin_families for instance in context: # Ignore inactive instances if ( @@ -387,11 +388,12 @@ def filter_instances_for_context_plugin(plugin, context): continue family = instance.data.get("family") - if family and family in plugin_families: - yield instance - - families = instance.data.get("families", []) - if any(f in plugin_families for f in families): + families = instance.data.get("families") or [] + if ( + all_families + or (family and family in plugin_families) + or any(f in plugin_families for f in families) + ): yield instance From 16bd7a3fef62c5469c9c13aa6881818a3cf07bac Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Aug 2022 11:50:53 +0200 Subject: [PATCH 071/274] fix imported function name --- openpype/pipeline/publish/publish_plugins.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/publish/publish_plugins.py b/openpype/pipeline/publish/publish_plugins.py index 00ac2c94ac..5bb6b5aaff 100644 --- a/openpype/pipeline/publish/publish_plugins.py +++ b/openpype/pipeline/publish/publish_plugins.py @@ -8,7 +8,7 @@ from openpype.lib import BoolDef from .lib import ( load_help_content_from_plugin, get_errored_instances_from_context, - get_errored_plugins_from_data + get_errored_plugins_from_context ) @@ -235,7 +235,7 @@ class RepairContextAction(pyblish.api.Action): # Get the errored instances self.log.info("Finding failed instances..") - errored_plugins = get_errored_plugins_from_data(context) + errored_plugins = get_errored_plugins_from_context(context) # Apply pyblish.logic to get the instances for the plug-in if plugin in errored_plugins: From dc9971fd7290705adfd2f79bcd6c13ee84024fe3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Aug 2022 11:56:18 +0200 Subject: [PATCH 072/274] fix py2 compatibility --- openpype/pipeline/publish/lib.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 266e0bbf08..afa02ac54c 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -374,12 +374,15 @@ def filter_instances_for_context_plugin(plugin, context): Iterator[pyblish.lib.Instance]: Iteration of valid instances. """ - if not plugin.families: - return [] + instances = [] + plugin_families = set() + all_families = False + if plugin.families: + instances = context + plugin_families = set(plugin.families) + all_families = "*" in plugin_families - plugin_families = set(plugin.families) - all_families = "*" in plugin_families - for instance in context: + for instance in instances: # Ignore inactive instances if ( not instance.data.get("publish", True) From 5203814b0e205d30df86787fc1868f2cd4d66750 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Aug 2022 11:59:45 +0200 Subject: [PATCH 073/274] added helper function to get staging dir even outside 'Extractor' plugin --- openpype/pipeline/publish/__init__.py | 4 ++++ openpype/pipeline/publish/lib.py | 28 +++++++++++++++++++++++++++ openpype/plugin.py | 14 ++++---------- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/openpype/pipeline/publish/__init__.py b/openpype/pipeline/publish/__init__.py index 06323ebf7c..9c6462740f 100644 --- a/openpype/pipeline/publish/__init__.py +++ b/openpype/pipeline/publish/__init__.py @@ -21,7 +21,9 @@ from .lib import ( get_errored_instances_from_context, get_errored_plugins_from_context, + filter_instances_for_context_plugin, context_plugin_should_run, + get_instance_staging_dir, ) from .abstract_expected_files import ExpectedFiles @@ -52,7 +54,9 @@ __all__ = ( "get_errored_instances_from_context", "get_errored_plugins_from_context", + "filter_instances_for_context_plugin", "context_plugin_should_run", + "get_instance_staging_dir", "ExpectedFiles", diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index afa02ac54c..4f06f0e6fd 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -2,6 +2,7 @@ import os import sys import types import inspect +import tempfile import xml.etree.ElementTree import six @@ -420,3 +421,30 @@ def context_plugin_should_run(plugin, context): for instance in filter_instances_for_context_plugin(plugin, context): return True return False + + +def get_instance_staging_dir(instance): + """Unified way how staging dir is stored and created on instances. + + First check if 'stagingDir' is already set in instance data. If there is + not create new in tempdir. + + Note: + Staging dir does not have to be necessarily in tempdir so be carefull + about it's usage. + + Args: + instance (pyblish.lib.Instance): Instance for which we want to get + staging dir. + + Returns: + str: Path to staging dir of instance. + """ + + staging_dir = instance.data.get("stagingDir") + if not staging_dir: + instance.data["stagingDir"] = os.path.normpath( + tempfile.mkdtemp(prefix="pyblish_tmp_") + ) + + return staging_dir diff --git a/openpype/plugin.py b/openpype/plugin.py index 9b194a13ba..9a131f320c 100644 --- a/openpype/plugin.py +++ b/openpype/plugin.py @@ -1,5 +1,3 @@ -import tempfile -import os import pyblish.api ValidatePipelineOrder = pyblish.api.ValidatorOrder + 0.05 @@ -18,7 +16,8 @@ class InstancePlugin(pyblish.api.InstancePlugin): super(InstancePlugin, cls).process(cls, *args, **kwargs) -class Extractor(InstancePlugin): +# NOTE: This class is used on so many places I gave up moving it +class Extractor(pyblish.api.InstancePlugin): """Extractor base class. The extractor base class implements a "staging_dir" function used to @@ -36,15 +35,10 @@ class Extractor(InstancePlugin): Upon calling this method the staging directory is stored inside the instance.data['stagingDir'] """ - staging_dir = instance.data.get('stagingDir', None) - if not staging_dir: - staging_dir = os.path.normpath( - tempfile.mkdtemp(prefix="pyblish_tmp_") - ) - instance.data['stagingDir'] = staging_dir + from openpype.pipeline.publish import get_instance_staging_dir - return staging_dir + return get_instance_staging_dir(instance) def contextplugin_should_run(plugin, context): From 4e8b40e3a01ea3d6759af19be390f3673ec9a069 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Aug 2022 12:01:09 +0200 Subject: [PATCH 074/274] 'contextplugin_should_run' is marked as deprecated --- openpype/plugin.py | 73 +++++++++++++++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 20 deletions(-) diff --git a/openpype/plugin.py b/openpype/plugin.py index 9a131f320c..5896cbd749 100644 --- a/openpype/plugin.py +++ b/openpype/plugin.py @@ -1,3 +1,6 @@ +import functools +import warnings + import pyblish.api ValidatePipelineOrder = pyblish.api.ValidatorOrder + 0.05 @@ -6,6 +9,53 @@ ValidateSceneOrder = pyblish.api.ValidatorOrder + 0.2 ValidateMeshOrder = pyblish.api.ValidatorOrder + 0.3 +class PluginDeprecatedWarning(DeprecationWarning): + pass + + +def _deprecation_warning(item_name, warning_message): + warnings.simplefilter("always", PluginDeprecatedWarning) + warnings.warn( + ( + "Call to deprecated function '{}'" + "\nFunction was moved or removed.{}" + ).format(item_name, warning_message), + category=PluginDeprecatedWarning, + stacklevel=4 + ) + + +def deprecated(new_destination): + """Mark functions as deprecated. + + It will result in a warning being emitted when the function is used. + """ + + func = None + if callable(new_destination): + func = new_destination + new_destination = None + + def _decorator(decorated_func): + if new_destination is None: + warning_message = ( + " Please check content of deprecated function to figure out" + " possible replacement." + ) + else: + warning_message = " Please replace your usage with '{}'.".format( + new_destination + ) + + @functools.wraps(decorated_func) + def wrapper(*args, **kwargs): + _deprecation_warning(decorated_func.__name__, warning_message) + return decorated_func(*args, **kwargs) + return wrapper + + if func is None: + return _decorator + return _decorator(func) class ContextPlugin(pyblish.api.ContextPlugin): def process(cls, *args, **kwargs): super(ContextPlugin, cls).process(cls, *args, **kwargs) @@ -41,6 +91,7 @@ class Extractor(pyblish.api.InstancePlugin): return get_instance_staging_dir(instance) +@deprecated("openpype.pipeline.publish.context_plugin_should_run") def contextplugin_should_run(plugin, context): """Return whether the ContextPlugin should run on the given context. @@ -51,25 +102,7 @@ def contextplugin_should_run(plugin, context): This actually checks it correctly and returns whether it should run. """ - required = set(plugin.families) - # When no filter always run - if "*" in required: - return True + from openpype.pipeline.publish import context_plugin_should_run - for instance in context: - - # Ignore inactive instances - if (not instance.data.get("publish", True) or - not instance.data.get("active", True)): - continue - - families = instance.data.get("families", []) - if any(f in required for f in families): - return True - - family = instance.data.get("family") - if family and family in required: - return True - - return False + return context_plugin_should_run(plugin, context) From 27517897ab6b78dbe65d1e1281dc21f757a30ddb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Aug 2022 12:07:26 +0200 Subject: [PATCH 075/274] use new imports of 'get_errored_plugins_from_context' and 'get_errored_instances_from_context' --- .../aftereffects/plugins/publish/increment_workfile.py | 4 ++-- openpype/hosts/blender/api/action.py | 2 +- .../plugins/publish/increment_current_file_deadline.py | 4 ++-- .../hosts/harmony/plugins/publish/increment_workfile.py | 4 ++-- .../hosts/houdini/plugins/publish/increment_current_file.py | 6 +++--- .../plugins/publish/increment_current_file_deadline.py | 6 +++--- openpype/hosts/maya/api/action.py | 2 +- .../maya/plugins/publish/increment_current_file_deadline.py | 5 ++--- openpype/hosts/nuke/api/actions.py | 2 +- openpype/hosts/nuke/plugins/publish/validate_write_nodes.py | 2 +- .../hosts/photoshop/plugins/publish/increment_workfile.py | 4 ++-- openpype/hosts/resolve/api/action.py | 2 +- 12 files changed, 21 insertions(+), 22 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/publish/increment_workfile.py b/openpype/hosts/aftereffects/plugins/publish/increment_workfile.py index 0829355f3b..d8f6ef5d27 100644 --- a/openpype/hosts/aftereffects/plugins/publish/increment_workfile.py +++ b/openpype/hosts/aftereffects/plugins/publish/increment_workfile.py @@ -1,6 +1,6 @@ import pyblish.api -from openpype.action import get_errored_plugins_from_data from openpype.lib import version_up +from openpype.pipeline.publish import get_errored_plugins_from_context from openpype.hosts.aftereffects.api import get_stub @@ -18,7 +18,7 @@ class IncrementWorkfile(pyblish.api.InstancePlugin): optional = True def process(self, instance): - errored_plugins = get_errored_plugins_from_data(instance.context) + errored_plugins = get_errored_plugins_from_context(instance.context) if errored_plugins: raise RuntimeError( "Skipping incrementing current file because publishing failed." diff --git a/openpype/hosts/blender/api/action.py b/openpype/hosts/blender/api/action.py index 09ef76326e..fe0833e39f 100644 --- a/openpype/hosts/blender/api/action.py +++ b/openpype/hosts/blender/api/action.py @@ -2,7 +2,7 @@ import bpy import pyblish.api -from openpype.api import get_errored_instances_from_context +from openpype.pipeline.publish import get_errored_instances_from_context class SelectInvalidAction(pyblish.api.Action): diff --git a/openpype/hosts/fusion/plugins/publish/increment_current_file_deadline.py b/openpype/hosts/fusion/plugins/publish/increment_current_file_deadline.py index 6483454d96..5c595638e9 100644 --- a/openpype/hosts/fusion/plugins/publish/increment_current_file_deadline.py +++ b/openpype/hosts/fusion/plugins/publish/increment_current_file_deadline.py @@ -17,9 +17,9 @@ class FusionIncrementCurrentFile(pyblish.api.ContextPlugin): def process(self, context): from openpype.lib import version_up - from openpype.action import get_errored_plugins_from_data + from openpype.pipeline.publish import get_errored_plugins_from_context - errored_plugins = get_errored_plugins_from_data(context) + errored_plugins = get_errored_plugins_from_context(context) if any(plugin.__name__ == "FusionSubmitDeadline" for plugin in errored_plugins): raise RuntimeError("Skipping incrementing current file because " diff --git a/openpype/hosts/harmony/plugins/publish/increment_workfile.py b/openpype/hosts/harmony/plugins/publish/increment_workfile.py index 417377fff8..1caf581567 100644 --- a/openpype/hosts/harmony/plugins/publish/increment_workfile.py +++ b/openpype/hosts/harmony/plugins/publish/increment_workfile.py @@ -1,7 +1,7 @@ import os import pyblish.api -from openpype.action import get_errored_plugins_from_data +from openpype.pipeline.publish import get_errored_plugins_from_context from openpype.lib import version_up import openpype.hosts.harmony.api as harmony @@ -19,7 +19,7 @@ class IncrementWorkfile(pyblish.api.InstancePlugin): optional = True def process(self, instance): - errored_plugins = get_errored_plugins_from_data(instance.context) + errored_plugins = get_errored_plugins_from_context(instance.context) if errored_plugins: raise RuntimeError( "Skipping incrementing current file because publishing failed." diff --git a/openpype/hosts/houdini/plugins/publish/increment_current_file.py b/openpype/hosts/houdini/plugins/publish/increment_current_file.py index c5cacd1880..5cb14d732a 100644 --- a/openpype/hosts/houdini/plugins/publish/increment_current_file.py +++ b/openpype/hosts/houdini/plugins/publish/increment_current_file.py @@ -1,8 +1,8 @@ import pyblish.api -from openpype.api import version_up -from openpype.action import get_errored_plugins_from_data +from openpype.lib import version_up from openpype.pipeline import registered_host +from openpype.pipeline.publish import get_errored_plugins_from_context class IncrementCurrentFile(pyblish.api.InstancePlugin): @@ -30,7 +30,7 @@ class IncrementCurrentFile(pyblish.api.InstancePlugin): context.data[key] = True context = instance.context - errored_plugins = get_errored_plugins_from_data(context) + errored_plugins = get_errored_plugins_from_context(context) if any( plugin.__name__ == "HoudiniSubmitPublishDeadline" for plugin in errored_plugins diff --git a/openpype/hosts/houdini/plugins/publish/increment_current_file_deadline.py b/openpype/hosts/houdini/plugins/publish/increment_current_file_deadline.py index faa015f739..cb0d7e3680 100644 --- a/openpype/hosts/houdini/plugins/publish/increment_current_file_deadline.py +++ b/openpype/hosts/houdini/plugins/publish/increment_current_file_deadline.py @@ -1,8 +1,8 @@ import pyblish.api import hou -from openpype.api import version_up -from openpype.action import get_errored_plugins_from_data +from openpype.lib import version_up +from openpype.pipeline.publish import get_errored_plugins_from_context class IncrementCurrentFileDeadline(pyblish.api.ContextPlugin): @@ -19,7 +19,7 @@ class IncrementCurrentFileDeadline(pyblish.api.ContextPlugin): def process(self, context): - errored_plugins = get_errored_plugins_from_data(context) + errored_plugins = get_errored_plugins_from_context(context) if any( plugin.__name__ == "HoudiniSubmitPublishDeadline" for plugin in errored_plugins diff --git a/openpype/hosts/maya/api/action.py b/openpype/hosts/maya/api/action.py index 90605734e7..065fdf3691 100644 --- a/openpype/hosts/maya/api/action.py +++ b/openpype/hosts/maya/api/action.py @@ -5,7 +5,7 @@ import pyblish.api from openpype.client import get_asset_by_name from openpype.pipeline import legacy_io -from openpype.api import get_errored_instances_from_context +from openpype.pipeline.publish import get_errored_instances_from_context class GenerateUUIDsOnInvalidAction(pyblish.api.Action): diff --git a/openpype/hosts/maya/plugins/publish/increment_current_file_deadline.py b/openpype/hosts/maya/plugins/publish/increment_current_file_deadline.py index f9cfac3eb9..b5d5847e9f 100644 --- a/openpype/hosts/maya/plugins/publish/increment_current_file_deadline.py +++ b/openpype/hosts/maya/plugins/publish/increment_current_file_deadline.py @@ -16,12 +16,11 @@ class IncrementCurrentFileDeadline(pyblish.api.ContextPlugin): def process(self, context): - import os from maya import cmds from openpype.lib import version_up - from openpype.action import get_errored_plugins_from_data + from openpype.pipeline.publish import get_errored_plugins_from_context - errored_plugins = get_errored_plugins_from_data(context) + errored_plugins = get_errored_plugins_from_context(context) if any(plugin.__name__ == "MayaSubmitDeadline" for plugin in errored_plugins): raise RuntimeError("Skipping incrementing current file because " diff --git a/openpype/hosts/nuke/api/actions.py b/openpype/hosts/nuke/api/actions.py index c4a6f0fb84..92b83560da 100644 --- a/openpype/hosts/nuke/api/actions.py +++ b/openpype/hosts/nuke/api/actions.py @@ -1,6 +1,6 @@ import pyblish.api -from openpype.api import get_errored_instances_from_context +from openpype.pipeline.publish import get_errored_instances_from_context from .lib import ( reset_selection, select_nodes diff --git a/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py b/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py index 362ff31174..5a8bc2022e 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py +++ b/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py @@ -1,5 +1,5 @@ import pyblish.api -from openpype.api import get_errored_instances_from_context +from openpype.pipeline.publish import get_errored_instances_from_context from openpype.hosts.nuke.api.lib import ( get_write_node_template_attr, set_node_knobs_from_settings diff --git a/openpype/hosts/photoshop/plugins/publish/increment_workfile.py b/openpype/hosts/photoshop/plugins/publish/increment_workfile.py index 92132c393b..665dd67fc5 100644 --- a/openpype/hosts/photoshop/plugins/publish/increment_workfile.py +++ b/openpype/hosts/photoshop/plugins/publish/increment_workfile.py @@ -1,6 +1,6 @@ import os import pyblish.api -from openpype.action import get_errored_plugins_from_data +from openpype.pipeline.publish import get_errored_plugins_from_context from openpype.lib import version_up from openpype.hosts.photoshop import api as photoshop @@ -19,7 +19,7 @@ class IncrementWorkfile(pyblish.api.InstancePlugin): optional = True def process(self, instance): - errored_plugins = get_errored_plugins_from_data(instance.context) + errored_plugins = get_errored_plugins_from_context(instance.context) if errored_plugins: raise RuntimeError( "Skipping incrementing current file because publishing failed." diff --git a/openpype/hosts/resolve/api/action.py b/openpype/hosts/resolve/api/action.py index d55a24a39a..ceedc2cc54 100644 --- a/openpype/hosts/resolve/api/action.py +++ b/openpype/hosts/resolve/api/action.py @@ -4,7 +4,7 @@ from __future__ import absolute_import import pyblish.api -from openpype.action import get_errored_instances_from_context +from openpype.pipeline.publish import get_errored_instances_from_context class SelectInvalidAction(pyblish.api.Action): From 94306ae8425af2250c766fa280a0b5a54982fc14 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Aug 2022 12:08:21 +0200 Subject: [PATCH 076/274] marked 'get_errored_plugins_from_data' and 'get_errored_instances_from_context' as deprecated in 'openpype.action' --- openpype/action.py | 83 +++++++++++++++++++++++++++++----------------- 1 file changed, 53 insertions(+), 30 deletions(-) diff --git a/openpype/action.py b/openpype/action.py index 50741875e4..8949858498 100644 --- a/openpype/action.py +++ b/openpype/action.py @@ -1,42 +1,65 @@ -# absolute_import is needed to counter the `module has no cmds error` in Maya -from __future__ import absolute_import - +import warnings +import functools import pyblish.api -def get_errored_instances_from_context(context): - - instances = list() - for result in context.data["results"]: - if result["instance"] is None: - # When instance is None we are on the "context" result - continue - - if result["error"]: - instances.append(result["instance"]) - - return instances +class ActionDeprecatedWarning(DeprecationWarning): + pass -def get_errored_plugins_from_data(context): - """Get all failed validation plugins - - Args: - context (object): - - Returns: - list of plugins which failed during validation +def deprecated(new_destination): + """Mark functions as deprecated. + It will result in a warning being emitted when the function is used. """ - plugins = list() - results = context.data.get("results", []) - for result in results: - if result["success"] is True: - continue - plugins.append(result["plugin"]) + func = None + if callable(new_destination): + func = new_destination + new_destination = None - return plugins + def _decorator(decorated_func): + if new_destination is None: + warning_message = ( + " Please check content of deprecated function to figure out" + " possible replacement." + ) + else: + warning_message = " Please replace your usage with '{}'.".format( + new_destination + ) + + @functools.wraps(decorated_func) + def wrapper(*args, **kwargs): + warnings.simplefilter("always", ActionDeprecatedWarning) + warnings.warn( + ( + "Call to deprecated function '{}'" + "\nFunction was moved or removed.{}" + ).format(decorated_func.__name__, warning_message), + category=ActionDeprecatedWarning, + stacklevel=4 + ) + return decorated_func(*args, **kwargs) + return wrapper + + if func is None: + return _decorator + return _decorator(func) + + +@deprecated("openpype.pipeline.publish.get_errored_instances_from_context") +def get_errored_instances_from_context(context): + from openpype.pipeline.publish import get_errored_instances_from_context + + return get_errored_instances_from_context(context) + + +@deprecated("openpype.pipeline.publish.get_errored_plugins_from_context") +def get_errored_plugins_from_data(context): + from openpype.pipeline.publish import get_errored_plugins_from_context + + return get_errored_plugins_from_context(context) class RepairAction(pyblish.api.Action): From 2e412b55c1d526373e8b2eb35250c2409a60a1ba Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Aug 2022 12:25:02 +0200 Subject: [PATCH 077/274] use new import sources for 'RepairContextAction' and 'RepairAction' --- .../fusion/plugins/publish/validate_background_depth.py | 4 ++-- .../plugins/publish/validate_create_folder_checked.py | 4 ++-- .../houdini/plugins/publish/validate_remote_publish.py | 4 ++-- .../plugins/publish/validate_remote_publish_enabled.py | 4 ++-- .../validate_animation_out_set_related_node_ids.py | 3 ++- .../maya/plugins/publish/validate_ass_relative_paths.py | 3 ++- .../maya/plugins/publish/validate_assembly_transforms.py | 3 ++- .../hosts/maya/plugins/publish/validate_attributes.py | 3 ++- .../hosts/maya/plugins/publish/validate_color_sets.py | 3 ++- .../hosts/maya/plugins/publish/validate_frame_range.py | 3 ++- .../hosts/maya/plugins/publish/validate_loaded_plugin.py | 7 ++++--- .../plugins/publish/validate_look_id_reference_edits.py | 3 ++- .../maya/plugins/publish/validate_look_shading_group.py | 3 ++- .../hosts/maya/plugins/publish/validate_maya_units.py | 3 ++- .../plugins/publish/validate_mesh_arnold_attributes.py | 3 ++- .../plugins/publish/validate_mesh_normals_unlocked.py | 3 ++- .../plugins/publish/validate_mesh_shader_connections.py | 3 ++- .../maya/plugins/publish/validate_mesh_single_uv_set.py | 3 ++- .../maya/plugins/publish/validate_mesh_uv_set_map1.py | 3 ++- .../plugins/publish/validate_mesh_vertices_have_edges.py | 3 ++- .../maya/plugins/publish/validate_muster_connection.py | 8 +++++--- .../hosts/maya/plugins/publish/validate_no_namespace.py | 3 ++- .../maya/plugins/publish/validate_no_null_transforms.py | 3 ++- .../plugins/publish/validate_node_ids_deformed_shapes.py | 3 ++- .../maya/plugins/publish/validate_render_image_rule.py | 3 ++- .../maya/plugins/publish/validate_rendersettings.py | 3 ++- .../maya/plugins/publish/validate_rig_controllers.py | 4 ++-- .../validate_rig_controllers_arnold_attributes.py | 3 ++- .../maya/plugins/publish/validate_rig_joints_hidden.py | 3 ++- .../plugins/publish/validate_rig_out_set_node_ids.py | 3 ++- .../maya/plugins/publish/validate_rig_output_ids.py | 3 ++- .../maya/plugins/publish/validate_shape_default_names.py | 3 ++- .../maya/plugins/publish/validate_shape_render_stats.py | 3 ++- .../hosts/maya/plugins/publish/validate_shape_zero.py | 3 ++- .../maya/plugins/publish/validate_unreal_up_axis.py | 3 ++- .../publish/validate_vray_distributed_rendering.py | 3 ++- .../plugins/publish/validate_vray_referenced_aovs.py | 4 ++-- .../plugins/publish/validate_vray_translator_settings.py | 7 +++++-- .../plugins/publish/validate_yeti_rig_cache_state.py | 4 ++-- openpype/hosts/nuke/plugins/publish/validate_knobs.py | 9 ++++++--- .../nuke/plugins/publish/validate_output_resolution.py | 6 +++--- .../nuke/plugins/publish/validate_script_attributes.py | 4 ++-- .../hosts/nuke/plugins/publish/validate_write_legacy.py | 5 +++-- 43 files changed, 100 insertions(+), 61 deletions(-) diff --git a/openpype/hosts/fusion/plugins/publish/validate_background_depth.py b/openpype/hosts/fusion/plugins/publish/validate_background_depth.py index a0734d8278..4268fab528 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_background_depth.py +++ b/openpype/hosts/fusion/plugins/publish/validate_background_depth.py @@ -1,6 +1,6 @@ import pyblish.api -from openpype import action +from openpype.pipeline.publish import RepairAction class ValidateBackgroundDepth(pyblish.api.InstancePlugin): @@ -8,7 +8,7 @@ class ValidateBackgroundDepth(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Validate Background Depth 32 bit" - actions = [action.RepairAction] + actions = [RepairAction] hosts = ["fusion"] families = ["render"] optional = True diff --git a/openpype/hosts/fusion/plugins/publish/validate_create_folder_checked.py b/openpype/hosts/fusion/plugins/publish/validate_create_folder_checked.py index 45ed53f65c..f6beefefc1 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_create_folder_checked.py +++ b/openpype/hosts/fusion/plugins/publish/validate_create_folder_checked.py @@ -1,6 +1,6 @@ import pyblish.api -from openpype import action +from openpype.pipeline.publish import RepairAction class ValidateCreateFolderChecked(pyblish.api.InstancePlugin): @@ -11,7 +11,7 @@ class ValidateCreateFolderChecked(pyblish.api.InstancePlugin): """ order = pyblish.api.ValidatorOrder - actions = [action.RepairAction] + actions = [RepairAction] label = "Validate Create Folder Checked" families = ["render"] hosts = ["fusion"] diff --git a/openpype/hosts/houdini/plugins/publish/validate_remote_publish.py b/openpype/hosts/houdini/plugins/publish/validate_remote_publish.py index 95c66edff0..0ab182c584 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_remote_publish.py +++ b/openpype/hosts/houdini/plugins/publish/validate_remote_publish.py @@ -1,7 +1,7 @@ import pyblish.api -import openpype.api from openpype.hosts.houdini.api import lib +from openpype.pipeline.publish import RepairContextAction import hou @@ -14,7 +14,7 @@ class ValidateRemotePublishOutNode(pyblish.api.ContextPlugin): hosts = ["houdini"] targets = ["deadline"] label = "Remote Publish ROP node" - actions = [openpype.api.RepairContextAction] + actions = [RepairContextAction] def process(self, context): diff --git a/openpype/hosts/houdini/plugins/publish/validate_remote_publish_enabled.py b/openpype/hosts/houdini/plugins/publish/validate_remote_publish_enabled.py index b681fd0ee1..afc8df7528 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_remote_publish_enabled.py +++ b/openpype/hosts/houdini/plugins/publish/validate_remote_publish_enabled.py @@ -1,7 +1,7 @@ import pyblish.api -import openpype.api import hou +from openpype.pipeline.publish import RepairContextAction class ValidateRemotePublishEnabled(pyblish.api.ContextPlugin): @@ -12,7 +12,7 @@ class ValidateRemotePublishEnabled(pyblish.api.ContextPlugin): hosts = ["houdini"] targets = ["deadline"] label = "Remote Publish ROP enabled" - actions = [openpype.api.RepairContextAction] + actions = [RepairContextAction] def process(self, context): diff --git a/openpype/hosts/maya/plugins/publish/validate_animation_out_set_related_node_ids.py b/openpype/hosts/maya/plugins/publish/validate_animation_out_set_related_node_ids.py index 05d63f1d56..5f4d92bed9 100644 --- a/openpype/hosts/maya/plugins/publish/validate_animation_out_set_related_node_ids.py +++ b/openpype/hosts/maya/plugins/publish/validate_animation_out_set_related_node_ids.py @@ -4,6 +4,7 @@ import pyblish.api import openpype.api import openpype.hosts.maya.api.action from openpype.hosts.maya.api import lib +from openpype.pipeline.publish import RepairAction class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin): @@ -22,7 +23,7 @@ class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin): label = 'Animation Out Set Related Node Ids' actions = [ openpype.hosts.maya.api.action.SelectInvalidAction, - openpype.api.RepairAction + RepairAction ] def process(self, instance): diff --git a/openpype/hosts/maya/plugins/publish/validate_ass_relative_paths.py b/openpype/hosts/maya/plugins/publish/validate_ass_relative_paths.py index 5fb9bd98b1..3de8e55a41 100644 --- a/openpype/hosts/maya/plugins/publish/validate_ass_relative_paths.py +++ b/openpype/hosts/maya/plugins/publish/validate_ass_relative_paths.py @@ -6,6 +6,7 @@ import maya.cmds as cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import RepairAction class ValidateAssRelativePaths(pyblish.api.InstancePlugin): @@ -15,7 +16,7 @@ class ValidateAssRelativePaths(pyblish.api.InstancePlugin): hosts = ['maya'] families = ['ass'] label = "ASS has relative texture paths" - actions = [openpype.api.RepairAction] + actions = [RepairAction] def process(self, instance): # we cannot ask this until user open render settings as diff --git a/openpype/hosts/maya/plugins/publish/validate_assembly_transforms.py b/openpype/hosts/maya/plugins/publish/validate_assembly_transforms.py index dca59b147b..f793846555 100644 --- a/openpype/hosts/maya/plugins/publish/validate_assembly_transforms.py +++ b/openpype/hosts/maya/plugins/publish/validate_assembly_transforms.py @@ -4,6 +4,7 @@ import openpype.api from maya import cmds import openpype.hosts.maya.api.action +from openpype.pipeline.publish import RepairAction class ValidateAssemblyModelTransforms(pyblish.api.InstancePlugin): @@ -29,7 +30,7 @@ class ValidateAssemblyModelTransforms(pyblish.api.InstancePlugin): label = "Assembly Model Transforms" families = ["assembly"] actions = [openpype.hosts.maya.api.action.SelectInvalidAction, - openpype.api.RepairAction] + RepairAction] prompt_message = ("You are about to reset the matrix to the default values." " This can alter the look of your scene. " diff --git a/openpype/hosts/maya/plugins/publish/validate_attributes.py b/openpype/hosts/maya/plugins/publish/validate_attributes.py index e2a22f80b6..e5b72ac3a3 100644 --- a/openpype/hosts/maya/plugins/publish/validate_attributes.py +++ b/openpype/hosts/maya/plugins/publish/validate_attributes.py @@ -2,6 +2,7 @@ import pymel.core as pm import pyblish.api import openpype.api +from openpype.pipeline.publish import RepairContextAction class ValidateAttributes(pyblish.api.ContextPlugin): @@ -19,7 +20,7 @@ class ValidateAttributes(pyblish.api.ContextPlugin): order = openpype.api.ValidateContentsOrder label = "Attributes" hosts = ["maya"] - actions = [openpype.api.RepairContextAction] + actions = [RepairContextAction] optional = True attributes = None diff --git a/openpype/hosts/maya/plugins/publish/validate_color_sets.py b/openpype/hosts/maya/plugins/publish/validate_color_sets.py index 45224b0672..ecc5c78e43 100644 --- a/openpype/hosts/maya/plugins/publish/validate_color_sets.py +++ b/openpype/hosts/maya/plugins/publish/validate_color_sets.py @@ -3,6 +3,7 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import RepairAction class ValidateColorSets(pyblish.api.Validator): @@ -19,7 +20,7 @@ class ValidateColorSets(pyblish.api.Validator): category = 'geometry' label = 'Mesh ColorSets' actions = [openpype.hosts.maya.api.action.SelectInvalidAction, - openpype.api.RepairAction] + RepairAction] optional = True @staticmethod diff --git a/openpype/hosts/maya/plugins/publish/validate_frame_range.py b/openpype/hosts/maya/plugins/publish/validate_frame_range.py index c51766379e..9094176abb 100644 --- a/openpype/hosts/maya/plugins/publish/validate_frame_range.py +++ b/openpype/hosts/maya/plugins/publish/validate_frame_range.py @@ -2,6 +2,7 @@ import pyblish.api import openpype.api from maya import cmds +from openpype.pipeline.publish import RepairAction class ValidateFrameRange(pyblish.api.InstancePlugin): @@ -26,7 +27,7 @@ class ValidateFrameRange(pyblish.api.InstancePlugin): "review", "yeticache"] optional = True - actions = [openpype.api.RepairAction] + actions = [RepairAction] exclude_families = [] def process(self, instance): diff --git a/openpype/hosts/maya/plugins/publish/validate_loaded_plugin.py b/openpype/hosts/maya/plugins/publish/validate_loaded_plugin.py index 9306d8ce15..624074aaf9 100644 --- a/openpype/hosts/maya/plugins/publish/validate_loaded_plugin.py +++ b/openpype/hosts/maya/plugins/publish/validate_loaded_plugin.py @@ -1,7 +1,8 @@ +import os import pyblish.api import maya.cmds as cmds -import openpype.api -import os + +from openpype.pipeline.publish import RepairContextAction class ValidateLoadedPlugin(pyblish.api.ContextPlugin): @@ -10,7 +11,7 @@ class ValidateLoadedPlugin(pyblish.api.ContextPlugin): label = "Loaded Plugin" order = pyblish.api.ValidatorOrder host = ["maya"] - actions = [openpype.api.RepairContextAction] + actions = [RepairContextAction] @classmethod def get_invalid(cls, context): diff --git a/openpype/hosts/maya/plugins/publish/validate_look_id_reference_edits.py b/openpype/hosts/maya/plugins/publish/validate_look_id_reference_edits.py index 9d074f927b..dc751b6b04 100644 --- a/openpype/hosts/maya/plugins/publish/validate_look_id_reference_edits.py +++ b/openpype/hosts/maya/plugins/publish/validate_look_id_reference_edits.py @@ -4,6 +4,7 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import RepairAction class ValidateLookIdReferenceEdits(pyblish.api.InstancePlugin): @@ -21,7 +22,7 @@ class ValidateLookIdReferenceEdits(pyblish.api.InstancePlugin): hosts = ['maya'] label = 'Look Id Reference Edits' actions = [openpype.hosts.maya.api.action.SelectInvalidAction, - openpype.api.RepairAction] + RepairAction] def process(self, instance): invalid = self.get_invalid(instance) diff --git a/openpype/hosts/maya/plugins/publish/validate_look_shading_group.py b/openpype/hosts/maya/plugins/publish/validate_look_shading_group.py index e8affac036..5e504cde9b 100644 --- a/openpype/hosts/maya/plugins/publish/validate_look_shading_group.py +++ b/openpype/hosts/maya/plugins/publish/validate_look_shading_group.py @@ -3,6 +3,7 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import RepairAction class ValidateShadingEngine(pyblish.api.InstancePlugin): @@ -16,7 +17,7 @@ class ValidateShadingEngine(pyblish.api.InstancePlugin): hosts = ["maya"] label = "Look Shading Engine Naming" actions = [ - openpype.hosts.maya.api.action.SelectInvalidAction, openpype.api.RepairAction + openpype.hosts.maya.api.action.SelectInvalidAction, RepairAction ] # The default connections to check diff --git a/openpype/hosts/maya/plugins/publish/validate_maya_units.py b/openpype/hosts/maya/plugins/publish/validate_maya_units.py index 5f67adec76..791030e932 100644 --- a/openpype/hosts/maya/plugins/publish/validate_maya_units.py +++ b/openpype/hosts/maya/plugins/publish/validate_maya_units.py @@ -5,6 +5,7 @@ import openpype.api import openpype.hosts.maya.api.lib as mayalib from openpype.pipeline.context_tools import get_current_project_asset from math import ceil +from openpype.pipeline.publish import RepairContextAction def float_round(num, places=0, direction=ceil): @@ -17,7 +18,7 @@ class ValidateMayaUnits(pyblish.api.ContextPlugin): order = openpype.api.ValidateSceneOrder label = "Maya Units" hosts = ['maya'] - actions = [openpype.api.RepairContextAction] + actions = [RepairContextAction] validate_linear_units = True linear_units = "cm" diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_arnold_attributes.py b/openpype/hosts/maya/plugins/publish/validate_mesh_arnold_attributes.py index 90eb01aa12..0924a40632 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_arnold_attributes.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_arnold_attributes.py @@ -4,6 +4,7 @@ import pyblish.api import openpype.api import openpype.hosts.maya.api.action from openpype.hosts.maya.api.lib import maintained_selection +from openpype.pipeline.publish import RepairAction class ValidateMeshArnoldAttributes(pyblish.api.InstancePlugin): @@ -20,7 +21,7 @@ class ValidateMeshArnoldAttributes(pyblish.api.InstancePlugin): label = "Mesh Arnold Attributes" actions = [ openpype.hosts.maya.api.action.SelectInvalidAction, - openpype.api.RepairAction + RepairAction ] optional = True if cmds.getAttr( diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_normals_unlocked.py b/openpype/hosts/maya/plugins/publish/validate_mesh_normals_unlocked.py index 750932df54..b11d4a3b63 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_normals_unlocked.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_normals_unlocked.py @@ -4,6 +4,7 @@ import maya.api.OpenMaya as om2 import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import RepairAction class ValidateMeshNormalsUnlocked(pyblish.api.Validator): @@ -21,7 +22,7 @@ class ValidateMeshNormalsUnlocked(pyblish.api.Validator): version = (0, 1, 0) label = 'Mesh Normals Unlocked' actions = [openpype.hosts.maya.api.action.SelectInvalidAction, - openpype.api.RepairAction] + RepairAction] optional = True @staticmethod diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_shader_connections.py b/openpype/hosts/maya/plugins/publish/validate_mesh_shader_connections.py index e0835000f0..90873369c9 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_shader_connections.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_shader_connections.py @@ -3,6 +3,7 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import RepairAction def pairs(iterable): @@ -91,7 +92,7 @@ class ValidateMeshShaderConnections(pyblish.api.InstancePlugin): families = ['model'] label = "Mesh Shader Connections" actions = [openpype.hosts.maya.api.action.SelectInvalidAction, - openpype.api.RepairAction] + RepairAction] def process(self, instance): """Process all the nodes in the instance 'objectSet'""" diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_single_uv_set.py b/openpype/hosts/maya/plugins/publish/validate_mesh_single_uv_set.py index 9d2aeb7d99..70007ccd7b 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_single_uv_set.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_single_uv_set.py @@ -4,6 +4,7 @@ import pyblish.api import openpype.api import openpype.hosts.maya.api.action from openpype.hosts.maya.api import lib +from openpype.pipeline.publish import RepairAction class ValidateMeshSingleUVSet(pyblish.api.InstancePlugin): @@ -23,7 +24,7 @@ class ValidateMeshSingleUVSet(pyblish.api.InstancePlugin): version = (0, 1, 0) label = "Mesh Single UV Set" actions = [openpype.hosts.maya.api.action.SelectInvalidAction, - openpype.api.RepairAction] + RepairAction] @staticmethod def get_invalid(instance): diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_uv_set_map1.py b/openpype/hosts/maya/plugins/publish/validate_mesh_uv_set_map1.py index 52c45d3b0c..cb9180fd09 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_uv_set_map1.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_uv_set_map1.py @@ -3,6 +3,7 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import RepairAction class ValidateMeshUVSetMap1(pyblish.api.InstancePlugin): @@ -21,7 +22,7 @@ class ValidateMeshUVSetMap1(pyblish.api.InstancePlugin): optional = True label = "Mesh has map1 UV Set" actions = [openpype.hosts.maya.api.action.SelectInvalidAction, - openpype.api.RepairAction] + RepairAction] @staticmethod def get_invalid(instance): diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_vertices_have_edges.py b/openpype/hosts/maya/plugins/publish/validate_mesh_vertices_have_edges.py index 463c3c4c50..0c9cc81de7 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_vertices_have_edges.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_vertices_have_edges.py @@ -5,6 +5,7 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import RepairAction def len_flattened(components): @@ -63,7 +64,7 @@ class ValidateMeshVerticesHaveEdges(pyblish.api.InstancePlugin): category = 'geometry' label = 'Mesh Vertices Have Edges' actions = [openpype.hosts.maya.api.action.SelectInvalidAction, - openpype.api.RepairAction] + RepairAction] @classmethod def repair(cls, instance): diff --git a/openpype/hosts/maya/plugins/publish/validate_muster_connection.py b/openpype/hosts/maya/plugins/publish/validate_muster_connection.py index 856f1d933d..c31ccf405c 100644 --- a/openpype/hosts/maya/plugins/publish/validate_muster_connection.py +++ b/openpype/hosts/maya/plugins/publish/validate_muster_connection.py @@ -5,8 +5,10 @@ import appdirs import pyblish.api from openpype.lib import requests_get -from openpype.pipeline.publish import context_plugin_should_run -import openpype.hosts.maya.api.action +from openpype.pipeline.publish import ( + context_plugin_should_run, + RepairAction, +) class ValidateMusterConnection(pyblish.api.ContextPlugin): @@ -21,7 +23,7 @@ class ValidateMusterConnection(pyblish.api.ContextPlugin): token = None if not os.environ.get("MUSTER_REST_URL"): active = False - actions = [openpype.api.RepairAction] + actions = [RepairAction] def process(self, context): diff --git a/openpype/hosts/maya/plugins/publish/validate_no_namespace.py b/openpype/hosts/maya/plugins/publish/validate_no_namespace.py index 5b3d6bc9c4..6b721135b8 100644 --- a/openpype/hosts/maya/plugins/publish/validate_no_namespace.py +++ b/openpype/hosts/maya/plugins/publish/validate_no_namespace.py @@ -3,6 +3,7 @@ import maya.cmds as cmds import pyblish.api import openpype.api +from openpype.pipeline.publish import RepairAction import openpype.hosts.maya.api.action @@ -23,7 +24,7 @@ class ValidateNoNamespace(pyblish.api.InstancePlugin): version = (0, 1, 0) label = 'No Namespaces' actions = [openpype.hosts.maya.api.action.SelectInvalidAction, - openpype.api.RepairAction] + RepairAction] @staticmethod def get_invalid(instance): diff --git a/openpype/hosts/maya/plugins/publish/validate_no_null_transforms.py b/openpype/hosts/maya/plugins/publish/validate_no_null_transforms.py index 36d61b03e8..fea91b666d 100644 --- a/openpype/hosts/maya/plugins/publish/validate_no_null_transforms.py +++ b/openpype/hosts/maya/plugins/publish/validate_no_null_transforms.py @@ -3,6 +3,7 @@ import maya.cmds as cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import RepairAction def has_shape_children(node): @@ -43,7 +44,7 @@ class ValidateNoNullTransforms(pyblish.api.InstancePlugin): category = 'cleanup' version = (0, 1, 0) label = 'No Empty/Null Transforms' - actions = [openpype.api.RepairAction, + actions = [RepairAction, openpype.hosts.maya.api.action.SelectInvalidAction] @staticmethod diff --git a/openpype/hosts/maya/plugins/publish/validate_node_ids_deformed_shapes.py b/openpype/hosts/maya/plugins/publish/validate_node_ids_deformed_shapes.py index 0324be9fc9..363d8882c0 100644 --- a/openpype/hosts/maya/plugins/publish/validate_node_ids_deformed_shapes.py +++ b/openpype/hosts/maya/plugins/publish/validate_node_ids_deformed_shapes.py @@ -4,6 +4,7 @@ import pyblish.api import openpype.api import openpype.hosts.maya.api.action from openpype.hosts.maya.api import lib +from openpype.pipeline.publish import RepairAction class ValidateNodeIdsDeformedShape(pyblish.api.InstancePlugin): @@ -22,7 +23,7 @@ class ValidateNodeIdsDeformedShape(pyblish.api.InstancePlugin): label = 'Deformed shape ids' actions = [ openpype.hosts.maya.api.action.SelectInvalidAction, - openpype.api.RepairAction + RepairAction ] def process(self, instance): diff --git a/openpype/hosts/maya/plugins/publish/validate_render_image_rule.py b/openpype/hosts/maya/plugins/publish/validate_render_image_rule.py index 4d3796e429..965211e046 100644 --- a/openpype/hosts/maya/plugins/publish/validate_render_image_rule.py +++ b/openpype/hosts/maya/plugins/publish/validate_render_image_rule.py @@ -2,6 +2,7 @@ from maya import cmds import pyblish.api import openpype.api +from openpype.pipeline.publish import RepairAction class ValidateRenderImageRule(pyblish.api.InstancePlugin): @@ -17,7 +18,7 @@ class ValidateRenderImageRule(pyblish.api.InstancePlugin): label = "Images File Rule (Workspace)" hosts = ["maya"] families = ["renderlayer"] - actions = [openpype.api.RepairAction] + actions = [RepairAction] def process(self, instance): diff --git a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index f19c0bff36..41cfbc19b5 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -7,6 +7,7 @@ from maya import cmds, mel import pyblish.api import openpype.api +from openpype.pipeline.publish import RepairAction from openpype.hosts.maya.api import lib @@ -43,7 +44,7 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): label = "Render Settings" hosts = ["maya"] families = ["renderlayer"] - actions = [openpype.api.RepairAction] + actions = [RepairAction] ImagePrefixes = { 'mentalray': 'defaultRenderGlobals.imageFilePrefix', diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py b/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py index d5a1fd3529..fc6cda7f23 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py @@ -2,7 +2,7 @@ from maya import cmds import pyblish.api -import openpype.api +from openpype.pipeline.publish import RepairAction import openpype.hosts.maya.api.action from openpype.hosts.maya.api.lib import undo_chunk @@ -29,7 +29,7 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): label = "Rig Controllers" hosts = ["maya"] families = ["rig"] - actions = [openpype.api.RepairAction, + actions = [RepairAction, openpype.hosts.maya.api.action.SelectInvalidAction] # Default controller values diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_controllers_arnold_attributes.py b/openpype/hosts/maya/plugins/publish/validate_rig_controllers_arnold_attributes.py index 1f1db9156b..23237d7ef6 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_controllers_arnold_attributes.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_controllers_arnold_attributes.py @@ -3,6 +3,7 @@ from maya import cmds import pyblish.api import openpype.api +from openpype.pipeline.publish import RepairAction from openpype.hosts.maya.api import lib import openpype.hosts.maya.api.action @@ -30,7 +31,7 @@ class ValidateRigControllersArnoldAttributes(pyblish.api.InstancePlugin): label = "Rig Controllers (Arnold Attributes)" hosts = ["maya"] families = ["rig"] - actions = [openpype.api.RepairAction, + actions = [RepairAction, openpype.hosts.maya.api.action.SelectInvalidAction] attributes = [ diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py b/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py index 5df754fff4..bad9c8b0d4 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py @@ -4,6 +4,7 @@ import pyblish.api import openpype.api import openpype.hosts.maya.api.action from openpype.hosts.maya.api import lib +from openpype.pipeline.publish import RepairAction class ValidateRigJointsHidden(pyblish.api.InstancePlugin): @@ -23,7 +24,7 @@ class ValidateRigJointsHidden(pyblish.api.InstancePlugin): version = (0, 1, 0) label = "Joints Hidden" actions = [openpype.hosts.maya.api.action.SelectInvalidAction, - openpype.api.RepairAction] + RepairAction] @staticmethod def get_invalid(instance): diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py b/openpype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py index cc3723a6e1..f170bc72b1 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py @@ -4,6 +4,7 @@ import pyblish.api import openpype.api import openpype.hosts.maya.api.action from openpype.hosts.maya.api import lib +from openpype.pipeline.publish import RepairAction class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin): @@ -22,7 +23,7 @@ class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin): label = 'Rig Out Set Node Ids' actions = [ openpype.hosts.maya.api.action.SelectInvalidAction, - openpype.api.RepairAction + RepairAction ] allow_history_only = False diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py b/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py index 7c5c540c60..d0ef8d9c04 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py @@ -4,6 +4,7 @@ import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import RepairAction class ValidateRigOutputIds(pyblish.api.InstancePlugin): @@ -17,7 +18,7 @@ class ValidateRigOutputIds(pyblish.api.InstancePlugin): label = "Rig Output Ids" hosts = ["maya"] families = ["rig"] - actions = [openpype.api.RepairAction, + actions = [RepairAction, openpype.hosts.maya.api.action.SelectInvalidAction] def process(self, instance): diff --git a/openpype/hosts/maya/plugins/publish/validate_shape_default_names.py b/openpype/hosts/maya/plugins/publish/validate_shape_default_names.py index e08e06b50e..d6e7ed82c4 100644 --- a/openpype/hosts/maya/plugins/publish/validate_shape_default_names.py +++ b/openpype/hosts/maya/plugins/publish/validate_shape_default_names.py @@ -5,6 +5,7 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import RepairAction def short_name(node): @@ -39,7 +40,7 @@ class ValidateShapeDefaultNames(pyblish.api.InstancePlugin): version = (0, 1, 0) label = "Shape Default Naming" actions = [openpype.hosts.maya.api.action.SelectInvalidAction, - openpype.api.RepairAction] + RepairAction] @staticmethod def _define_default_name(shape): diff --git a/openpype/hosts/maya/plugins/publish/validate_shape_render_stats.py b/openpype/hosts/maya/plugins/publish/validate_shape_render_stats.py index 714451bb98..c332e07505 100644 --- a/openpype/hosts/maya/plugins/publish/validate_shape_render_stats.py +++ b/openpype/hosts/maya/plugins/publish/validate_shape_render_stats.py @@ -4,6 +4,7 @@ import openpype.api from maya import cmds import openpype.hosts.maya.api.action +from openpype.pipeline.publish import RepairAction class ValidateShapeRenderStats(pyblish.api.Validator): @@ -14,7 +15,7 @@ class ValidateShapeRenderStats(pyblish.api.Validator): families = ['model'] label = 'Shape Default Render Stats' actions = [openpype.hosts.maya.api.action.SelectInvalidAction, - openpype.api.RepairAction] + RepairAction] defaults = {'castsShadows': 1, 'receiveShadows': 1, diff --git a/openpype/hosts/maya/plugins/publish/validate_shape_zero.py b/openpype/hosts/maya/plugins/publish/validate_shape_zero.py index 343eaccb7d..515a8056f7 100644 --- a/openpype/hosts/maya/plugins/publish/validate_shape_zero.py +++ b/openpype/hosts/maya/plugins/publish/validate_shape_zero.py @@ -4,6 +4,7 @@ import pyblish.api import openpype.api import openpype.hosts.maya.api.action from openpype.hosts.maya.api import lib +from openpype.pipeline.publish import RepairAction class ValidateShapeZero(pyblish.api.Validator): @@ -19,7 +20,7 @@ class ValidateShapeZero(pyblish.api.Validator): label = "Shape Zero (Freeze)" actions = [ openpype.hosts.maya.api.action.SelectInvalidAction, - openpype.api.RepairAction + RepairAction ] @staticmethod diff --git a/openpype/hosts/maya/plugins/publish/validate_unreal_up_axis.py b/openpype/hosts/maya/plugins/publish/validate_unreal_up_axis.py index 5e1b04889f..3ed22c7cef 100644 --- a/openpype/hosts/maya/plugins/publish/validate_unreal_up_axis.py +++ b/openpype/hosts/maya/plugins/publish/validate_unreal_up_axis.py @@ -3,6 +3,7 @@ from maya import cmds import pyblish.api import openpype.api +from openpype.pipeline.publish import RepairAction class ValidateUnrealUpAxis(pyblish.api.ContextPlugin): @@ -14,7 +15,7 @@ class ValidateUnrealUpAxis(pyblish.api.ContextPlugin): hosts = ["maya"] families = ["staticMesh"] label = "Unreal Up-Axis check" - actions = [openpype.api.RepairAction] + actions = [RepairAction] def process(self, context): assert cmds.upAxis(q=True, axis=True) == "z", ( diff --git a/openpype/hosts/maya/plugins/publish/validate_vray_distributed_rendering.py b/openpype/hosts/maya/plugins/publish/validate_vray_distributed_rendering.py index 5e35565383..376d49d583 100644 --- a/openpype/hosts/maya/plugins/publish/validate_vray_distributed_rendering.py +++ b/openpype/hosts/maya/plugins/publish/validate_vray_distributed_rendering.py @@ -1,6 +1,7 @@ import pyblish.api import openpype.api from openpype.hosts.maya.api import lib +from openpype.pipeline.publish import RepairAction from maya import cmds @@ -18,7 +19,7 @@ class ValidateVRayDistributedRendering(pyblish.api.InstancePlugin): order = openpype.api.ValidateContentsOrder label = "VRay Distributed Rendering" families = ["renderlayer"] - actions = [openpype.api.RepairAction] + actions = [RepairAction] # V-Ray attribute names enabled_attr = "vraySettings.sys_distributed_rendering_on" diff --git a/openpype/hosts/maya/plugins/publish/validate_vray_referenced_aovs.py b/openpype/hosts/maya/plugins/publish/validate_vray_referenced_aovs.py index 7a48c29b7d..39c721e717 100644 --- a/openpype/hosts/maya/plugins/publish/validate_vray_referenced_aovs.py +++ b/openpype/hosts/maya/plugins/publish/validate_vray_referenced_aovs.py @@ -4,7 +4,7 @@ import pyblish.api import types from maya import cmds -import openpype.hosts.maya.api.action +from openpype.pipeline.publish import RepairContextAction class ValidateVrayReferencedAOVs(pyblish.api.InstancePlugin): @@ -20,7 +20,7 @@ class ValidateVrayReferencedAOVs(pyblish.api.InstancePlugin): label = 'VRay Referenced AOVs' hosts = ['maya'] families = ['renderlayer'] - actions = [openpype.api.RepairContextAction] + actions = [RepairContextAction] def process(self, instance): """Plugin main entry point.""" diff --git a/openpype/hosts/maya/plugins/publish/validate_vray_translator_settings.py b/openpype/hosts/maya/plugins/publish/validate_vray_translator_settings.py index d611777f43..9b78699e14 100644 --- a/openpype/hosts/maya/plugins/publish/validate_vray_translator_settings.py +++ b/openpype/hosts/maya/plugins/publish/validate_vray_translator_settings.py @@ -2,7 +2,10 @@ """Validate VRay Translator settings.""" import pyblish.api import openpype.api -from openpype.pipeline.publish import context_plugin_should_run +from openpype.pipeline.publish import ( + context_plugin_should_run, + RepairContextAction, +) from maya import cmds @@ -13,7 +16,7 @@ class ValidateVRayTranslatorEnabled(pyblish.api.ContextPlugin): order = openpype.api.ValidateContentsOrder label = "VRay Translator Settings" families = ["vrayscene_layer"] - actions = [openpype.api.RepairContextAction] + actions = [RepairContextAction] def process(self, context): """Plugin entry point.""" diff --git a/openpype/hosts/maya/plugins/publish/validate_yeti_rig_cache_state.py b/openpype/hosts/maya/plugins/publish/validate_yeti_rig_cache_state.py index 5610733577..4842134b12 100644 --- a/openpype/hosts/maya/plugins/publish/validate_yeti_rig_cache_state.py +++ b/openpype/hosts/maya/plugins/publish/validate_yeti_rig_cache_state.py @@ -1,7 +1,7 @@ import pyblish.api -import openpype.action import maya.cmds as cmds import openpype.hosts.maya.api.action +from openpype.pipeline.publish import RepairAction class ValidateYetiRigCacheState(pyblish.api.InstancePlugin): @@ -17,7 +17,7 @@ class ValidateYetiRigCacheState(pyblish.api.InstancePlugin): label = "Yeti Rig Cache State" hosts = ["maya"] families = ["yetiRig"] - actions = [openpype.action.RepairAction, + actions = [RepairAction, openpype.hosts.maya.api.action.SelectInvalidAction] def process(self, instance): diff --git a/openpype/hosts/nuke/plugins/publish/validate_knobs.py b/openpype/hosts/nuke/plugins/publish/validate_knobs.py index e2b11892e5..d44f27791a 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_knobs.py +++ b/openpype/hosts/nuke/plugins/publish/validate_knobs.py @@ -1,8 +1,11 @@ import nuke import six import pyblish.api -import openpype.api -from openpype.pipeline import PublishXmlValidationError + +from openpype.pipeline.publish import ( + RepairContextAction, + PublishXmlValidationError, +) class ValidateKnobs(pyblish.api.ContextPlugin): @@ -24,7 +27,7 @@ class ValidateKnobs(pyblish.api.ContextPlugin): order = pyblish.api.ValidatorOrder label = "Validate Knobs" hosts = ["nuke"] - actions = [openpype.api.RepairContextAction] + actions = [RepairContextAction] optional = True def process(self, context): diff --git a/openpype/hosts/nuke/plugins/publish/validate_output_resolution.py b/openpype/hosts/nuke/plugins/publish/validate_output_resolution.py index fc07e9b83b..1e59880f90 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_output_resolution.py +++ b/openpype/hosts/nuke/plugins/publish/validate_output_resolution.py @@ -1,8 +1,8 @@ - import pyblish.api -import openpype.api + from openpype.hosts.nuke.api import maintained_selection from openpype.pipeline import PublishXmlValidationError +from openpype.pipeline.publish import RepairAction import nuke @@ -18,7 +18,7 @@ class ValidateOutputResolution(pyblish.api.InstancePlugin): families = ["render", "render.local", "render.farm"] label = "Write Resolution" hosts = ["nuke"] - actions = [openpype.api.RepairAction] + actions = [RepairAction] missing_msg = "Missing Reformat node in render group node" resolution_msg = "Reformat is set to wrong format" diff --git a/openpype/hosts/nuke/plugins/publish/validate_script_attributes.py b/openpype/hosts/nuke/plugins/publish/validate_script_attributes.py index 106d7a2524..f0632f8080 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_script_attributes.py +++ b/openpype/hosts/nuke/plugins/publish/validate_script_attributes.py @@ -1,8 +1,8 @@ from pprint import pformat import pyblish.api -import openpype.api from openpype.pipeline import PublishXmlValidationError +from openpype.pipeline.publish import RepairAction from openpype.hosts.nuke.api.lib import ( get_avalon_knob_data, WorkfileSettings @@ -19,7 +19,7 @@ class ValidateScriptAttributes(pyblish.api.InstancePlugin): label = "Validatte script attributes" hosts = ["nuke"] optional = True - actions = [openpype.api.RepairAction] + actions = [RepairAction] def process(self, instance): root = nuke.root() diff --git a/openpype/hosts/nuke/plugins/publish/validate_write_legacy.py b/openpype/hosts/nuke/plugins/publish/validate_write_legacy.py index 9fb57c1698..699526ef57 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_write_legacy.py +++ b/openpype/hosts/nuke/plugins/publish/validate_write_legacy.py @@ -3,8 +3,9 @@ import toml import nuke import pyblish.api -import openpype.api + from openpype.pipeline import discover_creator_plugins +from openpype.pipeline.publish import RepairAction from openpype.hosts.nuke.api.lib import get_avalon_knob_data @@ -16,7 +17,7 @@ class ValidateWriteLegacy(pyblish.api.InstancePlugin): families = ["write"] label = "Validate Write Legacy" hosts = ["nuke"] - actions = [openpype.api.RepairAction] + actions = [RepairAction] def process(self, instance): node = instance[0] From 9ead11791551a2ed5b7146a8b298e3c8d41fbee3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Aug 2022 12:27:55 +0200 Subject: [PATCH 078/274] added comments and deprecation warnings to classes which stayed where they are --- openpype/action.py | 6 ++++++ openpype/plugin.py | 24 ++++++++++++++++++++---- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/openpype/action.py b/openpype/action.py index 8949858498..70a3934723 100644 --- a/openpype/action.py +++ b/openpype/action.py @@ -62,6 +62,11 @@ def get_errored_plugins_from_data(context): return get_errored_plugins_from_context(context) +# 'RepairAction' and 'RepairContextAction' were moved to +# 'openpype.pipeline.publish' please change you imports. +# There is no "reasonable" way hot mark these classes as deprecated to show +# warning of wrong import. +# Deprecated since 3.14.* will be removed in 3.16.* class RepairAction(pyblish.api.Action): """Repairs the action @@ -88,6 +93,7 @@ class RepairAction(pyblish.api.Action): plugin.repair(instance) +# Deprecated since 3.14.* will be removed in 3.16.* class RepairContextAction(pyblish.api.Action): """Repairs the action diff --git a/openpype/plugin.py b/openpype/plugin.py index 5896cbd749..495cfaf233 100644 --- a/openpype/plugin.py +++ b/openpype/plugin.py @@ -56,14 +56,30 @@ def deprecated(new_destination): if func is None: return _decorator return _decorator(func) + + +# Classes just inheriting from pyblish classes +# - seems to be unused in code (not 100% sure) +# - they should be removed but because it is not clear if they're used +# we'll keep then and log deprecation warning +# Deprecated since 3.14.* will be removed in 3.16.* class ContextPlugin(pyblish.api.ContextPlugin): - def process(cls, *args, **kwargs): - super(ContextPlugin, cls).process(cls, *args, **kwargs) + def __init__(self, *args, **kwargs): + _deprecation_warning( + "openpype.plugin.ContextPlugin", + " Please replace your usage with 'pyblish.api.ContextPlugin'." + ) + super(ContextPlugin, self).__init__(*args, **kwargs) +# Deprecated since 3.14.* will be removed in 3.16.* class InstancePlugin(pyblish.api.InstancePlugin): - def process(cls, *args, **kwargs): - super(InstancePlugin, cls).process(cls, *args, **kwargs) + def __init__(self, *args, **kwargs): + _deprecation_warning( + "openpype.plugin.ContextPlugin", + " Please replace your usage with 'pyblish.api.InstancePlugin'." + ) + super(InstancePlugin, self).__init__(*args, **kwargs) # NOTE: This class is used on so many places I gave up moving it From 694953c1baf0085e8cc3d2f662432a0aa7ff6496 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Aug 2022 12:29:01 +0200 Subject: [PATCH 079/274] added deprecation into functions --- openpype/action.py | 10 ++++++++++ openpype/plugin.py | 2 ++ 2 files changed, 12 insertions(+) diff --git a/openpype/action.py b/openpype/action.py index 70a3934723..de9cdee010 100644 --- a/openpype/action.py +++ b/openpype/action.py @@ -50,6 +50,11 @@ def deprecated(new_destination): @deprecated("openpype.pipeline.publish.get_errored_instances_from_context") def get_errored_instances_from_context(context): + """ + Deprecated: + Since 3.14.* will be removed in 3.16.* or later. + """ + from openpype.pipeline.publish import get_errored_instances_from_context return get_errored_instances_from_context(context) @@ -57,6 +62,11 @@ def get_errored_instances_from_context(context): @deprecated("openpype.pipeline.publish.get_errored_plugins_from_context") def get_errored_plugins_from_data(context): + """ + Deprecated: + Since 3.14.* will be removed in 3.16.* or later. + """ + from openpype.pipeline.publish import get_errored_plugins_from_context return get_errored_plugins_from_context(context) diff --git a/openpype/plugin.py b/openpype/plugin.py index 495cfaf233..72dca31ec9 100644 --- a/openpype/plugin.py +++ b/openpype/plugin.py @@ -117,6 +117,8 @@ def contextplugin_should_run(plugin, context): This actually checks it correctly and returns whether it should run. + Deprecated: + Since 3.14.* will be removed in 3.16.* or later. """ from openpype.pipeline.publish import context_plugin_should_run From d570f882caade2e617c9ec7588f467fd00d2e816 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Aug 2022 12:34:32 +0200 Subject: [PATCH 080/274] added orders to publish constants --- openpype/pipeline/publish/__init__.py | 12 ++++++++++++ openpype/pipeline/publish/constants.py | 7 +++++++ 2 files changed, 19 insertions(+) create mode 100644 openpype/pipeline/publish/constants.py diff --git a/openpype/pipeline/publish/__init__.py b/openpype/pipeline/publish/__init__.py index 9c6462740f..8ba17b2516 100644 --- a/openpype/pipeline/publish/__init__.py +++ b/openpype/pipeline/publish/__init__.py @@ -1,3 +1,10 @@ +from .constants import ( + ValidatePipelineOrder, + ValidateContentsOrder, + ValidateSceneOrder, + ValidateMeshOrder, +) + from .publish_plugins import ( AbstractMetaInstancePlugin, AbstractMetaContextPlugin, @@ -34,6 +41,11 @@ from .abstract_collect_render import ( __all__ = ( + "ValidatePipelineOrder", + "ValidateContentsOrder", + "ValidateSceneOrder", + "ValidateMeshOrder", + "AbstractMetaInstancePlugin", "AbstractMetaContextPlugin", diff --git a/openpype/pipeline/publish/constants.py b/openpype/pipeline/publish/constants.py new file mode 100644 index 0000000000..dcd3445200 --- /dev/null +++ b/openpype/pipeline/publish/constants.py @@ -0,0 +1,7 @@ +import pyblish.api + + +ValidatePipelineOrder = pyblish.api.ValidatorOrder + 0.05 +ValidateContentsOrder = pyblish.api.ValidatorOrder + 0.1 +ValidateSceneOrder = pyblish.api.ValidatorOrder + 0.2 +ValidateMeshOrder = pyblish.api.ValidatorOrder + 0.3 From 10758ec144730ef33c70ea6b2e42a2f43f8a4d5d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Aug 2022 14:51:22 +0200 Subject: [PATCH 081/274] use new source of order variables --- .../publish/validate_instance_asset.py | 8 ++-- .../publish/validate_camera_zero_keyframe.py | 3 +- .../publish/validate_no_colons_in_name.py | 3 +- .../publish/validate_transform_zero.py | 3 +- .../plugins/publish/validate_instances.py | 9 ++-- .../plugins/publish/valiate_vdb_input_node.py | 4 +- .../validate_abc_primitive_to_detail.py | 5 +- .../publish/validate_alembic_face_sets.py | 5 +- .../publish/validate_alembic_input_node.py | 5 +- .../plugins/publish/validate_bypass.py | 4 +- .../plugins/publish/validate_camera_rop.py | 4 +- .../publish/validate_mkpaths_toggled.py | 4 +- .../plugins/publish/validate_no_errors.py | 4 +- .../validate_primitive_hierarchy_paths.py | 4 +- .../validate_usd_shade_model_exists.py | 4 +- .../publish/validate_usd_shade_workspace.py | 4 +- .../publish/validate_vdb_input_node.py | 4 +- .../publish/validate_vdb_output_node.py | 4 +- .../hosts/maya/plugins/create/create_ass0.py | 48 +++++++++++++++++++ .../publish/validate_animation_content.py | 3 +- ...date_animation_out_set_related_node_ids.py | 7 ++- .../publish/validate_ass_relative_paths.py | 9 ++-- .../plugins/publish/validate_attributes.py | 8 ++-- .../publish/validate_camera_attributes.py | 3 +- .../publish/validate_camera_contents.py | 3 +- .../plugins/publish/validate_color_sets.py | 7 ++- .../plugins/publish/validate_cycle_error.py | 3 +- .../plugins/publish/validate_frame_range.py | 8 ++-- .../publish/validate_instance_has_members.py | 3 +- .../publish/validate_instance_in_context.py | 4 +- .../publish/validate_instance_subset.py | 4 +- .../plugins/publish/validate_look_contents.py | 3 +- ...lidate_look_default_shaders_connections.py | 4 +- .../validate_look_id_reference_edits.py | 7 ++- .../publish/validate_look_members_unique.py | 3 +- .../validate_look_no_default_shaders.py | 3 +- .../plugins/publish/validate_look_sets.py | 8 ++-- .../publish/validate_look_shading_group.py | 7 ++- .../publish/validate_look_single_shader.py | 3 +- .../plugins/publish/validate_maya_units.py | 9 ++-- .../validate_mesh_arnold_attributes.py | 7 ++- .../plugins/publish/validate_mesh_has_uv.py | 3 +- .../publish/validate_mesh_lamina_faces.py | 3 +- .../plugins/publish/validate_mesh_ngons.py | 3 +- .../validate_mesh_no_negative_scale.py | 3 +- .../publish/validate_mesh_non_manifold.py | 3 +- .../publish/validate_mesh_non_zero_edge.py | 3 +- .../publish/validate_mesh_normals_unlocked.py | 7 ++- .../publish/validate_mesh_overlapping_uvs.py | 3 +- .../validate_mesh_shader_connections.py | 7 ++- .../publish/validate_mesh_single_uv_set.py | 7 ++- .../publish/validate_mesh_uv_set_map1.py | 7 ++- .../validate_mesh_vertices_have_edges.py | 7 ++- .../plugins/publish/validate_model_content.py | 3 +- .../plugins/publish/validate_model_name.py | 3 +- .../publish/validate_mvlook_contents.py | 5 +- .../plugins/publish/validate_no_animation.py | 3 +- .../publish/validate_no_default_camera.py | 3 +- .../plugins/publish/validate_no_namespace.py | 8 +++- .../publish/validate_no_null_transforms.py | 7 ++- .../publish/validate_no_unknown_nodes.py | 3 +- .../maya/plugins/publish/validate_node_ids.py | 4 +- .../validate_node_ids_deformed_shapes.py | 7 ++- .../publish/validate_node_ids_in_database.py | 3 +- .../publish/validate_node_ids_related.py | 3 +- .../publish/validate_node_ids_unique.py | 3 +- .../publish/validate_node_no_ghosting.py | 3 +- .../publish/validate_render_image_rule.py | 8 ++-- .../validate_render_no_default_cameras.py | 3 +- .../publish/validate_render_single_camera.py | 3 +- .../publish/validate_rendersettings.py | 8 ++-- .../plugins/publish/validate_resources.py | 4 +- .../validate_review_subset_uniqueness.py | 8 ++-- .../plugins/publish/validate_rig_contents.py | 4 +- .../publish/validate_rig_controllers.py | 7 ++- ...idate_rig_controllers_arnold_attributes.py | 7 ++- .../publish/validate_rig_joints_hidden.py | 7 ++- .../publish/validate_rig_out_set_node_ids.py | 7 ++- .../publish/validate_rig_output_ids.py | 7 ++- .../publish/validate_scene_set_workspace.py | 5 +- .../plugins/publish/validate_setdress_root.py | 5 +- .../plugins/publish/validate_shader_name.py | 5 +- .../publish/validate_shape_default_names.py | 7 ++- .../publish/validate_shape_render_stats.py | 7 ++- .../plugins/publish/validate_shape_zero.py | 7 ++- .../publish/validate_single_assembly.py | 4 +- .../validate_skeletalmesh_hierarchy.py | 9 ++-- .../validate_skinCluster_deformer_set.py | 3 +- .../plugins/publish/validate_step_size.py | 3 +- .../validate_transform_naming_suffix.py | 3 +- .../publish/validate_transform_zero.py | 3 +- .../validate_unreal_mesh_triangulated.py | 3 +- .../validate_unreal_staticmesh_naming.py | 5 +- .../publish/validate_unreal_up_axis.py | 9 ++-- .../plugins/publish/validate_visible_only.py | 3 +- .../validate_vray_distributed_rendering.py | 8 ++-- .../validate_vray_translator_settings.py | 4 +- .../validate_yeti_renderscript_callbacks.py | 4 +- .../validate_yeti_rig_input_in_instance.py | 3 +- .../plugins/publish/validate_asset_name.py | 9 ++-- .../publish/validate_instance_asset.py | 4 +- .../plugins/publish/validate_naming.py | 9 ++-- .../publish/validate_unique_subsets.py | 8 ++-- .../publish/validate_editorial_resources.py | 8 ++-- .../plugins/publish/validate_frame_ranges.py | 8 ++-- .../publish/validate_shot_duplicates.py | 9 ++-- .../validate_simple_unreal_texture_naming.py | 11 +++-- .../plugins/publish/validate_sources.py | 8 ++-- .../plugins/publish/validate_texture_batch.py | 8 ++-- .../publish/validate_texture_has_workfile.py | 8 ++-- .../plugins/publish/validate_texture_name.py | 8 ++-- .../publish/validate_texture_versions.py | 8 ++-- .../publish/validate_texture_workfiles.py | 8 ++-- .../plugins/publish/validate_frame_ranges.py | 8 ++-- .../validate_custom_ftrack_attributes.py | 4 +- .../plugins/publish/validate_shotgrid_user.py | 4 +- openpype/plugin.py | 3 ++ .../plugins/publish/validate_resources.py | 7 ++- .../plugins/publish/validate_unique_names.py | 3 +- 119 files changed, 444 insertions(+), 231 deletions(-) create mode 100644 openpype/hosts/maya/plugins/create/create_ass0.py diff --git a/openpype/hosts/aftereffects/plugins/publish/validate_instance_asset.py b/openpype/hosts/aftereffects/plugins/publish/validate_instance_asset.py index 7a9356f020..6c36136b20 100644 --- a/openpype/hosts/aftereffects/plugins/publish/validate_instance_asset.py +++ b/openpype/hosts/aftereffects/plugins/publish/validate_instance_asset.py @@ -1,9 +1,9 @@ import pyblish.api -import openpype.api -from openpype.pipeline import ( +from openpype.pipeline import legacy_io +from openpype.pipeline.publish import ( + ValidateContentsOrder, PublishXmlValidationError, - legacy_io, ) from openpype.hosts.aftereffects.api import get_stub @@ -50,7 +50,7 @@ class ValidateInstanceAsset(pyblish.api.InstancePlugin): label = "Validate Instance Asset" hosts = ["aftereffects"] actions = [ValidateInstanceAssetRepair] - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder def process(self, instance): instance_asset = instance.data["asset"] diff --git a/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py b/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py index 39b9b67511..f17800c6ed 100644 --- a/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py +++ b/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py @@ -4,6 +4,7 @@ import mathutils import pyblish.api import openpype.hosts.blender.api.action +from openpype.pipeline.publish import ValidateContentsOrder class ValidateCameraZeroKeyframe(pyblish.api.InstancePlugin): @@ -14,7 +15,7 @@ class ValidateCameraZeroKeyframe(pyblish.api.InstancePlugin): in Unreal and Blender. """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ["blender"] families = ["camera"] category = "geometry" diff --git a/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py b/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py index 261ff864d5..5be08fd794 100644 --- a/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py +++ b/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py @@ -2,6 +2,7 @@ from typing import List import pyblish.api import openpype.hosts.blender.api.action +from openpype.pipeline.publish import ValidateContentsOrder class ValidateNoColonsInName(pyblish.api.InstancePlugin): @@ -12,7 +13,7 @@ class ValidateNoColonsInName(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ["blender"] families = ["model", "rig"] version = (0, 1, 0) diff --git a/openpype/hosts/blender/plugins/publish/validate_transform_zero.py b/openpype/hosts/blender/plugins/publish/validate_transform_zero.py index 7456dbc423..9bcb405579 100644 --- a/openpype/hosts/blender/plugins/publish/validate_transform_zero.py +++ b/openpype/hosts/blender/plugins/publish/validate_transform_zero.py @@ -4,6 +4,7 @@ import mathutils import pyblish.api import openpype.hosts.blender.api.action +from openpype.pipeline.publish import ValidateContentsOrder class ValidateTransformZero(pyblish.api.InstancePlugin): @@ -15,7 +16,7 @@ class ValidateTransformZero(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ["blender"] families = ["model"] category = "geometry" diff --git a/openpype/hosts/harmony/plugins/publish/validate_instances.py b/openpype/hosts/harmony/plugins/publish/validate_instances.py index 373ef94cc3..ac367082ef 100644 --- a/openpype/hosts/harmony/plugins/publish/validate_instances.py +++ b/openpype/hosts/harmony/plugins/publish/validate_instances.py @@ -1,9 +1,12 @@ import os import pyblish.api -import openpype.api -from openpype.pipeline import PublishXmlValidationError + import openpype.hosts.harmony.api as harmony +from openpype.pipeline.publish import ( + ValidateContentsOrder, + PublishXmlValidationError, +) class ValidateInstanceRepair(pyblish.api.Action): @@ -37,7 +40,7 @@ class ValidateInstance(pyblish.api.InstancePlugin): label = "Validate Instance" hosts = ["harmony"] actions = [ValidateInstanceRepair] - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder def process(self, instance): instance_asset = instance.data["asset"] diff --git a/openpype/hosts/houdini/plugins/publish/valiate_vdb_input_node.py b/openpype/hosts/houdini/plugins/publish/valiate_vdb_input_node.py index 0ae1bc94eb..ac408bc842 100644 --- a/openpype/hosts/houdini/plugins/publish/valiate_vdb_input_node.py +++ b/openpype/hosts/houdini/plugins/publish/valiate_vdb_input_node.py @@ -1,5 +1,5 @@ import pyblish.api -import openpype.api +from openpype.pipeline.publish import ValidateContentsOrder class ValidateVDBInputNode(pyblish.api.InstancePlugin): @@ -16,7 +16,7 @@ class ValidateVDBInputNode(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + 0.1 + order = ValidateContentsOrder + 0.1 families = ["vdbcache"] hosts = ["houdini"] label = "Validate Input Node (VDB)" diff --git a/openpype/hosts/houdini/plugins/publish/validate_abc_primitive_to_detail.py b/openpype/hosts/houdini/plugins/publish/validate_abc_primitive_to_detail.py index 3e17d3e8de..ea800707fb 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_abc_primitive_to_detail.py +++ b/openpype/hosts/houdini/plugins/publish/validate_abc_primitive_to_detail.py @@ -1,8 +1,9 @@ import pyblish.api -import openpype.api from collections import defaultdict +from openpype.pipeline.publish import ValidateContentsOrder + class ValidateAbcPrimitiveToDetail(pyblish.api.InstancePlugin): """Validate Alembic ROP Primitive to Detail attribute is consistent. @@ -15,7 +16,7 @@ class ValidateAbcPrimitiveToDetail(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + 0.1 + order = ValidateContentsOrder + 0.1 families = ["pointcache"] hosts = ["houdini"] label = "Validate Primitive to Detail (Abc)" diff --git a/openpype/hosts/houdini/plugins/publish/validate_alembic_face_sets.py b/openpype/hosts/houdini/plugins/publish/validate_alembic_face_sets.py index e9126ffef0..cbed3ea235 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_alembic_face_sets.py +++ b/openpype/hosts/houdini/plugins/publish/validate_alembic_face_sets.py @@ -1,5 +1,6 @@ import pyblish.api -import openpype.api + +from openpype.pipeline.publish import ValidateContentsOrder class ValidateAlembicROPFaceSets(pyblish.api.InstancePlugin): @@ -17,7 +18,7 @@ class ValidateAlembicROPFaceSets(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + 0.1 + order = ValidateContentsOrder + 0.1 families = ["pointcache"] hosts = ["houdini"] label = "Validate Alembic ROP Face Sets" diff --git a/openpype/hosts/houdini/plugins/publish/validate_alembic_input_node.py b/openpype/hosts/houdini/plugins/publish/validate_alembic_input_node.py index 8d7e3b611f..2625ae5f83 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_alembic_input_node.py +++ b/openpype/hosts/houdini/plugins/publish/validate_alembic_input_node.py @@ -1,5 +1,6 @@ import pyblish.api -import colorbleed.api + +from openpype.pipeline.publish import ValidateContentsOrder class ValidateAlembicInputNode(pyblish.api.InstancePlugin): @@ -11,7 +12,7 @@ class ValidateAlembicInputNode(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder + 0.1 + order = ValidateContentsOrder + 0.1 families = ["pointcache"] hosts = ["houdini"] label = "Validate Input Node (Abc)" diff --git a/openpype/hosts/houdini/plugins/publish/validate_bypass.py b/openpype/hosts/houdini/plugins/publish/validate_bypass.py index fc4e18f701..7cf8da69d6 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_bypass.py +++ b/openpype/hosts/houdini/plugins/publish/validate_bypass.py @@ -1,5 +1,5 @@ import pyblish.api -import openpype.api +from openpype.pipeline.publish import ValidateContentsOrder class ValidateBypassed(pyblish.api.InstancePlugin): @@ -11,7 +11,7 @@ class ValidateBypassed(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder - 0.1 + order = ValidateContentsOrder - 0.1 families = ["*"] hosts = ["houdini"] label = "Validate ROP Bypass" diff --git a/openpype/hosts/houdini/plugins/publish/validate_camera_rop.py b/openpype/hosts/houdini/plugins/publish/validate_camera_rop.py index a0919e1323..d414920f8b 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_camera_rop.py +++ b/openpype/hosts/houdini/plugins/publish/validate_camera_rop.py @@ -1,11 +1,11 @@ import pyblish.api -import openpype.api +from openpype.pipeline.publish import ValidateContentsOrder class ValidateCameraROP(pyblish.api.InstancePlugin): """Validate Camera ROP settings.""" - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder families = ["camera"] hosts = ["houdini"] label = "Camera ROP" diff --git a/openpype/hosts/houdini/plugins/publish/validate_mkpaths_toggled.py b/openpype/hosts/houdini/plugins/publish/validate_mkpaths_toggled.py index cd72877949..be6a798a95 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_mkpaths_toggled.py +++ b/openpype/hosts/houdini/plugins/publish/validate_mkpaths_toggled.py @@ -1,11 +1,11 @@ import pyblish.api -import openpype.api +from openpype.pipeline.publish import ValidateContentsOrder class ValidateIntermediateDirectoriesChecked(pyblish.api.InstancePlugin): """Validate Create Intermediate Directories is enabled on ROP node.""" - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder families = ["pointcache", "camera", "vdbcache"] hosts = ["houdini"] label = "Create Intermediate Directories Checked" diff --git a/openpype/hosts/houdini/plugins/publish/validate_no_errors.py b/openpype/hosts/houdini/plugins/publish/validate_no_errors.py index f58e5f8d7d..76635d4ed5 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_no_errors.py +++ b/openpype/hosts/houdini/plugins/publish/validate_no_errors.py @@ -1,6 +1,6 @@ import pyblish.api -import openpype.api import hou +from openpype.pipeline.publish import ValidateContentsOrder def cook_in_range(node, start, end): @@ -28,7 +28,7 @@ def get_errors(node): class ValidateNoErrors(pyblish.api.InstancePlugin): """Validate the Instance has no current cooking errors.""" - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ["houdini"] label = "Validate no errors" diff --git a/openpype/hosts/houdini/plugins/publish/validate_primitive_hierarchy_paths.py b/openpype/hosts/houdini/plugins/publish/validate_primitive_hierarchy_paths.py index 1eb36763bb..7a8cd04f15 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_primitive_hierarchy_paths.py +++ b/openpype/hosts/houdini/plugins/publish/validate_primitive_hierarchy_paths.py @@ -1,5 +1,5 @@ import pyblish.api -import openpype.api +from openpype.pipeline.publish import ValidateContentsOrder class ValidatePrimitiveHierarchyPaths(pyblish.api.InstancePlugin): @@ -11,7 +11,7 @@ class ValidatePrimitiveHierarchyPaths(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + 0.1 + order = ValidateContentsOrder + 0.1 families = ["pointcache"] hosts = ["houdini"] label = "Validate Prims Hierarchy Path" diff --git a/openpype/hosts/houdini/plugins/publish/validate_usd_shade_model_exists.py b/openpype/hosts/houdini/plugins/publish/validate_usd_shade_model_exists.py index b979b87d84..f08c7c72c5 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_usd_shade_model_exists.py +++ b/openpype/hosts/houdini/plugins/publish/validate_usd_shade_model_exists.py @@ -3,14 +3,14 @@ import re import pyblish.api from openpype.client import get_subset_by_name -import openpype.api from openpype.pipeline import legacy_io +from openpype.pipeline.publish import ValidateContentsOrder class ValidateUSDShadeModelExists(pyblish.api.InstancePlugin): """Validate the Instance has no current cooking errors.""" - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ["houdini"] families = ["usdShade"] label = "USD Shade model exists" diff --git a/openpype/hosts/houdini/plugins/publish/validate_usd_shade_workspace.py b/openpype/hosts/houdini/plugins/publish/validate_usd_shade_workspace.py index a77ca2f3cb..a4902b48a9 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_usd_shade_workspace.py +++ b/openpype/hosts/houdini/plugins/publish/validate_usd_shade_workspace.py @@ -1,5 +1,5 @@ import pyblish.api -import openpype.api +from openpype.pipeline.publish import ValidateContentsOrder import hou @@ -12,7 +12,7 @@ class ValidateUsdShadeWorkspace(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ["houdini"] families = ["usdShade"] label = "USD Shade Workspace" diff --git a/openpype/hosts/houdini/plugins/publish/validate_vdb_input_node.py b/openpype/hosts/houdini/plugins/publish/validate_vdb_input_node.py index 0ae1bc94eb..ac408bc842 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_vdb_input_node.py +++ b/openpype/hosts/houdini/plugins/publish/validate_vdb_input_node.py @@ -1,5 +1,5 @@ import pyblish.api -import openpype.api +from openpype.pipeline.publish import ValidateContentsOrder class ValidateVDBInputNode(pyblish.api.InstancePlugin): @@ -16,7 +16,7 @@ class ValidateVDBInputNode(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + 0.1 + order = ValidateContentsOrder + 0.1 families = ["vdbcache"] hosts = ["houdini"] label = "Validate Input Node (VDB)" diff --git a/openpype/hosts/houdini/plugins/publish/validate_vdb_output_node.py b/openpype/hosts/houdini/plugins/publish/validate_vdb_output_node.py index 1ba840b71d..55ed581d4c 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_vdb_output_node.py +++ b/openpype/hosts/houdini/plugins/publish/validate_vdb_output_node.py @@ -1,6 +1,6 @@ import pyblish.api -import openpype.api import hou +from openpype.pipeline.publish import ValidateContentsOrder class ValidateVDBOutputNode(pyblish.api.InstancePlugin): @@ -17,7 +17,7 @@ class ValidateVDBOutputNode(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + 0.1 + order = ValidateContentsOrder + 0.1 families = ["vdbcache"] hosts = ["houdini"] label = "Validate Output Node (VDB)" diff --git a/openpype/hosts/maya/plugins/create/create_ass0.py b/openpype/hosts/maya/plugins/create/create_ass0.py new file mode 100644 index 0000000000..4523aa02c5 --- /dev/null +++ b/openpype/hosts/maya/plugins/create/create_ass0.py @@ -0,0 +1,48 @@ +from collections import OrderedDicta + +from openpype.hosts.maya.api import ( + lib, + plugin +) + +from maya import cmds + + +class CreateAss(plugin.Creator): + """Arnold Archive""" + + name = "ass" + label = "Ass StandIn" + family = "ass" + icon = "cube" + + def __init__(self, *args, **kwargs): + super(CreateAss, self).__init__(*args, **kwargs) + + # Add animation data + self.data.update(lib.collect_animation_data()) + + # Vertex colors with the geometry + self.data["exportSequence"] = False + + def process(self): + instance = super(CreateAss, self).process() + + # data = OrderedDict(**self.data) + + + + nodes = list() + + if (self.options or {}).get("useSelection"): + nodes = cmds.ls(selection=True) + + cmds.sets(nodes, rm=instance) + + assContent = cmds.sets(name="content_SET") + assProxy = cmds.sets(name="proxy_SET", empty=True) + cmds.sets([assContent, assProxy], forceElement=instance) + + # self.log.info(data) + # + # self.data = data diff --git a/openpype/hosts/maya/plugins/publish/validate_animation_content.py b/openpype/hosts/maya/plugins/publish/validate_animation_content.py index 7638c44b87..6f7a6b905a 100644 --- a/openpype/hosts/maya/plugins/publish/validate_animation_content.py +++ b/openpype/hosts/maya/plugins/publish/validate_animation_content.py @@ -1,6 +1,7 @@ import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import ValidateContentsOrder class ValidateAnimationContent(pyblish.api.InstancePlugin): @@ -11,7 +12,7 @@ class ValidateAnimationContent(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ["maya"] families = ["animation"] label = "Animation Content" diff --git a/openpype/hosts/maya/plugins/publish/validate_animation_out_set_related_node_ids.py b/openpype/hosts/maya/plugins/publish/validate_animation_out_set_related_node_ids.py index 5f4d92bed9..aa27633402 100644 --- a/openpype/hosts/maya/plugins/publish/validate_animation_out_set_related_node_ids.py +++ b/openpype/hosts/maya/plugins/publish/validate_animation_out_set_related_node_ids.py @@ -4,7 +4,10 @@ import pyblish.api import openpype.api import openpype.hosts.maya.api.action from openpype.hosts.maya.api import lib -from openpype.pipeline.publish import RepairAction +from openpype.pipeline.publish import ( + RepairAction, + ValidateContentsOrder, +) class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin): @@ -17,7 +20,7 @@ class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder families = ['animation', "pointcache"] hosts = ['maya'] label = 'Animation Out Set Related Node Ids' diff --git a/openpype/hosts/maya/plugins/publish/validate_ass_relative_paths.py b/openpype/hosts/maya/plugins/publish/validate_ass_relative_paths.py index 3de8e55a41..ac6ce4d22d 100644 --- a/openpype/hosts/maya/plugins/publish/validate_ass_relative_paths.py +++ b/openpype/hosts/maya/plugins/publish/validate_ass_relative_paths.py @@ -4,15 +4,16 @@ import types import maya.cmds as cmds import pyblish.api -import openpype.api -import openpype.hosts.maya.api.action -from openpype.pipeline.publish import RepairAction +from openpype.pipeline.publish import ( + RepairAction, + ValidateContentsOrder, +) class ValidateAssRelativePaths(pyblish.api.InstancePlugin): """Ensure exporting ass file has set relative texture paths""" - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ['maya'] families = ['ass'] label = "ASS has relative texture paths" diff --git a/openpype/hosts/maya/plugins/publish/validate_attributes.py b/openpype/hosts/maya/plugins/publish/validate_attributes.py index e5b72ac3a3..136c38bc1d 100644 --- a/openpype/hosts/maya/plugins/publish/validate_attributes.py +++ b/openpype/hosts/maya/plugins/publish/validate_attributes.py @@ -1,8 +1,10 @@ import pymel.core as pm import pyblish.api -import openpype.api -from openpype.pipeline.publish import RepairContextAction +from openpype.pipeline.publish import ( + RepairContextAction, + ValidateContentsOrder, +) class ValidateAttributes(pyblish.api.ContextPlugin): @@ -17,7 +19,7 @@ class ValidateAttributes(pyblish.api.ContextPlugin): } """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder label = "Attributes" hosts = ["maya"] actions = [RepairContextAction] diff --git a/openpype/hosts/maya/plugins/publish/validate_camera_attributes.py b/openpype/hosts/maya/plugins/publish/validate_camera_attributes.py index e019788aff..19c1179e52 100644 --- a/openpype/hosts/maya/plugins/publish/validate_camera_attributes.py +++ b/openpype/hosts/maya/plugins/publish/validate_camera_attributes.py @@ -3,6 +3,7 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import ValidateContentsOrder class ValidateCameraAttributes(pyblish.api.InstancePlugin): @@ -14,7 +15,7 @@ class ValidateCameraAttributes(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder families = ['camera'] hosts = ['maya'] label = 'Camera Attributes' diff --git a/openpype/hosts/maya/plugins/publish/validate_camera_contents.py b/openpype/hosts/maya/plugins/publish/validate_camera_contents.py index 5f6faddbe7..f846319807 100644 --- a/openpype/hosts/maya/plugins/publish/validate_camera_contents.py +++ b/openpype/hosts/maya/plugins/publish/validate_camera_contents.py @@ -3,6 +3,7 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import ValidateContentsOrder class ValidateCameraContents(pyblish.api.InstancePlugin): @@ -15,7 +16,7 @@ class ValidateCameraContents(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder families = ['camera'] hosts = ['maya'] label = 'Camera Contents' diff --git a/openpype/hosts/maya/plugins/publish/validate_color_sets.py b/openpype/hosts/maya/plugins/publish/validate_color_sets.py index ecc5c78e43..cab9d6ebab 100644 --- a/openpype/hosts/maya/plugins/publish/validate_color_sets.py +++ b/openpype/hosts/maya/plugins/publish/validate_color_sets.py @@ -3,7 +3,10 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action -from openpype.pipeline.publish import RepairAction +from openpype.pipeline.publish import ( + RepairAction, + ValidateMeshOrder, +) class ValidateColorSets(pyblish.api.Validator): @@ -14,7 +17,7 @@ class ValidateColorSets(pyblish.api.Validator): """ - order = openpype.api.ValidateMeshOrder + order = ValidateMeshOrder hosts = ['maya'] families = ['model'] category = 'geometry' diff --git a/openpype/hosts/maya/plugins/publish/validate_cycle_error.py b/openpype/hosts/maya/plugins/publish/validate_cycle_error.py index 4dfe0b8add..d3b8316d94 100644 --- a/openpype/hosts/maya/plugins/publish/validate_cycle_error.py +++ b/openpype/hosts/maya/plugins/publish/validate_cycle_error.py @@ -5,12 +5,13 @@ import pyblish.api import openpype.api import openpype.hosts.maya.api.action from openpype.hosts.maya.api.lib import maintained_selection +from openpype.pipeline.publish import ValidateContentsOrder class ValidateCycleError(pyblish.api.InstancePlugin): """Validate nodes produce no cycle errors.""" - order = openpype.api.ValidateContentsOrder + 0.05 + order = ValidateContentsOrder + 0.05 label = "Cycle Errors" hosts = ["maya"] families = ["rig"] diff --git a/openpype/hosts/maya/plugins/publish/validate_frame_range.py b/openpype/hosts/maya/plugins/publish/validate_frame_range.py index 9094176abb..b467a7c232 100644 --- a/openpype/hosts/maya/plugins/publish/validate_frame_range.py +++ b/openpype/hosts/maya/plugins/publish/validate_frame_range.py @@ -1,8 +1,10 @@ import pyblish.api -import openpype.api from maya import cmds -from openpype.pipeline.publish import RepairAction +from openpype.pipeline.publish import ( + RepairAction, + ValidateContentsOrder, +) class ValidateFrameRange(pyblish.api.InstancePlugin): @@ -19,7 +21,7 @@ class ValidateFrameRange(pyblish.api.InstancePlugin): """ label = "Validate Frame Range" - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder families = ["animation", "pointcache", "camera", diff --git a/openpype/hosts/maya/plugins/publish/validate_instance_has_members.py b/openpype/hosts/maya/plugins/publish/validate_instance_has_members.py index e04a26e4fd..bf92ac5099 100644 --- a/openpype/hosts/maya/plugins/publish/validate_instance_has_members.py +++ b/openpype/hosts/maya/plugins/publish/validate_instance_has_members.py @@ -1,12 +1,13 @@ import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import ValidateContentsOrder class ValidateInstanceHasMembers(pyblish.api.InstancePlugin): """Validates instance objectSet has *any* members.""" - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ["maya"] label = 'Instance has members' actions = [openpype.hosts.maya.api.action.SelectInvalidAction] diff --git a/openpype/hosts/maya/plugins/publish/validate_instance_in_context.py b/openpype/hosts/maya/plugins/publish/validate_instance_in_context.py index 7b8c335062..41bb414829 100644 --- a/openpype/hosts/maya/plugins/publish/validate_instance_in_context.py +++ b/openpype/hosts/maya/plugins/publish/validate_instance_in_context.py @@ -3,7 +3,7 @@ from __future__ import absolute_import import pyblish.api -import openpype.api +from openpype.pipeline.publish import ValidateContentsOrder from maya import cmds @@ -98,7 +98,7 @@ class ValidateInstanceInContext(pyblish.api.InstancePlugin): Action on this validator will select invalid instances in Outliner. """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder label = "Instance in same Context" optional = True hosts = ["maya"] diff --git a/openpype/hosts/maya/plugins/publish/validate_instance_subset.py b/openpype/hosts/maya/plugins/publish/validate_instance_subset.py index 539f3f9d3c..bb3dde761c 100644 --- a/openpype/hosts/maya/plugins/publish/validate_instance_subset.py +++ b/openpype/hosts/maya/plugins/publish/validate_instance_subset.py @@ -1,8 +1,8 @@ import pyblish.api -import openpype.api import string import six +from openpype.pipeline.publish import ValidateContentsOrder # Allow only characters, numbers and underscore allowed = set(string.ascii_lowercase + @@ -18,7 +18,7 @@ def validate_name(subset): class ValidateSubsetName(pyblish.api.InstancePlugin): """Validates subset name has only valid characters""" - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder families = ["*"] label = "Subset Name" diff --git a/openpype/hosts/maya/plugins/publish/validate_look_contents.py b/openpype/hosts/maya/plugins/publish/validate_look_contents.py index b1e1d5416b..d9819b05d5 100644 --- a/openpype/hosts/maya/plugins/publish/validate_look_contents.py +++ b/openpype/hosts/maya/plugins/publish/validate_look_contents.py @@ -1,6 +1,7 @@ import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import ValidateContentsOrder class ValidateLookContents(pyblish.api.InstancePlugin): @@ -17,7 +18,7 @@ class ValidateLookContents(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder families = ['look'] hosts = ['maya'] label = 'Look Data Contents' diff --git a/openpype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py b/openpype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py index 262dd10b74..20f561a892 100644 --- a/openpype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py +++ b/openpype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py @@ -1,7 +1,7 @@ from maya import cmds import pyblish.api -import openpype.api +from openpype.pipeline.publish import ValidateContentsOrder class ValidateLookDefaultShadersConnections(pyblish.api.InstancePlugin): @@ -16,7 +16,7 @@ class ValidateLookDefaultShadersConnections(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder families = ['look'] hosts = ['maya'] label = 'Look Default Shader Connections' diff --git a/openpype/hosts/maya/plugins/publish/validate_look_id_reference_edits.py b/openpype/hosts/maya/plugins/publish/validate_look_id_reference_edits.py index dc751b6b04..f223c1a42b 100644 --- a/openpype/hosts/maya/plugins/publish/validate_look_id_reference_edits.py +++ b/openpype/hosts/maya/plugins/publish/validate_look_id_reference_edits.py @@ -4,7 +4,10 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action -from openpype.pipeline.publish import RepairAction +from openpype.pipeline.publish import ( + RepairAction, + ValidateContentsOrder, +) class ValidateLookIdReferenceEdits(pyblish.api.InstancePlugin): @@ -17,7 +20,7 @@ class ValidateLookIdReferenceEdits(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder families = ['look'] hosts = ['maya'] label = 'Look Id Reference Edits' diff --git a/openpype/hosts/maya/plugins/publish/validate_look_members_unique.py b/openpype/hosts/maya/plugins/publish/validate_look_members_unique.py index 2367602d05..210fcb174d 100644 --- a/openpype/hosts/maya/plugins/publish/validate_look_members_unique.py +++ b/openpype/hosts/maya/plugins/publish/validate_look_members_unique.py @@ -3,6 +3,7 @@ from collections import defaultdict import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import ValidatePipelineOrder class ValidateUniqueRelationshipMembers(pyblish.api.InstancePlugin): @@ -20,7 +21,7 @@ class ValidateUniqueRelationshipMembers(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidatePipelineOrder + order = ValidatePipelineOrder label = 'Look members unique' hosts = ['maya'] families = ['look'] diff --git a/openpype/hosts/maya/plugins/publish/validate_look_no_default_shaders.py b/openpype/hosts/maya/plugins/publish/validate_look_no_default_shaders.py index 8ba6cde988..95f8fa20d0 100644 --- a/openpype/hosts/maya/plugins/publish/validate_look_no_default_shaders.py +++ b/openpype/hosts/maya/plugins/publish/validate_look_no_default_shaders.py @@ -3,6 +3,7 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import ValidateContentsOrder class ValidateLookNoDefaultShaders(pyblish.api.InstancePlugin): @@ -23,7 +24,7 @@ class ValidateLookNoDefaultShaders(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + 0.01 + order = ValidateContentsOrder + 0.01 families = ['look'] hosts = ['maya'] label = 'Look No Default Shaders' diff --git a/openpype/hosts/maya/plugins/publish/validate_look_sets.py b/openpype/hosts/maya/plugins/publish/validate_look_sets.py index 5e737ca876..3a60b771f4 100644 --- a/openpype/hosts/maya/plugins/publish/validate_look_sets.py +++ b/openpype/hosts/maya/plugins/publish/validate_look_sets.py @@ -1,8 +1,8 @@ -import openpype.hosts.maya.api.action -from openpype.hosts.maya.api import lib - import pyblish.api import openpype.api +import openpype.hosts.maya.api.action +from openpype.hosts.maya.api import lib +from openpype.pipeline.publish import ValidateContentsOrder class ValidateLookSets(pyblish.api.InstancePlugin): @@ -38,7 +38,7 @@ class ValidateLookSets(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder families = ['look'] hosts = ['maya'] label = 'Look Sets' diff --git a/openpype/hosts/maya/plugins/publish/validate_look_shading_group.py b/openpype/hosts/maya/plugins/publish/validate_look_shading_group.py index 5e504cde9b..7d043eddb8 100644 --- a/openpype/hosts/maya/plugins/publish/validate_look_shading_group.py +++ b/openpype/hosts/maya/plugins/publish/validate_look_shading_group.py @@ -3,7 +3,10 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action -from openpype.pipeline.publish import RepairAction +from openpype.pipeline.publish import ( + RepairAction, + ValidateContentsOrder, +) class ValidateShadingEngine(pyblish.api.InstancePlugin): @@ -12,7 +15,7 @@ class ValidateShadingEngine(pyblish.api.InstancePlugin): Shading engines should be named "{surface_shader}SG" """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder families = ["look"] hosts = ["maya"] label = "Look Shading Engine Naming" diff --git a/openpype/hosts/maya/plugins/publish/validate_look_single_shader.py b/openpype/hosts/maya/plugins/publish/validate_look_single_shader.py index 2b32ccf492..51e1232bb7 100644 --- a/openpype/hosts/maya/plugins/publish/validate_look_single_shader.py +++ b/openpype/hosts/maya/plugins/publish/validate_look_single_shader.py @@ -3,6 +3,7 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import ValidateContentsOrder class ValidateSingleShader(pyblish.api.InstancePlugin): @@ -12,7 +13,7 @@ class ValidateSingleShader(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder families = ['look'] hosts = ['maya'] label = 'Look Single Shader Per Shape' diff --git a/openpype/hosts/maya/plugins/publish/validate_maya_units.py b/openpype/hosts/maya/plugins/publish/validate_maya_units.py index 791030e932..5698d795ff 100644 --- a/openpype/hosts/maya/plugins/publish/validate_maya_units.py +++ b/openpype/hosts/maya/plugins/publish/validate_maya_units.py @@ -1,11 +1,14 @@ import maya.cmds as cmds import pyblish.api -import openpype.api + import openpype.hosts.maya.api.lib as mayalib from openpype.pipeline.context_tools import get_current_project_asset from math import ceil -from openpype.pipeline.publish import RepairContextAction +from openpype.pipeline.publish import ( + RepairContextAction, + ValidateSceneOrder, +) def float_round(num, places=0, direction=ceil): @@ -15,7 +18,7 @@ def float_round(num, places=0, direction=ceil): class ValidateMayaUnits(pyblish.api.ContextPlugin): """Check if the Maya units are set correct""" - order = openpype.api.ValidateSceneOrder + order = ValidateSceneOrder label = "Maya Units" hosts = ['maya'] actions = [RepairContextAction] diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_arnold_attributes.py b/openpype/hosts/maya/plugins/publish/validate_mesh_arnold_attributes.py index 0924a40632..abfe1213a0 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_arnold_attributes.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_arnold_attributes.py @@ -4,7 +4,10 @@ import pyblish.api import openpype.api import openpype.hosts.maya.api.action from openpype.hosts.maya.api.lib import maintained_selection -from openpype.pipeline.publish import RepairAction +from openpype.pipeline.publish import ( + RepairAction, + ValidateMeshOrder, +) class ValidateMeshArnoldAttributes(pyblish.api.InstancePlugin): @@ -14,7 +17,7 @@ class ValidateMeshArnoldAttributes(pyblish.api.InstancePlugin): later published looks can discover non-default Arnold attributes. """ - order = openpype.api.ValidateMeshOrder + order = ValidateMeshOrder hosts = ["maya"] families = ["model"] category = "geometry" diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_has_uv.py b/openpype/hosts/maya/plugins/publish/validate_mesh_has_uv.py index 8f9b5d1c4e..4d2885d6e2 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_has_uv.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_has_uv.py @@ -5,6 +5,7 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import ValidateMeshOrder def len_flattened(components): @@ -45,7 +46,7 @@ class ValidateMeshHasUVs(pyblish.api.InstancePlugin): UVs for every face. """ - order = openpype.api.ValidateMeshOrder + order = ValidateMeshOrder hosts = ['maya'] families = ['model'] category = 'geometry' diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_lamina_faces.py b/openpype/hosts/maya/plugins/publish/validate_mesh_lamina_faces.py index 8fa1f3cf3b..e7a73c21b0 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_lamina_faces.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_lamina_faces.py @@ -3,6 +3,7 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import ValidateMeshOrder class ValidateMeshLaminaFaces(pyblish.api.InstancePlugin): @@ -12,7 +13,7 @@ class ValidateMeshLaminaFaces(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateMeshOrder + order = ValidateMeshOrder hosts = ['maya'] families = ['model'] category = 'geometry' diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_ngons.py b/openpype/hosts/maya/plugins/publish/validate_mesh_ngons.py index ab0beb2a9c..24d6188ec8 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_ngons.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_ngons.py @@ -4,6 +4,7 @@ import pyblish.api import openpype.api import openpype.hosts.maya.api.action from openpype.hosts.maya.api import lib +from openpype.pipeline.publish import ValidateContentsOrder class ValidateMeshNgons(pyblish.api.Validator): @@ -16,7 +17,7 @@ class ValidateMeshNgons(pyblish.api.Validator): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ["maya"] families = ["model"] label = "Mesh ngons" diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_no_negative_scale.py b/openpype/hosts/maya/plugins/publish/validate_mesh_no_negative_scale.py index 5ccfa7377a..18ceccaa28 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_no_negative_scale.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_no_negative_scale.py @@ -3,6 +3,7 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import ValidateMeshOrder class ValidateMeshNoNegativeScale(pyblish.api.Validator): @@ -17,7 +18,7 @@ class ValidateMeshNoNegativeScale(pyblish.api.Validator): """ - order = openpype.api.ValidateMeshOrder + order = ValidateMeshOrder hosts = ['maya'] families = ['model'] label = 'Mesh No Negative Scale' diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_non_manifold.py b/openpype/hosts/maya/plugins/publish/validate_mesh_non_manifold.py index 9bd584bbbf..e75a132d50 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_non_manifold.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_non_manifold.py @@ -3,6 +3,7 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import ValidateMeshOrder class ValidateMeshNonManifold(pyblish.api.Validator): @@ -13,7 +14,7 @@ class ValidateMeshNonManifold(pyblish.api.Validator): """ - order = openpype.api.ValidateMeshOrder + order = ValidateMeshOrder hosts = ['maya'] families = ['model'] label = 'Mesh Non-Manifold Vertices/Edges' diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_non_zero_edge.py b/openpype/hosts/maya/plugins/publish/validate_mesh_non_zero_edge.py index 5e6f24cf79..8c03b54971 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_non_zero_edge.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_non_zero_edge.py @@ -4,6 +4,7 @@ import pyblish.api import openpype.api import openpype.hosts.maya.api.action from openpype.hosts.maya.api import lib +from openpype.pipeline.publish import ValidateMeshOrder class ValidateMeshNonZeroEdgeLength(pyblish.api.InstancePlugin): @@ -16,7 +17,7 @@ class ValidateMeshNonZeroEdgeLength(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateMeshOrder + order = ValidateMeshOrder families = ['model'] hosts = ['maya'] category = 'geometry' diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_normals_unlocked.py b/openpype/hosts/maya/plugins/publish/validate_mesh_normals_unlocked.py index b11d4a3b63..7d88161058 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_normals_unlocked.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_normals_unlocked.py @@ -4,7 +4,10 @@ import maya.api.OpenMaya as om2 import pyblish.api import openpype.api import openpype.hosts.maya.api.action -from openpype.pipeline.publish import RepairAction +from openpype.pipeline.publish import ( + RepairAction, + ValidateMeshOrder, +) class ValidateMeshNormalsUnlocked(pyblish.api.Validator): @@ -15,7 +18,7 @@ class ValidateMeshNormalsUnlocked(pyblish.api.Validator): """ - order = openpype.api.ValidateMeshOrder + order = ValidateMeshOrder hosts = ['maya'] families = ['model'] category = 'geometry' diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_overlapping_uvs.py b/openpype/hosts/maya/plugins/publish/validate_mesh_overlapping_uvs.py index bf95d8ba09..dde3e4fead 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_overlapping_uvs.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_overlapping_uvs.py @@ -6,6 +6,7 @@ import maya.api.OpenMaya as om import pymel.core as pm from six.moves import xrange +from openpype.pipeline.publish import ValidateMeshOrder class GetOverlappingUVs(object): @@ -232,7 +233,7 @@ class ValidateMeshHasOverlappingUVs(pyblish.api.InstancePlugin): It is optional to warn publisher about it. """ - order = openpype.api.ValidateMeshOrder + order = ValidateMeshOrder hosts = ['maya'] families = ['model'] category = 'geometry' diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_shader_connections.py b/openpype/hosts/maya/plugins/publish/validate_mesh_shader_connections.py index 90873369c9..9621fd5aa8 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_shader_connections.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_shader_connections.py @@ -3,7 +3,10 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action -from openpype.pipeline.publish import RepairAction +from openpype.pipeline.publish import ( + RepairAction, + ValidateMeshOrder, +) def pairs(iterable): @@ -87,7 +90,7 @@ class ValidateMeshShaderConnections(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateMeshOrder + order = ValidateMeshOrder hosts = ['maya'] families = ['model'] label = "Mesh Shader Connections" diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_single_uv_set.py b/openpype/hosts/maya/plugins/publish/validate_mesh_single_uv_set.py index 70007ccd7b..3fb09356d3 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_single_uv_set.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_single_uv_set.py @@ -4,7 +4,10 @@ import pyblish.api import openpype.api import openpype.hosts.maya.api.action from openpype.hosts.maya.api import lib -from openpype.pipeline.publish import RepairAction +from openpype.pipeline.publish import ( + RepairAction, + ValidateMeshOrder, +) class ValidateMeshSingleUVSet(pyblish.api.InstancePlugin): @@ -16,7 +19,7 @@ class ValidateMeshSingleUVSet(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateMeshOrder + order = ValidateMeshOrder hosts = ['maya'] families = ['model', 'pointcache'] category = 'uv' diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_uv_set_map1.py b/openpype/hosts/maya/plugins/publish/validate_mesh_uv_set_map1.py index cb9180fd09..2711682f76 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_uv_set_map1.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_uv_set_map1.py @@ -3,7 +3,10 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action -from openpype.pipeline.publish import RepairAction +from openpype.pipeline.publish import ( + RepairAction, + ValidateMeshOrder, +) class ValidateMeshUVSetMap1(pyblish.api.InstancePlugin): @@ -16,7 +19,7 @@ class ValidateMeshUVSetMap1(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateMeshOrder + order = ValidateMeshOrder hosts = ['maya'] families = ['model'] optional = True diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_vertices_have_edges.py b/openpype/hosts/maya/plugins/publish/validate_mesh_vertices_have_edges.py index 0c9cc81de7..350a5f4789 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_vertices_have_edges.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_vertices_have_edges.py @@ -5,7 +5,10 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action -from openpype.pipeline.publish import RepairAction +from openpype.pipeline.publish import ( + RepairAction, + ValidateMeshOrder, +) def len_flattened(components): @@ -58,7 +61,7 @@ class ValidateMeshVerticesHaveEdges(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateMeshOrder + order = ValidateMeshOrder hosts = ['maya'] families = ['model'] category = 'geometry' diff --git a/openpype/hosts/maya/plugins/publish/validate_model_content.py b/openpype/hosts/maya/plugins/publish/validate_model_content.py index aee0ea52f0..0557858639 100644 --- a/openpype/hosts/maya/plugins/publish/validate_model_content.py +++ b/openpype/hosts/maya/plugins/publish/validate_model_content.py @@ -4,6 +4,7 @@ import pyblish.api import openpype.api import openpype.hosts.maya.api.action from openpype.hosts.maya.api import lib +from openpype.pipeline.publish import ValidateContentsOrder class ValidateModelContent(pyblish.api.InstancePlugin): @@ -14,7 +15,7 @@ class ValidateModelContent(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ["maya"] families = ["model"] label = "Model Content" diff --git a/openpype/hosts/maya/plugins/publish/validate_model_name.py b/openpype/hosts/maya/plugins/publish/validate_model_name.py index 02107d5732..99a4b2654e 100644 --- a/openpype/hosts/maya/plugins/publish/validate_model_name.py +++ b/openpype/hosts/maya/plugins/publish/validate_model_name.py @@ -7,6 +7,7 @@ import pyblish.api import openpype.api from openpype.pipeline import legacy_io +from openpype.pipeline.publish import ValidateContentsOrder import openpype.hosts.maya.api.action from openpype.hosts.maya.api.shader_definition_editor import ( DEFINITION_FILENAME) @@ -23,7 +24,7 @@ class ValidateModelName(pyblish.api.InstancePlugin): """ optional = True - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ["maya"] families = ["model"] label = "Model Name" diff --git a/openpype/hosts/maya/plugins/publish/validate_mvlook_contents.py b/openpype/hosts/maya/plugins/publish/validate_mvlook_contents.py index bac2c030c8..62f360cd86 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mvlook_contents.py +++ b/openpype/hosts/maya/plugins/publish/validate_mvlook_contents.py @@ -1,15 +1,16 @@ +import os import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import ValidateContentsOrder -import os COLOUR_SPACES = ['sRGB', 'linear', 'auto'] MIPMAP_EXTENSIONS = ['tdl'] class ValidateMvLookContents(pyblish.api.InstancePlugin): - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder families = ['mvLook'] hosts = ['maya'] label = 'Validate mvLook Data' diff --git a/openpype/hosts/maya/plugins/publish/validate_no_animation.py b/openpype/hosts/maya/plugins/publish/validate_no_animation.py index 6621e452f0..177de1468d 100644 --- a/openpype/hosts/maya/plugins/publish/validate_no_animation.py +++ b/openpype/hosts/maya/plugins/publish/validate_no_animation.py @@ -3,6 +3,7 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import ValidateContentsOrder class ValidateNoAnimation(pyblish.api.Validator): @@ -14,7 +15,7 @@ class ValidateNoAnimation(pyblish.api.Validator): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder label = "No Animation" hosts = ["maya"] families = ["model"] diff --git a/openpype/hosts/maya/plugins/publish/validate_no_default_camera.py b/openpype/hosts/maya/plugins/publish/validate_no_default_camera.py index c3f6f3c38e..d4ddb28070 100644 --- a/openpype/hosts/maya/plugins/publish/validate_no_default_camera.py +++ b/openpype/hosts/maya/plugins/publish/validate_no_default_camera.py @@ -3,6 +3,7 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import ValidateContentsOrder class ValidateNoDefaultCameras(pyblish.api.InstancePlugin): @@ -13,7 +14,7 @@ class ValidateNoDefaultCameras(pyblish.api.InstancePlugin): settings when being loaded and sometimes being skipped. """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ['maya'] families = ['camera'] version = (0, 1, 0) diff --git a/openpype/hosts/maya/plugins/publish/validate_no_namespace.py b/openpype/hosts/maya/plugins/publish/validate_no_namespace.py index 6b721135b8..95caa1007f 100644 --- a/openpype/hosts/maya/plugins/publish/validate_no_namespace.py +++ b/openpype/hosts/maya/plugins/publish/validate_no_namespace.py @@ -3,7 +3,11 @@ import maya.cmds as cmds import pyblish.api import openpype.api -from openpype.pipeline.publish import RepairAction +from openpype.pipeline.publish import ( + RepairAction, + ValidateContentsOrder, +) + import openpype.hosts.maya.api.action @@ -17,7 +21,7 @@ def get_namespace(node_name): class ValidateNoNamespace(pyblish.api.InstancePlugin): """Ensure the nodes don't have a namespace""" - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ['maya'] families = ['model'] category = 'cleanup' diff --git a/openpype/hosts/maya/plugins/publish/validate_no_null_transforms.py b/openpype/hosts/maya/plugins/publish/validate_no_null_transforms.py index fea91b666d..f31fd09c95 100644 --- a/openpype/hosts/maya/plugins/publish/validate_no_null_transforms.py +++ b/openpype/hosts/maya/plugins/publish/validate_no_null_transforms.py @@ -3,7 +3,10 @@ import maya.cmds as cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action -from openpype.pipeline.publish import RepairAction +from openpype.pipeline.publish import ( + RepairAction, + ValidateContentsOrder, +) def has_shape_children(node): @@ -38,7 +41,7 @@ class ValidateNoNullTransforms(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ['maya'] families = ['model'] category = 'cleanup' diff --git a/openpype/hosts/maya/plugins/publish/validate_no_unknown_nodes.py b/openpype/hosts/maya/plugins/publish/validate_no_unknown_nodes.py index d140a1f24a..20fe34f2fd 100644 --- a/openpype/hosts/maya/plugins/publish/validate_no_unknown_nodes.py +++ b/openpype/hosts/maya/plugins/publish/validate_no_unknown_nodes.py @@ -3,6 +3,7 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import ValidateContentsOrder class ValidateNoUnknownNodes(pyblish.api.InstancePlugin): @@ -16,7 +17,7 @@ class ValidateNoUnknownNodes(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ['maya'] families = ['model', 'rig'] optional = True diff --git a/openpype/hosts/maya/plugins/publish/validate_node_ids.py b/openpype/hosts/maya/plugins/publish/validate_node_ids.py index d17d34117f..877ba0e781 100644 --- a/openpype/hosts/maya/plugins/publish/validate_node_ids.py +++ b/openpype/hosts/maya/plugins/publish/validate_node_ids.py @@ -1,7 +1,7 @@ import pyblish.api import openpype.api +from openpype.pipeline.publish import ValidatePipelineOrder import openpype.hosts.maya.api.action - from openpype.hosts.maya.api import lib @@ -14,7 +14,7 @@ class ValidateNodeIDs(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidatePipelineOrder + order = ValidatePipelineOrder label = 'Instance Nodes Have ID' hosts = ['maya'] families = ["model", diff --git a/openpype/hosts/maya/plugins/publish/validate_node_ids_deformed_shapes.py b/openpype/hosts/maya/plugins/publish/validate_node_ids_deformed_shapes.py index 363d8882c0..1fe4a34e07 100644 --- a/openpype/hosts/maya/plugins/publish/validate_node_ids_deformed_shapes.py +++ b/openpype/hosts/maya/plugins/publish/validate_node_ids_deformed_shapes.py @@ -4,7 +4,10 @@ import pyblish.api import openpype.api import openpype.hosts.maya.api.action from openpype.hosts.maya.api import lib -from openpype.pipeline.publish import RepairAction +from openpype.pipeline.publish import ( + RepairAction, + ValidateContentsOrder, +) class ValidateNodeIdsDeformedShape(pyblish.api.InstancePlugin): @@ -17,7 +20,7 @@ class ValidateNodeIdsDeformedShape(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder families = ['look'] hosts = ['maya'] label = 'Deformed shape ids' diff --git a/openpype/hosts/maya/plugins/publish/validate_node_ids_in_database.py b/openpype/hosts/maya/plugins/publish/validate_node_ids_in_database.py index 632b531668..a5b1215f30 100644 --- a/openpype/hosts/maya/plugins/publish/validate_node_ids_in_database.py +++ b/openpype/hosts/maya/plugins/publish/validate_node_ids_in_database.py @@ -3,6 +3,7 @@ import pyblish.api import openpype.api from openpype.client import get_assets from openpype.pipeline import legacy_io +from openpype.pipeline.publish import ValidatePipelineOrder import openpype.hosts.maya.api.action from openpype.hosts.maya.api import lib @@ -18,7 +19,7 @@ class ValidateNodeIdsInDatabase(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidatePipelineOrder + order = ValidatePipelineOrder label = 'Node Ids in Database' hosts = ['maya'] families = ["*"] diff --git a/openpype/hosts/maya/plugins/publish/validate_node_ids_related.py b/openpype/hosts/maya/plugins/publish/validate_node_ids_related.py index c8bac6e569..a7595d7392 100644 --- a/openpype/hosts/maya/plugins/publish/validate_node_ids_related.py +++ b/openpype/hosts/maya/plugins/publish/validate_node_ids_related.py @@ -1,6 +1,7 @@ import pyblish.api import openpype.api +from openpype.pipeline.publish import ValidatePipelineOrder import openpype.hosts.maya.api.action from openpype.hosts.maya.api import lib @@ -10,7 +11,7 @@ class ValidateNodeIDsRelated(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidatePipelineOrder + order = ValidatePipelineOrder label = 'Node Ids Related (ID)' hosts = ['maya'] families = ["model", diff --git a/openpype/hosts/maya/plugins/publish/validate_node_ids_unique.py b/openpype/hosts/maya/plugins/publish/validate_node_ids_unique.py index ed9ef526d6..5ff18358e2 100644 --- a/openpype/hosts/maya/plugins/publish/validate_node_ids_unique.py +++ b/openpype/hosts/maya/plugins/publish/validate_node_ids_unique.py @@ -2,6 +2,7 @@ from collections import defaultdict import pyblish.api import openpype.api +from openpype.pipeline.publish import ValidatePipelineOrder import openpype.hosts.maya.api.action from openpype.hosts.maya.api import lib @@ -12,7 +13,7 @@ class ValidateNodeIdsUnique(pyblish.api.InstancePlugin): Here we ensure that what has been added to the instance is unique """ - order = openpype.api.ValidatePipelineOrder + order = ValidatePipelineOrder label = 'Non Duplicate Instance Members (ID)' hosts = ['maya'] families = ["model", diff --git a/openpype/hosts/maya/plugins/publish/validate_node_no_ghosting.py b/openpype/hosts/maya/plugins/publish/validate_node_no_ghosting.py index 38f3ab1e68..2f22d6da1e 100644 --- a/openpype/hosts/maya/plugins/publish/validate_node_no_ghosting.py +++ b/openpype/hosts/maya/plugins/publish/validate_node_no_ghosting.py @@ -3,6 +3,7 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import ValidateContentsOrder class ValidateNodeNoGhosting(pyblish.api.InstancePlugin): @@ -17,7 +18,7 @@ class ValidateNodeNoGhosting(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ['maya'] families = ['model', 'rig'] label = "No Ghosting" diff --git a/openpype/hosts/maya/plugins/publish/validate_render_image_rule.py b/openpype/hosts/maya/plugins/publish/validate_render_image_rule.py index 965211e046..78bb022785 100644 --- a/openpype/hosts/maya/plugins/publish/validate_render_image_rule.py +++ b/openpype/hosts/maya/plugins/publish/validate_render_image_rule.py @@ -1,8 +1,10 @@ from maya import cmds import pyblish.api -import openpype.api -from openpype.pipeline.publish import RepairAction +from openpype.pipeline.publish import ( + RepairAction, + ValidateContentsOrder, +) class ValidateRenderImageRule(pyblish.api.InstancePlugin): @@ -14,7 +16,7 @@ class ValidateRenderImageRule(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder label = "Images File Rule (Workspace)" hosts = ["maya"] families = ["renderlayer"] diff --git a/openpype/hosts/maya/plugins/publish/validate_render_no_default_cameras.py b/openpype/hosts/maya/plugins/publish/validate_render_no_default_cameras.py index 044cc7c6a2..da35f42291 100644 --- a/openpype/hosts/maya/plugins/publish/validate_render_no_default_cameras.py +++ b/openpype/hosts/maya/plugins/publish/validate_render_no_default_cameras.py @@ -3,12 +3,13 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import ValidateContentsOrder class ValidateRenderNoDefaultCameras(pyblish.api.InstancePlugin): """Ensure no default (startup) cameras are to be rendered.""" - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ['maya'] families = ['renderlayer'] label = "No Default Cameras Renderable" diff --git a/openpype/hosts/maya/plugins/publish/validate_render_single_camera.py b/openpype/hosts/maya/plugins/publish/validate_render_single_camera.py index 35b87fd0ab..fc41b1cf5b 100644 --- a/openpype/hosts/maya/plugins/publish/validate_render_single_camera.py +++ b/openpype/hosts/maya/plugins/publish/validate_render_single_camera.py @@ -6,6 +6,7 @@ from maya import cmds import openpype.api import openpype.hosts.maya.api.action from openpype.hosts.maya.api.render_settings import RenderSettings +from openpype.pipeline.publish import ValidateContentsOrder class ValidateRenderSingleCamera(pyblish.api.InstancePlugin): @@ -15,7 +16,7 @@ class ValidateRenderSingleCamera(pyblish.api.InstancePlugin): prefix must contain token. """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder label = "Render Single Camera" hosts = ['maya'] families = ["renderlayer", diff --git a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index 41cfbc19b5..08ecc0d149 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -6,8 +6,10 @@ from collections import OrderedDict from maya import cmds, mel import pyblish.api -import openpype.api -from openpype.pipeline.publish import RepairAction +from openpype.pipeline.publish import ( + RepairAction, + ValidateContentsOrder, +) from openpype.hosts.maya.api import lib @@ -40,7 +42,7 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder label = "Render Settings" hosts = ["maya"] families = ["renderlayer"] diff --git a/openpype/hosts/maya/plugins/publish/validate_resources.py b/openpype/hosts/maya/plugins/publish/validate_resources.py index 08f0f5467c..b7bd47ad0a 100644 --- a/openpype/hosts/maya/plugins/publish/validate_resources.py +++ b/openpype/hosts/maya/plugins/publish/validate_resources.py @@ -2,7 +2,7 @@ import os from collections import defaultdict import pyblish.api -import openpype.api +from openpype.pipeline.publish import ValidateContentsOrder class ValidateResources(pyblish.api.InstancePlugin): @@ -17,7 +17,7 @@ class ValidateResources(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder label = "Resources Unique" def process(self, instance): diff --git a/openpype/hosts/maya/plugins/publish/validate_review_subset_uniqueness.py b/openpype/hosts/maya/plugins/publish/validate_review_subset_uniqueness.py index 04cc9ab5fb..361c594013 100644 --- a/openpype/hosts/maya/plugins/publish/validate_review_subset_uniqueness.py +++ b/openpype/hosts/maya/plugins/publish/validate_review_subset_uniqueness.py @@ -1,14 +1,16 @@ # -*- coding: utf-8 -*- import collections import pyblish.api -import openpype.api -from openpype.pipeline import PublishXmlValidationError +from openpype.pipeline.publish import ( + ValidateContentsOrder, + PublishXmlValidationError, +) class ValidateReviewSubsetUniqueness(pyblish.api.ContextPlugin): """Validates that review subset has unique name.""" - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ["maya"] families = ["review"] label = "Validate Review Subset Unique" diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_contents.py b/openpype/hosts/maya/plugins/publish/validate_rig_contents.py index 6fe51d7b51..1096c95486 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_contents.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_contents.py @@ -1,7 +1,7 @@ from maya import cmds import pyblish.api -import openpype.api +from openpype.pipeline.publish import ValidateContentsOrder class ValidateRigContents(pyblish.api.InstancePlugin): @@ -13,7 +13,7 @@ class ValidateRigContents(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder label = "Rig Contents" hosts = ["maya"] families = ["rig"] diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py b/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py index fc6cda7f23..1e42abdcd9 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py @@ -2,7 +2,10 @@ from maya import cmds import pyblish.api -from openpype.pipeline.publish import RepairAction +from openpype.pipeline.publish import ( + ValidateContentsOrder, + RepairAction, +) import openpype.hosts.maya.api.action from openpype.hosts.maya.api.lib import undo_chunk @@ -25,7 +28,7 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): - Break all incoming connections to keyable attributes """ - order = openpype.api.ValidateContentsOrder + 0.05 + order = ValidateContentsOrder + 0.05 label = "Rig Controllers" hosts = ["maya"] families = ["rig"] diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_controllers_arnold_attributes.py b/openpype/hosts/maya/plugins/publish/validate_rig_controllers_arnold_attributes.py index 23237d7ef6..3d486cf7a4 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_controllers_arnold_attributes.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_controllers_arnold_attributes.py @@ -3,7 +3,10 @@ from maya import cmds import pyblish.api import openpype.api -from openpype.pipeline.publish import RepairAction +from openpype.pipeline.publish import ( + ValidateContentsOrder, + RepairAction, +) from openpype.hosts.maya.api import lib import openpype.hosts.maya.api.action @@ -27,7 +30,7 @@ class ValidateRigControllersArnoldAttributes(pyblish.api.InstancePlugin): This validator will ensure they are hidden or unkeyable attributes. """ - order = openpype.api.ValidateContentsOrder + 0.05 + order = ValidateContentsOrder + 0.05 label = "Rig Controllers (Arnold Attributes)" hosts = ["maya"] families = ["rig"] diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py b/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py index bad9c8b0d4..86967d7502 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py @@ -4,7 +4,10 @@ import pyblish.api import openpype.api import openpype.hosts.maya.api.action from openpype.hosts.maya.api import lib -from openpype.pipeline.publish import RepairAction +from openpype.pipeline.publish import ( + RepairAction, + ValidateContentsOrder, +) class ValidateRigJointsHidden(pyblish.api.InstancePlugin): @@ -18,7 +21,7 @@ class ValidateRigJointsHidden(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ['maya'] families = ['rig'] version = (0, 1, 0) diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py b/openpype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py index f170bc72b1..70128ac493 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py @@ -4,7 +4,10 @@ import pyblish.api import openpype.api import openpype.hosts.maya.api.action from openpype.hosts.maya.api import lib -from openpype.pipeline.publish import RepairAction +from openpype.pipeline.publish import ( + RepairAction, + ValidateContentsOrder, +) class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin): @@ -17,7 +20,7 @@ class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder families = ["rig"] hosts = ['maya'] label = 'Rig Out Set Node Ids' diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py b/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py index d0ef8d9c04..f075f42ff2 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py @@ -4,7 +4,10 @@ import pyblish.api import openpype.api import openpype.hosts.maya.api.action -from openpype.pipeline.publish import RepairAction +from openpype.pipeline.publish import ( + RepairAction, + ValidateContentsOrder, +) class ValidateRigOutputIds(pyblish.api.InstancePlugin): @@ -14,7 +17,7 @@ class ValidateRigOutputIds(pyblish.api.InstancePlugin): to ensure the id from the model is preserved through animation. """ - order = openpype.api.ValidateContentsOrder + 0.05 + order = ValidateContentsOrder + 0.05 label = "Rig Output Ids" hosts = ["maya"] families = ["rig"] diff --git a/openpype/hosts/maya/plugins/publish/validate_scene_set_workspace.py b/openpype/hosts/maya/plugins/publish/validate_scene_set_workspace.py index 174bc44a6f..ec2bea220d 100644 --- a/openpype/hosts/maya/plugins/publish/validate_scene_set_workspace.py +++ b/openpype/hosts/maya/plugins/publish/validate_scene_set_workspace.py @@ -3,7 +3,8 @@ import os import maya.cmds as cmds import pyblish.api -import openpype.api + +from openpype.pipeline.publish import ValidatePipelineOrder def is_subdir(path, root_dir): @@ -28,7 +29,7 @@ def is_subdir(path, root_dir): class ValidateSceneSetWorkspace(pyblish.api.ContextPlugin): """Validate the scene is inside the currently set Maya workspace""" - order = openpype.api.ValidatePipelineOrder + order = ValidatePipelineOrder hosts = ['maya'] category = 'scene' version = (0, 1, 0) diff --git a/openpype/hosts/maya/plugins/publish/validate_setdress_root.py b/openpype/hosts/maya/plugins/publish/validate_setdress_root.py index 8e23a7c04f..5fd971f8c4 100644 --- a/openpype/hosts/maya/plugins/publish/validate_setdress_root.py +++ b/openpype/hosts/maya/plugins/publish/validate_setdress_root.py @@ -1,12 +1,11 @@ - import pyblish.api -import openpype.api +from openpype.pipeline.publish import ValidateContentsOrder class ValidateSetdressRoot(pyblish.api.InstancePlugin): """Validate if set dress top root node is published.""" - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder label = "SetDress Root" hosts = ["maya"] families = ["setdress"] diff --git a/openpype/hosts/maya/plugins/publish/validate_shader_name.py b/openpype/hosts/maya/plugins/publish/validate_shader_name.py index 24111f0ad4..522b42fd00 100644 --- a/openpype/hosts/maya/plugins/publish/validate_shader_name.py +++ b/openpype/hosts/maya/plugins/publish/validate_shader_name.py @@ -1,9 +1,10 @@ +import re from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action -import re +from openpype.pipeline.publish import ValidateContentsOrder class ValidateShaderName(pyblish.api.InstancePlugin): @@ -13,7 +14,7 @@ class ValidateShaderName(pyblish.api.InstancePlugin): """ optional = True - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder families = ["look"] hosts = ['maya'] label = 'Validate Shaders Name' diff --git a/openpype/hosts/maya/plugins/publish/validate_shape_default_names.py b/openpype/hosts/maya/plugins/publish/validate_shape_default_names.py index d6e7ed82c4..25bd3442a3 100644 --- a/openpype/hosts/maya/plugins/publish/validate_shape_default_names.py +++ b/openpype/hosts/maya/plugins/publish/validate_shape_default_names.py @@ -5,7 +5,10 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action -from openpype.pipeline.publish import RepairAction +from openpype.pipeline.publish import ( + ValidateContentsOrder, + RepairAction, +) def short_name(node): @@ -32,7 +35,7 @@ class ValidateShapeDefaultNames(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ['maya'] families = ['model'] category = 'cleanup' diff --git a/openpype/hosts/maya/plugins/publish/validate_shape_render_stats.py b/openpype/hosts/maya/plugins/publish/validate_shape_render_stats.py index c332e07505..0980d6b4b6 100644 --- a/openpype/hosts/maya/plugins/publish/validate_shape_render_stats.py +++ b/openpype/hosts/maya/plugins/publish/validate_shape_render_stats.py @@ -4,13 +4,16 @@ import openpype.api from maya import cmds import openpype.hosts.maya.api.action -from openpype.pipeline.publish import RepairAction +from openpype.pipeline.publish import ( + RepairAction, + ValidateMeshOrder, +) class ValidateShapeRenderStats(pyblish.api.Validator): """Ensure all render stats are set to the default values.""" - order = openpype.api.ValidateMeshOrder + order = ValidateMeshOrder hosts = ['maya'] families = ['model'] label = 'Shape Default Render Stats' diff --git a/openpype/hosts/maya/plugins/publish/validate_shape_zero.py b/openpype/hosts/maya/plugins/publish/validate_shape_zero.py index 515a8056f7..9e30735d40 100644 --- a/openpype/hosts/maya/plugins/publish/validate_shape_zero.py +++ b/openpype/hosts/maya/plugins/publish/validate_shape_zero.py @@ -4,7 +4,10 @@ import pyblish.api import openpype.api import openpype.hosts.maya.api.action from openpype.hosts.maya.api import lib -from openpype.pipeline.publish import RepairAction +from openpype.pipeline.publish import ( + ValidateContentsOrder, + RepairAction, +) class ValidateShapeZero(pyblish.api.Validator): @@ -14,7 +17,7 @@ class ValidateShapeZero(pyblish.api.Validator): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ["maya"] families = ["model"] label = "Shape Zero (Freeze)" diff --git a/openpype/hosts/maya/plugins/publish/validate_single_assembly.py b/openpype/hosts/maya/plugins/publish/validate_single_assembly.py index 9fb3a47e6d..8771ca58d1 100644 --- a/openpype/hosts/maya/plugins/publish/validate_single_assembly.py +++ b/openpype/hosts/maya/plugins/publish/validate_single_assembly.py @@ -1,5 +1,5 @@ import pyblish.api -import openpype.api +from openpype.pipeline.publish import ValidateContentsOrder class ValidateSingleAssembly(pyblish.api.InstancePlugin): @@ -17,7 +17,7 @@ class ValidateSingleAssembly(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ['maya'] families = ['rig', 'animation'] label = 'Single Assembly' diff --git a/openpype/hosts/maya/plugins/publish/validate_skeletalmesh_hierarchy.py b/openpype/hosts/maya/plugins/publish/validate_skeletalmesh_hierarchy.py index 54a86d27cf..8221c18b17 100644 --- a/openpype/hosts/maya/plugins/publish/validate_skeletalmesh_hierarchy.py +++ b/openpype/hosts/maya/plugins/publish/validate_skeletalmesh_hierarchy.py @@ -1,7 +1,10 @@ # -*- coding: utf-8 -*- import pyblish.api -import openpype.api -from openpype.pipeline import PublishXmlValidationError + +from openpype.pipeline.publish import ( + ValidateContentsOrder, + PublishXmlValidationError, +) from maya import cmds @@ -9,7 +12,7 @@ from maya import cmds class ValidateSkeletalMeshHierarchy(pyblish.api.InstancePlugin): """Validates that nodes has common root.""" - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ["maya"] families = ["skeletalMesh"] label = "Skeletal Mesh Top Node" diff --git a/openpype/hosts/maya/plugins/publish/validate_skinCluster_deformer_set.py b/openpype/hosts/maya/plugins/publish/validate_skinCluster_deformer_set.py index 8c804786f3..86ff914cb0 100644 --- a/openpype/hosts/maya/plugins/publish/validate_skinCluster_deformer_set.py +++ b/openpype/hosts/maya/plugins/publish/validate_skinCluster_deformer_set.py @@ -3,6 +3,7 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import ValidateContentsOrder class ValidateSkinclusterDeformerSet(pyblish.api.InstancePlugin): @@ -14,7 +15,7 @@ class ValidateSkinclusterDeformerSet(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ['maya'] families = ['fbx'] label = "Skincluster Deformer Relationships" diff --git a/openpype/hosts/maya/plugins/publish/validate_step_size.py b/openpype/hosts/maya/plugins/publish/validate_step_size.py index 172ac5f26e..552a936966 100644 --- a/openpype/hosts/maya/plugins/publish/validate_step_size.py +++ b/openpype/hosts/maya/plugins/publish/validate_step_size.py @@ -1,6 +1,7 @@ import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import ValidateContentsOrder class ValidateStepSize(pyblish.api.InstancePlugin): @@ -10,7 +11,7 @@ class ValidateStepSize(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder label = 'Step size' families = ['camera', 'pointcache', diff --git a/openpype/hosts/maya/plugins/publish/validate_transform_naming_suffix.py b/openpype/hosts/maya/plugins/publish/validate_transform_naming_suffix.py index 6f5ff24b9c..64faf9ecb6 100644 --- a/openpype/hosts/maya/plugins/publish/validate_transform_naming_suffix.py +++ b/openpype/hosts/maya/plugins/publish/validate_transform_naming_suffix.py @@ -5,6 +5,7 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import ValidateContentsOrder class ValidateTransformNamingSuffix(pyblish.api.InstancePlugin): @@ -27,7 +28,7 @@ class ValidateTransformNamingSuffix(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ['maya'] families = ['model'] category = 'cleanup' diff --git a/openpype/hosts/maya/plugins/publish/validate_transform_zero.py b/openpype/hosts/maya/plugins/publish/validate_transform_zero.py index fdd09658d1..9e232f6023 100644 --- a/openpype/hosts/maya/plugins/publish/validate_transform_zero.py +++ b/openpype/hosts/maya/plugins/publish/validate_transform_zero.py @@ -3,6 +3,7 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import ValidateContentsOrder class ValidateTransformZero(pyblish.api.Validator): @@ -14,7 +15,7 @@ class ValidateTransformZero(pyblish.api.Validator): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ["maya"] families = ["model"] category = "geometry" diff --git a/openpype/hosts/maya/plugins/publish/validate_unreal_mesh_triangulated.py b/openpype/hosts/maya/plugins/publish/validate_unreal_mesh_triangulated.py index c05121a1b0..1ed3e5531c 100644 --- a/openpype/hosts/maya/plugins/publish/validate_unreal_mesh_triangulated.py +++ b/openpype/hosts/maya/plugins/publish/validate_unreal_mesh_triangulated.py @@ -3,12 +3,13 @@ from maya import cmds import pyblish.api import openpype.api +from openpype.pipeline.publish import ValidateMeshOrder class ValidateUnrealMeshTriangulated(pyblish.api.InstancePlugin): """Validate if mesh is made of triangles for Unreal Engine""" - order = openpype.api.ValidateMeshOrder + order = ValidateMeshOrder hosts = ["maya"] families = ["staticMesh"] category = "geometry" diff --git a/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py b/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py index 33788d1835..a4bb54f5af 100644 --- a/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py +++ b/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py @@ -6,7 +6,8 @@ import pyblish.api import openpype.api import openpype.hosts.maya.api.action from openpype.pipeline import legacy_io -from openpype.api import get_project_settings +from openpype.settings import get_project_settings +from openpype.pipeline.publish import ValidateContentsOrder class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin): @@ -50,7 +51,7 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin): """ optional = True - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ["maya"] families = ["staticMesh"] label = "Unreal Static Mesh Name" diff --git a/openpype/hosts/maya/plugins/publish/validate_unreal_up_axis.py b/openpype/hosts/maya/plugins/publish/validate_unreal_up_axis.py index 3ed22c7cef..dd699735d9 100644 --- a/openpype/hosts/maya/plugins/publish/validate_unreal_up_axis.py +++ b/openpype/hosts/maya/plugins/publish/validate_unreal_up_axis.py @@ -2,8 +2,11 @@ from maya import cmds import pyblish.api -import openpype.api -from openpype.pipeline.publish import RepairAction + +from openpype.pipeline.publish import ( + ValidateContentsOrder, + RepairAction, +) class ValidateUnrealUpAxis(pyblish.api.ContextPlugin): @@ -11,7 +14,7 @@ class ValidateUnrealUpAxis(pyblish.api.ContextPlugin): optional = True active = False - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ["maya"] families = ["staticMesh"] label = "Unreal Up-Axis check" diff --git a/openpype/hosts/maya/plugins/publish/validate_visible_only.py b/openpype/hosts/maya/plugins/publish/validate_visible_only.py index 59a7f976ab..f326b91796 100644 --- a/openpype/hosts/maya/plugins/publish/validate_visible_only.py +++ b/openpype/hosts/maya/plugins/publish/validate_visible_only.py @@ -3,6 +3,7 @@ import pyblish.api import openpype.api from openpype.hosts.maya.api.lib import iter_visible_nodes_in_range import openpype.hosts.maya.api.action +from openpype.pipeline.publish import ValidateContentsOrder class ValidateAlembicVisibleOnly(pyblish.api.InstancePlugin): @@ -12,7 +13,7 @@ class ValidateAlembicVisibleOnly(pyblish.api.InstancePlugin): on the instance - otherwise the validation is skipped. """ - order = openpype.api.ValidateContentsOrder + 0.05 + order = ValidateContentsOrder + 0.05 label = "Alembic Visible Only" hosts = ["maya"] families = ["pointcache", "animation"] diff --git a/openpype/hosts/maya/plugins/publish/validate_vray_distributed_rendering.py b/openpype/hosts/maya/plugins/publish/validate_vray_distributed_rendering.py index 376d49d583..366f3bd10e 100644 --- a/openpype/hosts/maya/plugins/publish/validate_vray_distributed_rendering.py +++ b/openpype/hosts/maya/plugins/publish/validate_vray_distributed_rendering.py @@ -1,7 +1,9 @@ import pyblish.api -import openpype.api from openpype.hosts.maya.api import lib -from openpype.pipeline.publish import RepairAction +from openpype.pipeline.publish import ( + ValidateContentsOrder, + RepairAction, +) from maya import cmds @@ -16,7 +18,7 @@ class ValidateVRayDistributedRendering(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder label = "VRay Distributed Rendering" families = ["renderlayer"] actions = [RepairAction] diff --git a/openpype/hosts/maya/plugins/publish/validate_vray_translator_settings.py b/openpype/hosts/maya/plugins/publish/validate_vray_translator_settings.py index 9b78699e14..f49811c2c0 100644 --- a/openpype/hosts/maya/plugins/publish/validate_vray_translator_settings.py +++ b/openpype/hosts/maya/plugins/publish/validate_vray_translator_settings.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- """Validate VRay Translator settings.""" import pyblish.api -import openpype.api from openpype.pipeline.publish import ( context_plugin_should_run, RepairContextAction, + ValidateContentsOrder, ) from maya import cmds @@ -13,7 +13,7 @@ from maya import cmds class ValidateVRayTranslatorEnabled(pyblish.api.ContextPlugin): """Validate VRay Translator settings for extracting vrscenes.""" - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder label = "VRay Translator Settings" families = ["vrayscene_layer"] actions = [RepairContextAction] diff --git a/openpype/hosts/maya/plugins/publish/validate_yeti_renderscript_callbacks.py b/openpype/hosts/maya/plugins/publish/validate_yeti_renderscript_callbacks.py index 79cd09315e..a864a18cee 100644 --- a/openpype/hosts/maya/plugins/publish/validate_yeti_renderscript_callbacks.py +++ b/openpype/hosts/maya/plugins/publish/validate_yeti_renderscript_callbacks.py @@ -1,7 +1,7 @@ from maya import cmds import pyblish.api -import openpype.api +from openpype.pipeline.publish import ValidateContentsOrder class ValidateYetiRenderScriptCallbacks(pyblish.api.InstancePlugin): @@ -20,7 +20,7 @@ class ValidateYetiRenderScriptCallbacks(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder label = "Yeti Render Script Callbacks" hosts = ["maya"] families = ["renderlayer"] diff --git a/openpype/hosts/maya/plugins/publish/validate_yeti_rig_input_in_instance.py b/openpype/hosts/maya/plugins/publish/validate_yeti_rig_input_in_instance.py index 651c8da849..0fe89634f5 100644 --- a/openpype/hosts/maya/plugins/publish/validate_yeti_rig_input_in_instance.py +++ b/openpype/hosts/maya/plugins/publish/validate_yeti_rig_input_in_instance.py @@ -3,12 +3,13 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import ValidateContentsOrder class ValidateYetiRigInputShapesInInstance(pyblish.api.Validator): """Validate if all input nodes are part of the instance's hierarchy""" - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ["maya"] families = ["yetiRig"] label = "Yeti Rig Input Shapes In Instance" diff --git a/openpype/hosts/nuke/plugins/publish/validate_asset_name.py b/openpype/hosts/nuke/plugins/publish/validate_asset_name.py index 7647471f8a..52731140ff 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_asset_name.py +++ b/openpype/hosts/nuke/plugins/publish/validate_asset_name.py @@ -4,10 +4,13 @@ from __future__ import absolute_import import nuke import pyblish.api -import openpype.api + import openpype.hosts.nuke.api.lib as nlib import openpype.hosts.nuke.api as nuke_api -from openpype.pipeline import PublishXmlValidationError +from openpype.pipeline.publish import ( + ValidateContentsOrder, + PublishXmlValidationError, +) class SelectInvalidInstances(pyblish.api.Action): @@ -97,7 +100,7 @@ class ValidateCorrectAssetName(pyblish.api.InstancePlugin): Action on this validator will select invalid instances in Outliner. """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder label = "Validate correct asset name" hosts = ["nuke"] actions = [ diff --git a/openpype/hosts/photoshop/plugins/publish/validate_instance_asset.py b/openpype/hosts/photoshop/plugins/publish/validate_instance_asset.py index b65f9d259f..2609f7a8cf 100644 --- a/openpype/hosts/photoshop/plugins/publish/validate_instance_asset.py +++ b/openpype/hosts/photoshop/plugins/publish/validate_instance_asset.py @@ -1,7 +1,7 @@ import pyblish.api -import openpype.api from openpype.pipeline import legacy_io +from openpype.pipeline.publish import ValidateContentsOrder from openpype.hosts.photoshop import api as photoshop @@ -45,7 +45,7 @@ class ValidateInstanceAsset(pyblish.api.InstancePlugin): label = "Validate Instance Asset" hosts = ["photoshop"] actions = [ValidateInstanceAssetRepair] - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder def process(self, instance): instance_asset = instance.data["asset"] diff --git a/openpype/hosts/photoshop/plugins/publish/validate_naming.py b/openpype/hosts/photoshop/plugins/publish/validate_naming.py index 8106d6ff16..0665aff9d0 100644 --- a/openpype/hosts/photoshop/plugins/publish/validate_naming.py +++ b/openpype/hosts/photoshop/plugins/publish/validate_naming.py @@ -1,10 +1,13 @@ import re import pyblish.api -import openpype.api -from openpype.pipeline import PublishXmlValidationError + from openpype.hosts.photoshop import api as photoshop from openpype.pipeline.create import SUBSET_NAME_ALLOWED_SYMBOLS +from openpype.pipeline.publish import ( + ValidateContentsOrder, + PublishXmlValidationError, +) class ValidateNamingRepair(pyblish.api.Action): @@ -72,7 +75,7 @@ class ValidateNaming(pyblish.api.InstancePlugin): label = "Validate Naming" hosts = ["photoshop"] - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder families = ["image"] actions = [ValidateNamingRepair] diff --git a/openpype/hosts/photoshop/plugins/publish/validate_unique_subsets.py b/openpype/hosts/photoshop/plugins/publish/validate_unique_subsets.py index 01f2323157..78e84729ce 100644 --- a/openpype/hosts/photoshop/plugins/publish/validate_unique_subsets.py +++ b/openpype/hosts/photoshop/plugins/publish/validate_unique_subsets.py @@ -1,7 +1,9 @@ import collections import pyblish.api -import openpype.api -from openpype.pipeline import PublishXmlValidationError +from openpype.pipeline.publish import ( + ValidateContentsOrder, + PublishXmlValidationError, +) class ValidateSubsetUniqueness(pyblish.api.ContextPlugin): @@ -11,7 +13,7 @@ class ValidateSubsetUniqueness(pyblish.api.ContextPlugin): label = "Validate Subset Uniqueness" hosts = ["photoshop"] - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder families = ["image"] def process(self, context): diff --git a/openpype/hosts/standalonepublisher/plugins/publish/validate_editorial_resources.py b/openpype/hosts/standalonepublisher/plugins/publish/validate_editorial_resources.py index afb828474d..3d2b6d04ad 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/validate_editorial_resources.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/validate_editorial_resources.py @@ -1,6 +1,8 @@ import pyblish.api -import openpype.api -from openpype.pipeline import PublishXmlValidationError +from openpype.pipeline.publish import ( + ValidateContentsOrder, + PublishXmlValidationError, +) class ValidateEditorialResources(pyblish.api.InstancePlugin): @@ -13,7 +15,7 @@ class ValidateEditorialResources(pyblish.api.InstancePlugin): # make sure it is enabled only if at least both families are available match = pyblish.api.Subset - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder def process(self, instance): self.log.debug( diff --git a/openpype/hosts/standalonepublisher/plugins/publish/validate_frame_ranges.py b/openpype/hosts/standalonepublisher/plugins/publish/validate_frame_ranges.py index ff7f60354e..074c62ea0e 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/validate_frame_ranges.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/validate_frame_ranges.py @@ -2,9 +2,11 @@ import re import pyblish.api -import openpype.api -from openpype.pipeline import PublishXmlValidationError from openpype.pipeline.context_tools import get_current_project_asset +from openpype.pipeline.publish import ( + ValidateContentsOrder, + PublishXmlValidationError, +) class ValidateFrameRange(pyblish.api.InstancePlugin): @@ -13,7 +15,7 @@ class ValidateFrameRange(pyblish.api.InstancePlugin): label = "Validate Frame Range" hosts = ["standalonepublisher"] families = ["render"] - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder optional = True # published data might be sequence (.mov, .mp4) in that counting files diff --git a/openpype/hosts/standalonepublisher/plugins/publish/validate_shot_duplicates.py b/openpype/hosts/standalonepublisher/plugins/publish/validate_shot_duplicates.py index fe655f6b74..df04ae3b66 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/validate_shot_duplicates.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/validate_shot_duplicates.py @@ -1,14 +1,17 @@ import pyblish.api -import openpype.api -from openpype.pipeline import PublishXmlValidationError +from openpype.pipeline.publish import ( + ValidateContentsOrder, + PublishXmlValidationError, +) + class ValidateShotDuplicates(pyblish.api.ContextPlugin): """Validating no duplicate names are in context.""" label = "Validate Shot Duplicates" hosts = ["standalonepublisher"] - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder def process(self, context): shot_names = [] diff --git a/openpype/hosts/standalonepublisher/plugins/publish/validate_simple_unreal_texture_naming.py b/openpype/hosts/standalonepublisher/plugins/publish/validate_simple_unreal_texture_naming.py index ef8da9f280..c123bef4f8 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/validate_simple_unreal_texture_naming.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/validate_simple_unreal_texture_naming.py @@ -1,16 +1,19 @@ # -*- coding: utf-8 -*- """Validator for correct file naming.""" -import pyblish.api -import openpype.api import re -from openpype.pipeline import PublishXmlValidationError +import pyblish.api + +from openpype.pipeline.publish import ( + ValidateContentsOrder, + PublishXmlValidationError, +) class ValidateSimpleUnrealTextureNaming(pyblish.api.InstancePlugin): label = "Validate Unreal Texture Names" hosts = ["standalonepublisher"] families = ["simpleUnrealTexture"] - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder regex = "^T_{asset}.*" def process(self, instance): diff --git a/openpype/hosts/standalonepublisher/plugins/publish/validate_sources.py b/openpype/hosts/standalonepublisher/plugins/publish/validate_sources.py index 316f58988f..1782f53de2 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/validate_sources.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/validate_sources.py @@ -2,8 +2,10 @@ import os import pyblish.api -import openpype.api -from openpype.pipeline import PublishXmlValidationError +from openpype.pipeline.publish import ( + ValidateContentsOrder, + PublishXmlValidationError, +) class ValidateSources(pyblish.api.InstancePlugin): @@ -13,7 +15,7 @@ class ValidateSources(pyblish.api.InstancePlugin): got deleted between starting of SP and now. """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder label = "Check source files" optional = True # only for unforeseeable cases diff --git a/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_batch.py b/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_batch.py index d66fb257bb..44f69e48f7 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_batch.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_batch.py @@ -1,7 +1,9 @@ import pyblish.api -import openpype.api -from openpype.pipeline import PublishXmlValidationError +from openpype.pipeline.publish import ( + ValidateContentsOrder, + PublishXmlValidationError, +) class ValidateTextureBatch(pyblish.api.InstancePlugin): @@ -9,7 +11,7 @@ class ValidateTextureBatch(pyblish.api.InstancePlugin): label = "Validate Texture Presence" hosts = ["standalonepublisher"] - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder families = ["texture_batch_workfile"] optional = False diff --git a/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_has_workfile.py b/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_has_workfile.py index 0e67464f59..f489d37f59 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_has_workfile.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_has_workfile.py @@ -1,7 +1,9 @@ import pyblish.api -import openpype.api -from openpype.pipeline import PublishXmlValidationError +from openpype.pipeline.publish import ( + ValidateContentsOrder, + PublishXmlValidationError, +) class ValidateTextureHasWorkfile(pyblish.api.InstancePlugin): @@ -12,7 +14,7 @@ class ValidateTextureHasWorkfile(pyblish.api.InstancePlugin): """ label = "Validate Texture Has Workfile" hosts = ["standalonepublisher"] - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder families = ["textures"] optional = True diff --git a/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_name.py b/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_name.py index 751ad917ca..22f4a0eafc 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_name.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_name.py @@ -1,14 +1,16 @@ import pyblish.api -import openpype.api -from openpype.pipeline import PublishXmlValidationError +from openpype.pipeline.publish import ( + ValidateContentsOrder, + PublishXmlValidationError, +) class ValidateTextureBatchNaming(pyblish.api.InstancePlugin): """Validates that all instances had properly formatted name.""" label = "Validate Texture Batch Naming" hosts = ["standalonepublisher"] - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder families = ["texture_batch_workfile", "textures"] optional = False diff --git a/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_versions.py b/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_versions.py index 84d9def895..dab160d537 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_versions.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_versions.py @@ -1,7 +1,9 @@ import pyblish.api -import openpype.api -from openpype.pipeline import PublishXmlValidationError +from openpype.pipeline.publish import ( + ValidateContentsOrder, + PublishXmlValidationError, +) class ValidateTextureBatchVersions(pyblish.api.InstancePlugin): @@ -14,7 +16,7 @@ class ValidateTextureBatchVersions(pyblish.api.InstancePlugin): """ label = "Validate Texture Batch Versions" hosts = ["standalonepublisher"] - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder families = ["textures"] optional = False diff --git a/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_workfiles.py b/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_workfiles.py index fa492a80d8..56ea82f6b6 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_workfiles.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_workfiles.py @@ -1,7 +1,9 @@ import pyblish.api -import openpype.api -from openpype.pipeline import PublishXmlValidationError +from openpype.pipeline.publish import ( + ValidateContentsOrder, + PublishXmlValidationError, +) class ValidateTextureBatchWorkfiles(pyblish.api.InstancePlugin): @@ -12,7 +14,7 @@ class ValidateTextureBatchWorkfiles(pyblish.api.InstancePlugin): label = "Validate Texture Workfile Has Resources" hosts = ["standalonepublisher"] - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder families = ["texture_batch_workfile"] optional = True diff --git a/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py b/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py index 947624100a..b962ea464a 100644 --- a/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py +++ b/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py @@ -2,10 +2,10 @@ import re import pyblish.api -import openpype.api -from openpype.pipeline import ( +from openpype.pipeline.publish import ( + ValidateContentsOrder, PublishXmlValidationError, - OptionalPyblishPluginMixin + OptionalPyblishPluginMixin, ) @@ -16,7 +16,7 @@ class ValidateFrameRange(OptionalPyblishPluginMixin, label = "Validate Frame Range" hosts = ["traypublisher"] families = ["render"] - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder optional = True # published data might be sequence (.mov, .mp4) in that counting files diff --git a/openpype/modules/ftrack/plugins/publish/validate_custom_ftrack_attributes.py b/openpype/modules/ftrack/plugins/publish/validate_custom_ftrack_attributes.py index dc80bf4eb3..489f291c0f 100644 --- a/openpype/modules/ftrack/plugins/publish/validate_custom_ftrack_attributes.py +++ b/openpype/modules/ftrack/plugins/publish/validate_custom_ftrack_attributes.py @@ -1,5 +1,5 @@ import pyblish.api -import openpype.api +from openpype.pipeline.publish import ValidateContentsOrder class ValidateFtrackAttributes(pyblish.api.InstancePlugin): @@ -34,7 +34,7 @@ class ValidateFtrackAttributes(pyblish.api.InstancePlugin): """ label = "Validate Custom Ftrack Attributes" - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder families = ["ftrack"] optional = True # Ignore standalone host, because it does not have an Ftrack entity diff --git a/openpype/modules/shotgrid/plugins/publish/validate_shotgrid_user.py b/openpype/modules/shotgrid/plugins/publish/validate_shotgrid_user.py index c14c980e2a..48b320e15e 100644 --- a/openpype/modules/shotgrid/plugins/publish/validate_shotgrid_user.py +++ b/openpype/modules/shotgrid/plugins/publish/validate_shotgrid_user.py @@ -1,5 +1,5 @@ import pyblish.api -import openpype.api +from openpype.pipeline.publish import ValidateContentsOrder class ValidateShotgridUser(pyblish.api.ContextPlugin): @@ -8,7 +8,7 @@ class ValidateShotgridUser(pyblish.api.ContextPlugin): """ label = "Validate Shotgrid User" - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder def process(self, context): sg = context.data.get("shotgridSession") diff --git a/openpype/plugin.py b/openpype/plugin.py index 72dca31ec9..d3605fcb1e 100644 --- a/openpype/plugin.py +++ b/openpype/plugin.py @@ -3,6 +3,9 @@ import warnings import pyblish.api +# New location of orders: openpype.pipeline.publish.constants +# - can be imported as +# 'from openpype.pipeline.publish import ValidatePipelineOrder' ValidatePipelineOrder = pyblish.api.ValidatorOrder + 0.05 ValidateContentsOrder = pyblish.api.ValidatorOrder + 0.1 ValidateSceneOrder = pyblish.api.ValidatorOrder + 0.2 diff --git a/openpype/plugins/publish/validate_resources.py b/openpype/plugins/publish/validate_resources.py index 644977ecd4..7911c70c2d 100644 --- a/openpype/plugins/publish/validate_resources.py +++ b/openpype/plugins/publish/validate_resources.py @@ -1,7 +1,6 @@ -import pyblish.api -import openpype.api - import os +import pyblish.api +from openpype.pipeline.publish import ValidateContentsOrder class ValidateResources(pyblish.api.InstancePlugin): @@ -17,7 +16,7 @@ class ValidateResources(pyblish.api.InstancePlugin): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder label = "Resources" def process(self, instance): diff --git a/openpype/plugins/publish/validate_unique_names.py b/openpype/plugins/publish/validate_unique_names.py index 459c90e6c1..33a460f7cc 100644 --- a/openpype/plugins/publish/validate_unique_names.py +++ b/openpype/plugins/publish/validate_unique_names.py @@ -3,6 +3,7 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.pipeline.publish import ValidateContentsOrder class ValidateUniqueNames(pyblish.api.Validator): @@ -12,7 +13,7 @@ class ValidateUniqueNames(pyblish.api.Validator): """ - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ["maya"] families = ["model"] label = "Unique transform name" From f29f45c3803210082b2fb8de52fac856658b34f5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Sat, 27 Aug 2022 10:09:54 +0200 Subject: [PATCH 082/274] removed duplicated plugin --- .../hosts/maya/plugins/create/create_ass0.py | 48 ------------------- 1 file changed, 48 deletions(-) delete mode 100644 openpype/hosts/maya/plugins/create/create_ass0.py diff --git a/openpype/hosts/maya/plugins/create/create_ass0.py b/openpype/hosts/maya/plugins/create/create_ass0.py deleted file mode 100644 index 4523aa02c5..0000000000 --- a/openpype/hosts/maya/plugins/create/create_ass0.py +++ /dev/null @@ -1,48 +0,0 @@ -from collections import OrderedDicta - -from openpype.hosts.maya.api import ( - lib, - plugin -) - -from maya import cmds - - -class CreateAss(plugin.Creator): - """Arnold Archive""" - - name = "ass" - label = "Ass StandIn" - family = "ass" - icon = "cube" - - def __init__(self, *args, **kwargs): - super(CreateAss, self).__init__(*args, **kwargs) - - # Add animation data - self.data.update(lib.collect_animation_data()) - - # Vertex colors with the geometry - self.data["exportSequence"] = False - - def process(self): - instance = super(CreateAss, self).process() - - # data = OrderedDict(**self.data) - - - - nodes = list() - - if (self.options or {}).get("useSelection"): - nodes = cmds.ls(selection=True) - - cmds.sets(nodes, rm=instance) - - assContent = cmds.sets(name="content_SET") - assProxy = cmds.sets(name="proxy_SET", empty=True) - cmds.sets([assContent, assProxy], forceElement=instance) - - # self.log.info(data) - # - # self.data = data From 38932ba3012668e8ca2a239ffa2b68b002d979d6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Sat, 27 Aug 2022 10:38:01 +0200 Subject: [PATCH 083/274] fixed interface name --- openpype/hosts/fusion/addon.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/fusion/addon.py b/openpype/hosts/fusion/addon.py index 97fb262517..e257005061 100644 --- a/openpype/hosts/fusion/addon.py +++ b/openpype/hosts/fusion/addon.py @@ -1,11 +1,11 @@ import os from openpype.modules import OpenPypeModule -from openpype.modules.interfaces import IHostModule +from openpype.modules.interfaces import IHostAddon FUSION_HOST_DIR = os.path.dirname(os.path.abspath(__file__)) -class FusionAddon(OpenPypeModule, IHostModule): +class FusionAddon(OpenPypeModule, IHostAddon): name = "fusion" host_name = "fusion" From 981b56d1e4f38b45b5ab809760b8ede8dedb7d89 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Sat, 27 Aug 2022 10:39:12 +0200 Subject: [PATCH 084/274] fixed interface name --- openpype/hosts/houdini/addon.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/addon.py b/openpype/hosts/houdini/addon.py index 255d6ed53f..8d88e83c56 100644 --- a/openpype/hosts/houdini/addon.py +++ b/openpype/hosts/houdini/addon.py @@ -1,11 +1,11 @@ import os from openpype.modules import OpenPypeModule -from openpype.modules.interfaces import IHostModule +from openpype.modules.interfaces import IHostAddon HOUDINI_HOST_DIR = os.path.dirname(os.path.abspath(__file__)) -class HoudiniAddon(OpenPypeModule, IHostModule): +class HoudiniAddon(OpenPypeModule, IHostAddon): name = "houdini" host_name = "houdini" From b21ac83866b375320ec3463b040f753b2a52d9d5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Sat, 27 Aug 2022 10:40:24 +0200 Subject: [PATCH 085/274] fixed innterface name --- openpype/hosts/resolve/addon.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/resolve/addon.py b/openpype/hosts/resolve/addon.py index af09448a43..a31da52a6d 100644 --- a/openpype/hosts/resolve/addon.py +++ b/openpype/hosts/resolve/addon.py @@ -1,12 +1,12 @@ import os from openpype.modules import OpenPypeModule -from openpype.modules.interfaces import IHostModule +from openpype.modules.interfaces import IHostAddon from .utils import RESOLVE_ROOT_DIR -class ResolveAddon(OpenPypeModule, IHostModule): +class ResolveAddon(OpenPypeModule, IHostAddon): name = "resolve" host_name = "resolve" From 892af6ad9e6e49e74573710d5d4b3997bf9e3716 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Sat, 27 Aug 2022 10:41:13 +0200 Subject: [PATCH 086/274] fixed interface name --- openpype/hosts/flame/addon.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/flame/addon.py b/openpype/hosts/flame/addon.py index 7e68378f4b..5a34413bb0 100644 --- a/openpype/hosts/flame/addon.py +++ b/openpype/hosts/flame/addon.py @@ -1,11 +1,11 @@ import os from openpype.modules import OpenPypeModule -from openpype.modules.interfaces import IHostModule +from openpype.modules.interfaces import IHostAddon HOST_DIR = os.path.dirname(os.path.abspath(__file__)) -class FlameAddon(OpenPypeModule, IHostModule): +class FlameAddon(OpenPypeModule, IHostAddon): name = "flame" host_name = "flame" From 2cfa25682f72d630d7efed9e7b1a27c43db86269 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 11:55:25 +0200 Subject: [PATCH 087/274] split host into host and interfaces --- openpype/host/__init__.py | 4 + openpype/host/host.py | 371 +----------------------------------- openpype/host/interfaces.py | 370 +++++++++++++++++++++++++++++++++++ 3 files changed, 375 insertions(+), 370 deletions(-) create mode 100644 openpype/host/interfaces.py diff --git a/openpype/host/__init__.py b/openpype/host/__init__.py index 84a2fa930a..77df655788 100644 --- a/openpype/host/__init__.py +++ b/openpype/host/__init__.py @@ -1,5 +1,8 @@ from .host import ( HostBase, +) + +from .interfaces import ( IWorkfileHost, ILoadHost, INewPublisher, @@ -7,6 +10,7 @@ from .host import ( __all__ = ( "HostBase", + "IWorkfileHost", "ILoadHost", "INewPublisher", diff --git a/openpype/host/host.py b/openpype/host/host.py index 9cdbb819e1..99f7868727 100644 --- a/openpype/host/host.py +++ b/openpype/host/host.py @@ -1,37 +1,12 @@ import logging import contextlib -from abc import ABCMeta, abstractproperty, abstractmethod +from abc import ABCMeta, abstractproperty import six # NOTE can't import 'typing' because of issues in Maya 2020 # - shiboken crashes on 'typing' module import -class MissingMethodsError(ValueError): - """Exception when host miss some required methods for specific workflow. - - Args: - host (HostBase): Host implementation where are missing methods. - missing_methods (list[str]): List of missing methods. - """ - - def __init__(self, host, missing_methods): - joined_missing = ", ".join( - ['"{}"'.format(item) for item in missing_methods] - ) - if isinstance(host, HostBase): - host_name = host.name - else: - try: - host_name = host.__file__.replace("\\", "/").split("/")[-3] - except Exception: - host_name = str(host) - message = ( - "Host \"{}\" miss methods {}".format(host_name, joined_missing) - ) - super(MissingMethodsError, self).__init__(message) - - @six.add_metaclass(ABCMeta) class HostBase(object): """Base of host implementation class. @@ -185,347 +160,3 @@ class HostBase(object): yield finally: pass - - -class ILoadHost: - """Implementation requirements to be able use reference of representations. - - The load plugins can do referencing even without implementation of methods - here, but switch and removement of containers would not be possible. - - Questions: - - Is list container dependency of host or load plugins? - - Should this be directly in HostBase? - - how to find out if referencing is available? - - do we need to know that? - """ - - @staticmethod - def get_missing_load_methods(host): - """Look for missing methods on "old type" host implementation. - - Method is used for validation of implemented functions related to - loading. Checks only existence of methods. - - Args: - Union[ModuleType, HostBase]: Object of host where to look for - required methods. - - Returns: - list[str]: Missing method implementations for loading workflow. - """ - - if isinstance(host, ILoadHost): - return [] - - required = ["ls"] - missing = [] - for name in required: - if not hasattr(host, name): - missing.append(name) - return missing - - @staticmethod - def validate_load_methods(host): - """Validate implemented methods of "old type" host for load workflow. - - Args: - Union[ModuleType, HostBase]: Object of host to validate. - - Raises: - MissingMethodsError: If there are missing methods on host - implementation. - """ - missing = ILoadHost.get_missing_load_methods(host) - if missing: - raise MissingMethodsError(host, missing) - - @abstractmethod - def get_containers(self): - """Retreive referenced containers from scene. - - This can be implemented in hosts where referencing can be used. - - Todo: - Rename function to something more self explanatory. - Suggestion: 'get_containers' - - Returns: - list[dict]: Information about loaded containers. - """ - - pass - - # --- Deprecated method names --- - def ls(self): - """Deprecated variant of 'get_containers'. - - Todo: - Remove when all usages are replaced. - """ - - return self.get_containers() - - -@six.add_metaclass(ABCMeta) -class IWorkfileHost: - """Implementation requirements to be able use workfile utils and tool.""" - - @staticmethod - def get_missing_workfile_methods(host): - """Look for missing methods on "old type" host implementation. - - Method is used for validation of implemented functions related to - workfiles. Checks only existence of methods. - - Args: - Union[ModuleType, HostBase]: Object of host where to look for - required methods. - - Returns: - list[str]: Missing method implementations for workfiles workflow. - """ - - if isinstance(host, IWorkfileHost): - return [] - - required = [ - "open_file", - "save_file", - "current_file", - "has_unsaved_changes", - "file_extensions", - "work_root", - ] - missing = [] - for name in required: - if not hasattr(host, name): - missing.append(name) - return missing - - @staticmethod - def validate_workfile_methods(host): - """Validate methods of "old type" host for workfiles workflow. - - Args: - Union[ModuleType, HostBase]: Object of host to validate. - - Raises: - MissingMethodsError: If there are missing methods on host - implementation. - """ - - missing = IWorkfileHost.get_missing_workfile_methods(host) - if missing: - raise MissingMethodsError(host, missing) - - @abstractmethod - def get_workfile_extensions(self): - """Extensions that can be used as save. - - Questions: - This could potentially use 'HostDefinition'. - """ - - return [] - - @abstractmethod - def save_workfile(self, dst_path=None): - """Save currently opened scene. - - Args: - dst_path (str): Where the current scene should be saved. Or use - current path if 'None' is passed. - """ - - pass - - @abstractmethod - def open_workfile(self, filepath): - """Open passed filepath in the host. - - Args: - filepath (str): Path to workfile. - """ - - pass - - @abstractmethod - def get_current_workfile(self): - """Retreive path to current opened file. - - Returns: - str: Path to file which is currently opened. - None: If nothing is opened. - """ - - return None - - def workfile_has_unsaved_changes(self): - """Currently opened scene is saved. - - Not all hosts can know if current scene is saved because the API of - DCC does not support it. - - Returns: - bool: True if scene is saved and False if has unsaved - modifications. - None: Can't tell if workfiles has modifications. - """ - - return None - - def work_root(self, session): - """Modify workdir per host. - - Default implementation keeps workdir untouched. - - Warnings: - We must handle this modification with more sofisticated way because - this can't be called out of DCC so opening of last workfile - (calculated before DCC is launched) is complicated. Also breaking - defined work template is not a good idea. - Only place where it's really used and can make sense is Maya. There - workspace.mel can modify subfolders where to look for maya files. - - Args: - session (dict): Session context data. - - Returns: - str: Path to new workdir. - """ - - return session["AVALON_WORKDIR"] - - # --- Deprecated method names --- - def file_extensions(self): - """Deprecated variant of 'get_workfile_extensions'. - - Todo: - Remove when all usages are replaced. - """ - return self.get_workfile_extensions() - - def save_file(self, dst_path=None): - """Deprecated variant of 'save_workfile'. - - Todo: - Remove when all usages are replaced. - """ - - self.save_workfile() - - def open_file(self, filepath): - """Deprecated variant of 'open_workfile'. - - Todo: - Remove when all usages are replaced. - """ - - return self.open_workfile(filepath) - - def current_file(self): - """Deprecated variant of 'get_current_workfile'. - - Todo: - Remove when all usages are replaced. - """ - - return self.get_current_workfile() - - def has_unsaved_changes(self): - """Deprecated variant of 'workfile_has_unsaved_changes'. - - Todo: - Remove when all usages are replaced. - """ - - return self.workfile_has_unsaved_changes() - - -class INewPublisher: - """Functions related to new creation system in new publisher. - - New publisher is not storing information only about each created instance - but also some global data. At this moment are data related only to context - publish plugins but that can extend in future. - """ - - @staticmethod - def get_missing_publish_methods(host): - """Look for missing methods on "old type" host implementation. - - Method is used for validation of implemented functions related to - new publish creation. Checks only existence of methods. - - Args: - Union[ModuleType, HostBase]: Host module where to look for - required methods. - - Returns: - list[str]: Missing method implementations for new publsher - workflow. - """ - - if isinstance(host, INewPublisher): - return [] - - required = [ - "get_context_data", - "update_context_data", - ] - missing = [] - for name in required: - if not hasattr(host, name): - missing.append(name) - return missing - - @staticmethod - def validate_publish_methods(host): - """Validate implemented methods of "old type" host. - - Args: - Union[ModuleType, HostBase]: Host module to validate. - - Raises: - MissingMethodsError: If there are missing methods on host - implementation. - """ - missing = INewPublisher.get_missing_publish_methods(host) - if missing: - raise MissingMethodsError(host, missing) - - @abstractmethod - def get_context_data(self): - """Get global data related to creation-publishing from workfile. - - These data are not related to any created instance but to whole - publishing context. Not saving/returning them will cause that each - reset of publishing resets all values to default ones. - - Context data can contain information about enabled/disabled publish - plugins or other values that can be filled by artist. - - Returns: - dict: Context data stored using 'update_context_data'. - """ - - pass - - @abstractmethod - def update_context_data(self, data, changes): - """Store global context data to workfile. - - Called when some values in context data has changed. - - Without storing the values in a way that 'get_context_data' would - return them will each reset of publishing cause loose of filled values - by artist. Best practice is to store values into workfile, if possible. - - Args: - data (dict): New data as are. - changes (dict): Only data that has been changed. Each value has - tuple with '(, )' value. - """ - - pass diff --git a/openpype/host/interfaces.py b/openpype/host/interfaces.py new file mode 100644 index 0000000000..cbf12b0d13 --- /dev/null +++ b/openpype/host/interfaces.py @@ -0,0 +1,370 @@ +from abc import ABCMeta, abstractmethod +import six + + +class MissingMethodsError(ValueError): + """Exception when host miss some required methods for specific workflow. + + Args: + host (HostBase): Host implementation where are missing methods. + missing_methods (list[str]): List of missing methods. + """ + + def __init__(self, host, missing_methods): + joined_missing = ", ".join( + ['"{}"'.format(item) for item in missing_methods] + ) + host_name = getattr(host, "name", None) + if not host_name: + try: + host_name = host.__file__.replace("\\", "/").split("/")[-3] + except Exception: + host_name = str(host) + message = ( + "Host \"{}\" miss methods {}".format(host_name, joined_missing) + ) + super(MissingMethodsError, self).__init__(message) + + +class ILoadHost: + """Implementation requirements to be able use reference of representations. + + The load plugins can do referencing even without implementation of methods + here, but switch and removement of containers would not be possible. + + Questions: + - Is list container dependency of host or load plugins? + - Should this be directly in HostBase? + - how to find out if referencing is available? + - do we need to know that? + """ + + @staticmethod + def get_missing_load_methods(host): + """Look for missing methods on "old type" host implementation. + + Method is used for validation of implemented functions related to + loading. Checks only existence of methods. + + Args: + Union[ModuleType, HostBase]: Object of host where to look for + required methods. + + Returns: + list[str]: Missing method implementations for loading workflow. + """ + + if isinstance(host, ILoadHost): + return [] + + required = ["ls"] + missing = [] + for name in required: + if not hasattr(host, name): + missing.append(name) + return missing + + @staticmethod + def validate_load_methods(host): + """Validate implemented methods of "old type" host for load workflow. + + Args: + Union[ModuleType, HostBase]: Object of host to validate. + + Raises: + MissingMethodsError: If there are missing methods on host + implementation. + """ + missing = ILoadHost.get_missing_load_methods(host) + if missing: + raise MissingMethodsError(host, missing) + + @abstractmethod + def get_containers(self): + """Retreive referenced containers from scene. + + This can be implemented in hosts where referencing can be used. + + Todo: + Rename function to something more self explanatory. + Suggestion: 'get_containers' + + Returns: + list[dict]: Information about loaded containers. + """ + + pass + + # --- Deprecated method names --- + def ls(self): + """Deprecated variant of 'get_containers'. + + Todo: + Remove when all usages are replaced. + """ + + return self.get_containers() + + +@six.add_metaclass(ABCMeta) +class IWorkfileHost: + """Implementation requirements to be able use workfile utils and tool.""" + + @staticmethod + def get_missing_workfile_methods(host): + """Look for missing methods on "old type" host implementation. + + Method is used for validation of implemented functions related to + workfiles. Checks only existence of methods. + + Args: + Union[ModuleType, HostBase]: Object of host where to look for + required methods. + + Returns: + list[str]: Missing method implementations for workfiles workflow. + """ + + if isinstance(host, IWorkfileHost): + return [] + + required = [ + "open_file", + "save_file", + "current_file", + "has_unsaved_changes", + "file_extensions", + "work_root", + ] + missing = [] + for name in required: + if not hasattr(host, name): + missing.append(name) + return missing + + @staticmethod + def validate_workfile_methods(host): + """Validate methods of "old type" host for workfiles workflow. + + Args: + Union[ModuleType, HostBase]: Object of host to validate. + + Raises: + MissingMethodsError: If there are missing methods on host + implementation. + """ + + missing = IWorkfileHost.get_missing_workfile_methods(host) + if missing: + raise MissingMethodsError(host, missing) + + @abstractmethod + def get_workfile_extensions(self): + """Extensions that can be used as save. + + Questions: + This could potentially use 'HostDefinition'. + """ + + return [] + + @abstractmethod + def save_workfile(self, dst_path=None): + """Save currently opened scene. + + Args: + dst_path (str): Where the current scene should be saved. Or use + current path if 'None' is passed. + """ + + pass + + @abstractmethod + def open_workfile(self, filepath): + """Open passed filepath in the host. + + Args: + filepath (str): Path to workfile. + """ + + pass + + @abstractmethod + def get_current_workfile(self): + """Retreive path to current opened file. + + Returns: + str: Path to file which is currently opened. + None: If nothing is opened. + """ + + return None + + def workfile_has_unsaved_changes(self): + """Currently opened scene is saved. + + Not all hosts can know if current scene is saved because the API of + DCC does not support it. + + Returns: + bool: True if scene is saved and False if has unsaved + modifications. + None: Can't tell if workfiles has modifications. + """ + + return None + + def work_root(self, session): + """Modify workdir per host. + + Default implementation keeps workdir untouched. + + Warnings: + We must handle this modification with more sofisticated way because + this can't be called out of DCC so opening of last workfile + (calculated before DCC is launched) is complicated. Also breaking + defined work template is not a good idea. + Only place where it's really used and can make sense is Maya. There + workspace.mel can modify subfolders where to look for maya files. + + Args: + session (dict): Session context data. + + Returns: + str: Path to new workdir. + """ + + return session["AVALON_WORKDIR"] + + # --- Deprecated method names --- + def file_extensions(self): + """Deprecated variant of 'get_workfile_extensions'. + + Todo: + Remove when all usages are replaced. + """ + return self.get_workfile_extensions() + + def save_file(self, dst_path=None): + """Deprecated variant of 'save_workfile'. + + Todo: + Remove when all usages are replaced. + """ + + self.save_workfile() + + def open_file(self, filepath): + """Deprecated variant of 'open_workfile'. + + Todo: + Remove when all usages are replaced. + """ + + return self.open_workfile(filepath) + + def current_file(self): + """Deprecated variant of 'get_current_workfile'. + + Todo: + Remove when all usages are replaced. + """ + + return self.get_current_workfile() + + def has_unsaved_changes(self): + """Deprecated variant of 'workfile_has_unsaved_changes'. + + Todo: + Remove when all usages are replaced. + """ + + return self.workfile_has_unsaved_changes() + + +class INewPublisher: + """Functions related to new creation system in new publisher. + + New publisher is not storing information only about each created instance + but also some global data. At this moment are data related only to context + publish plugins but that can extend in future. + """ + + @staticmethod + def get_missing_publish_methods(host): + """Look for missing methods on "old type" host implementation. + + Method is used for validation of implemented functions related to + new publish creation. Checks only existence of methods. + + Args: + Union[ModuleType, HostBase]: Host module where to look for + required methods. + + Returns: + list[str]: Missing method implementations for new publsher + workflow. + """ + + if isinstance(host, INewPublisher): + return [] + + required = [ + "get_context_data", + "update_context_data", + ] + missing = [] + for name in required: + if not hasattr(host, name): + missing.append(name) + return missing + + @staticmethod + def validate_publish_methods(host): + """Validate implemented methods of "old type" host. + + Args: + Union[ModuleType, HostBase]: Host module to validate. + + Raises: + MissingMethodsError: If there are missing methods on host + implementation. + """ + missing = INewPublisher.get_missing_publish_methods(host) + if missing: + raise MissingMethodsError(host, missing) + + @abstractmethod + def get_context_data(self): + """Get global data related to creation-publishing from workfile. + + These data are not related to any created instance but to whole + publishing context. Not saving/returning them will cause that each + reset of publishing resets all values to default ones. + + Context data can contain information about enabled/disabled publish + plugins or other values that can be filled by artist. + + Returns: + dict: Context data stored using 'update_context_data'. + """ + + pass + + @abstractmethod + def update_context_data(self, data, changes): + """Store global context data to workfile. + + Called when some values in context data has changed. + + Without storing the values in a way that 'get_context_data' would + return them will each reset of publishing cause loose of filled values + by artist. Best practice is to store values into workfile, if possible. + + Args: + data (dict): New data as are. + changes (dict): Only data that has been changed. Each value has + tuple with '(, )' value. + """ + + pass From 5d12773b6d53e2bc5dda2d3a890ce347941c8075 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 11:55:46 +0200 Subject: [PATCH 088/274] moved HostDirnap into openpype.host --- openpype/host/__init__.py | 5 + openpype/host/dirmap.py | 197 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 202 insertions(+) create mode 100644 openpype/host/dirmap.py diff --git a/openpype/host/__init__.py b/openpype/host/__init__.py index 77df655788..519888fce3 100644 --- a/openpype/host/__init__.py +++ b/openpype/host/__init__.py @@ -8,10 +8,15 @@ from .interfaces import ( INewPublisher, ) +from .dirmap import HostDirmap + + __all__ = ( "HostBase", "IWorkfileHost", "ILoadHost", "INewPublisher", + + "HostDirmap", ) diff --git a/openpype/host/dirmap.py b/openpype/host/dirmap.py new file mode 100644 index 0000000000..901bbb185c --- /dev/null +++ b/openpype/host/dirmap.py @@ -0,0 +1,197 @@ +import os +from abc import ABCMeta, abstractmethod + +import six + +from openpype.lib import Logger +from openpype.modules import ModulesManager +from openpype.settings import get_project_settings +from openpype.settings.lib import get_site_local_overrides + + +@six.add_metaclass(ABCMeta) +class HostDirmap(object): + """Abstract class for running dirmap on a workfile in a host. + + Dirmap is used to translate paths inside of host workfile from one + OS to another. (Eg. arstist created workfile on Win, different artists + opens same file on Linux.) + + Expects methods to be implemented inside of host: + on_dirmap_enabled: run host code for enabling dirmap + do_dirmap: run host code to do actual remapping + """ + + def __init__( + self, host_name, project_name, project_settings=None, sync_module=None + ): + self.host_name = host_name + self.project_name = project_name + self._project_settings = project_settings + self._sync_module = sync_module # to limit reinit of Modules + self._log = None + self._mapping = None # cache mapping + + @property + def sync_module(self): + if self._sync_module is None: + manager = ModulesManager() + self._sync_module = manager["sync_server"] + return self._sync_module + + @property + def project_settings(self): + if self._project_settings is None: + self._project_settings = get_project_settings(self.project_name) + return self._project_settings + + @property + def log(self): + if self._log is None: + self._log = Logger.get_logger(self.__class__.__name__) + return self._log + + @abstractmethod + def on_enable_dirmap(self): + """Run host dependent operation for enabling dirmap if necessary.""" + pass + + @abstractmethod + def dirmap_routine(self, source_path, destination_path): + """Run host dependent remapping from source_path to destination_path""" + pass + + def process_dirmap(self): + # type: (dict) -> None + """Go through all paths in Settings and set them using `dirmap`. + + If artists has Site Sync enabled, take dirmap mapping directly from + Local Settings when artist is syncing workfile locally. + + Args: + project_settings (dict): Settings for current project. + """ + + if not self._mapping: + self._mapping = self.get_mappings(self.project_settings) + if not self._mapping: + return + + self.log.info("Processing directory mapping ...") + self.on_enable_dirmap() + self.log.info("mapping:: {}".format(self._mapping)) + + for k, sp in enumerate(self._mapping["source-path"]): + dst = self._mapping["destination-path"][k] + try: + print("{} -> {}".format(sp, dst)) + self.dirmap_routine(sp, dst) + except IndexError: + # missing corresponding destination path + self.log.error(( + "invalid dirmap mapping, missing corresponding" + " destination directory." + )) + break + except RuntimeError: + self.log.error( + "invalid path {} -> {}, mapping not registered".format( + sp, dst + ) + ) + continue + + def get_mappings(self, project_settings): + """Get translation from source-path to destination-path. + + It checks if Site Sync is enabled and user chose to use local + site, in that case configuration in Local Settings takes precedence + """ + + local_mapping = self._get_local_sync_dirmap(project_settings) + dirmap_label = "{}-dirmap".format(self.host_name) + if ( + not self.project_settings[self.host_name].get(dirmap_label) + and not local_mapping + ): + return {} + mapping_settings = self.project_settings[self.host_name][dirmap_label] + mapping_enabled = mapping_settings["enabled"] or bool(local_mapping) + if not mapping_enabled: + return {} + + mapping = ( + local_mapping + or mapping_settings["paths"] + or {} + ) + + if ( + not mapping + or not mapping.get("destination-path") + or not mapping.get("source-path") + ): + return {} + return mapping + + def _get_local_sync_dirmap(self, project_settings): + """ + Returns dirmap if synch to local project is enabled. + + Only valid mapping is from roots of remote site to local site set + in Local Settings. + + Args: + project_settings (dict) + Returns: + dict : { "source-path": [XXX], "destination-path": [YYYY]} + """ + + mapping = {} + + if not project_settings["global"]["sync_server"]["enabled"]: + return mapping + + project_name = os.getenv("AVALON_PROJECT") + + active_site = self.sync_module.get_local_normalized_site( + self.sync_module.get_active_site(project_name)) + remote_site = self.sync_module.get_local_normalized_site( + self.sync_module.get_remote_site(project_name)) + self.log.debug( + "active {} - remote {}".format(active_site, remote_site) + ) + + if ( + active_site == "local" + and project_name in self.sync_module.get_enabled_projects() + and active_site != remote_site + ): + sync_settings = self.sync_module.get_sync_project_setting( + project_name, + exclude_locals=False, + cached=False) + + active_overrides = get_site_local_overrides( + project_name, active_site) + remote_overrides = get_site_local_overrides( + project_name, remote_site) + + self.log.debug("local overrides {}".format(active_overrides)) + self.log.debug("remote overrides {}".format(remote_overrides)) + for root_name, active_site_dir in active_overrides.items(): + remote_site_dir = ( + remote_overrides.get(root_name) + or sync_settings["sites"][remote_site]["root"][root_name] + ) + if os.path.isdir(active_site_dir): + if "destination-path" not in mapping: + mapping["destination-path"] = [] + mapping["destination-path"].append(active_site_dir) + + if "source-path" not in mapping: + mapping["source-path"] = [] + mapping["source-path"].append(remote_site_dir) + + self.log.debug("local sync mapping:: {}".format(mapping)) + return mapping From 761e52b2dca2d0288a1aa39c2bb95c993e42b1a1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 12:00:59 +0200 Subject: [PATCH 089/274] use new HostDirmap location in maya --- openpype/hosts/maya/api/pipeline.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index f565f6a308..c9f22fe119 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -9,14 +9,18 @@ import maya.api.OpenMaya as om import pyblish.api from openpype.settings import get_project_settings -from openpype.host import HostBase, IWorkfileHost, ILoadHost import openpype.hosts.maya +from openpype.host import ( + HostBase, + IWorkfileHost, + ILoadHost, + HostDirmap, +) from openpype.tools.utils import host_tools from openpype.lib import ( register_event_callback, emit_event ) -from openpype.lib.path_tools import HostDirmap from openpype.pipeline import ( legacy_io, register_loader_plugin_path, @@ -59,9 +63,10 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost): self._op_events = {} def install(self): - project_settings = get_project_settings(os.getenv("AVALON_PROJECT")) + project_name = os.getenv("AVALON_PROJECT") + project_settings = get_project_settings(project_name) # process path mapping - dirmap_processor = MayaDirmap("maya", project_settings) + dirmap_processor = MayaDirmap("maya", project_name, project_settings) dirmap_processor.process_dirmap() pyblish.api.register_plugin_path(PUBLISH_PATH) From fcb047770ad41364bbb9aa50ab40765fd43132cf Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 12:07:51 +0200 Subject: [PATCH 090/274] fix import in collect ftrack api --- .../modules/ftrack/plugins/publish/collect_ftrack_family.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/ftrack/plugins/publish/collect_ftrack_family.py b/openpype/modules/ftrack/plugins/publish/collect_ftrack_family.py index 5758068f86..576a7d36c4 100644 --- a/openpype/modules/ftrack/plugins/publish/collect_ftrack_family.py +++ b/openpype/modules/ftrack/plugins/publish/collect_ftrack_family.py @@ -8,7 +8,7 @@ Provides: import pyblish.api from openpype.pipeline import legacy_io -from openpype.lib.plugin_tools import filter_profiles +from openpype.lib import filter_profiles class CollectFtrackFamily(pyblish.api.InstancePlugin): From 5ad2de372a3507ed38321afe881e7414e6738051 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 12:11:30 +0200 Subject: [PATCH 091/274] use new 'get_subset_name' in creator plugins --- openpype/pipeline/create/creator_plugins.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 9e1530a6a7..bf2fdd2c5f 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -9,7 +9,7 @@ from abc import ( import six from openpype.settings import get_system_settings, get_project_settings -from openpype.lib import get_subset_name_with_asset_doc +from .subset_name import get_subset_name from openpype.pipeline.plugin_discover import ( discover, register_plugin, @@ -75,6 +75,7 @@ class BaseCreator: ): # Reference to CreateContext self.create_context = create_context + self.project_settings = project_settings # Creator is running in headless mode (without UI elemets) # - we may use UI inside processing this attribute should be checked @@ -276,14 +277,15 @@ class BaseCreator: variant, task_name, asset_doc, project_name, host_name ) - return get_subset_name_with_asset_doc( + return get_subset_name( self.family, variant, task_name, asset_doc, project_name, host_name, - dynamic_data=dynamic_data + dynamic_data=dynamic_data, + project_settings=self.project_settings ) def get_instance_attr_defs(self): From d611fdb88c9c0a4d264dd898ae693f3b49a3679c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 12:20:35 +0200 Subject: [PATCH 092/274] added docstring to dirmap --- openpype/host/dirmap.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpype/host/dirmap.py b/openpype/host/dirmap.py index 901bbb185c..88d68f27bf 100644 --- a/openpype/host/dirmap.py +++ b/openpype/host/dirmap.py @@ -1,3 +1,11 @@ +"""Dirmap functionality used in host integrations inside DCCs. + +Idea for current dirmap implementation was used from Maya where is possible to +enter source and destination roots and maya will try each found source +in referenced file replace with each destionation paths. First path which +exists is used. +""" + import os from abc import ABCMeta, abstractmethod From 8fae41c9704cee79959d9c851120a44ab089b790 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 12:20:49 +0200 Subject: [PATCH 093/274] use new HostDirmap in nuke --- openpype/hosts/nuke/api/lib.py | 54 +++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index b14f1a1eb1..69512c37f8 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -19,17 +19,19 @@ from openpype.client import ( get_last_versions, get_representations, ) -from openpype.api import ( + +from openpype.host import HostDirmap +from openpype.tools.utils import host_tools +from openpype.lib import ( + env_value_to_bool, Logger, get_version_from_path, - get_current_project_settings, -) -from openpype.tools.utils import host_tools -from openpype.lib import env_value_to_bool -from openpype.lib.path_tools import HostDirmap + ) + from openpype.settings import ( get_project_settings, get_anatomy_settings, + get_current_project_settings, ) from openpype.modules import ModulesManager from openpype.pipeline.template_data import get_template_data_with_names @@ -2651,20 +2653,16 @@ def add_scripts_gizmo(): class NukeDirmap(HostDirmap): - def __init__(self, host_name, project_settings, sync_module, file_name): + def __init__(self, file_name, *args, **kwargs): """ - Args: - host_name (str): Nuke - project_settings (dict): settings of current project - sync_module (SyncServerModule): to limit reinitialization - file_name (str): full path of referenced file from workfiles + Args: + file_name (str): full path of referenced file from workfiles + *args (tuple): Positional arguments for 'HostDirmap' class + **kwargs (dict): Keyword arguments for 'HostDirmap' class """ - self.host_name = host_name - self.project_settings = project_settings - self.file_name = file_name - self.sync_module = sync_module - self._mapping = None # cache mapping + self.file_name = file_name + super(NukeDirmap, self).__init__(*args, **kwargs) def on_enable_dirmap(self): pass @@ -2684,14 +2682,20 @@ class NukeDirmap(HostDirmap): class DirmapCache: """Caching class to get settings and sync_module easily and only once.""" + _project_name = None _project_settings = None _sync_module = None + @classmethod + def project_name(cls): + if cls._project_name is None: + cls._project_name = os.getenv("AVALON_PROJECT") + return cls._project_name + @classmethod def project_settings(cls): if cls._project_settings is None: - cls._project_settings = get_project_settings( - os.getenv("AVALON_PROJECT")) + cls._project_settings = get_project_settings(cls.project_name()) return cls._project_settings @classmethod @@ -2757,10 +2761,14 @@ def dirmap_file_name_filter(file_name): Checks project settings for potential mapping from source to dest. """ - dirmap_processor = NukeDirmap("nuke", - DirmapCache.project_settings(), - DirmapCache.sync_module(), - file_name) + + dirmap_processor = NukeDirmap( + file_name, + "nuke", + DirmapCache.project_name(), + DirmapCache.project_settings(), + DirmapCache.sync_module(), + ) dirmap_processor.process_dirmap() if os.path.exists(dirmap_processor.file_name): return dirmap_processor.file_name From 5133d9d22e32221313dacc9bf358c93a38e0fd50 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 13:16:45 +0200 Subject: [PATCH 094/274] removed 'HostDirmap' from lib --- openpype/lib/path_tools.py | 156 ------------------------------------- 1 file changed, 156 deletions(-) diff --git a/openpype/lib/path_tools.py b/openpype/lib/path_tools.py index 4f28be3302..01d947af1a 100644 --- a/openpype/lib/path_tools.py +++ b/openpype/lib/path_tools.py @@ -322,159 +322,3 @@ def create_workdir_extra_folders( fullpath = os.path.join(workdir, subfolder) if not os.path.exists(fullpath): os.makedirs(fullpath) - - -@six.add_metaclass(abc.ABCMeta) -class HostDirmap: - """ - Abstract class for running dirmap on a workfile in a host. - - Dirmap is used to translate paths inside of host workfile from one - OS to another. (Eg. arstist created workfile on Win, different artists - opens same file on Linux.) - - Expects methods to be implemented inside of host: - on_dirmap_enabled: run host code for enabling dirmap - do_dirmap: run host code to do actual remapping - """ - - def __init__(self, host_name, project_settings, sync_module=None): - self.host_name = host_name - self.project_settings = project_settings - self.sync_module = sync_module # to limit reinit of Modules - - self._mapping = None # cache mapping - - @abc.abstractmethod - def on_enable_dirmap(self): - """ - Run host dependent operation for enabling dirmap if necessary. - """ - - @abc.abstractmethod - def dirmap_routine(self, source_path, destination_path): - """ - Run host dependent remapping from source_path to destination_path - """ - - def process_dirmap(self): - # type: (dict) -> None - """Go through all paths in Settings and set them using `dirmap`. - - If artists has Site Sync enabled, take dirmap mapping directly from - Local Settings when artist is syncing workfile locally. - - Args: - project_settings (dict): Settings for current project. - - """ - if not self._mapping: - self._mapping = self.get_mappings(self.project_settings) - if not self._mapping: - return - - log.info("Processing directory mapping ...") - self.on_enable_dirmap() - log.info("mapping:: {}".format(self._mapping)) - - for k, sp in enumerate(self._mapping["source-path"]): - try: - print("{} -> {}".format(sp, - self._mapping["destination-path"][k])) - self.dirmap_routine(sp, - self._mapping["destination-path"][k]) - except IndexError: - # missing corresponding destination path - log.error(("invalid dirmap mapping, missing corresponding" - " destination directory.")) - break - except RuntimeError: - log.error("invalid path {} -> {}, mapping not registered".format( # noqa: E501 - sp, self._mapping["destination-path"][k] - )) - continue - - def get_mappings(self, project_settings): - """Get translation from source-path to destination-path. - - It checks if Site Sync is enabled and user chose to use local - site, in that case configuration in Local Settings takes precedence - """ - local_mapping = self._get_local_sync_dirmap(project_settings) - dirmap_label = "{}-dirmap".format(self.host_name) - if not self.project_settings[self.host_name].get(dirmap_label) and \ - not local_mapping: - return [] - mapping = local_mapping or \ - self.project_settings[self.host_name][dirmap_label]["paths"] or {} - enbled = self.project_settings[self.host_name][dirmap_label]["enabled"] - mapping_enabled = enbled or bool(local_mapping) - - if not mapping or not mapping_enabled or \ - not mapping.get("destination-path") or \ - not mapping.get("source-path"): - return [] - return mapping - - def _get_local_sync_dirmap(self, project_settings): - """ - Returns dirmap if synch to local project is enabled. - - Only valid mapping is from roots of remote site to local site set - in Local Settings. - - Args: - project_settings (dict) - Returns: - dict : { "source-path": [XXX], "destination-path": [YYYY]} - """ - import json - mapping = {} - - if not project_settings["global"]["sync_server"]["enabled"]: - return mapping - - from openpype.settings.lib import get_site_local_overrides - - if not self.sync_module: - from openpype.modules import ModulesManager - manager = ModulesManager() - self.sync_module = manager.modules_by_name["sync_server"] - - project_name = os.getenv("AVALON_PROJECT") - - active_site = self.sync_module.get_local_normalized_site( - self.sync_module.get_active_site(project_name)) - remote_site = self.sync_module.get_local_normalized_site( - self.sync_module.get_remote_site(project_name)) - log.debug("active {} - remote {}".format(active_site, remote_site)) - - if active_site == "local" \ - and project_name in self.sync_module.get_enabled_projects()\ - and active_site != remote_site: - - sync_settings = self.sync_module.get_sync_project_setting( - os.getenv("AVALON_PROJECT"), exclude_locals=False, - cached=False) - - active_overrides = get_site_local_overrides( - os.getenv("AVALON_PROJECT"), active_site) - remote_overrides = get_site_local_overrides( - os.getenv("AVALON_PROJECT"), remote_site) - - log.debug("local overrides".format(active_overrides)) - log.debug("remote overrides".format(remote_overrides)) - for root_name, active_site_dir in active_overrides.items(): - remote_site_dir = remote_overrides.get(root_name) or\ - sync_settings["sites"][remote_site]["root"][root_name] - if os.path.isdir(active_site_dir): - if not mapping.get("destination-path"): - mapping["destination-path"] = [] - mapping["destination-path"].append(active_site_dir) - - if not mapping.get("source-path"): - mapping["source-path"] = [] - mapping["source-path"].append(remote_site_dir) - - log.debug("local sync mapping:: {}".format(mapping)) - return mapping From ffbc3cc25e3875d812f7981a270853d5a92c004b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 13:23:52 +0200 Subject: [PATCH 095/274] hound fix --- openpype/hosts/nuke/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 69512c37f8..8721117dba 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -26,7 +26,7 @@ from openpype.lib import ( env_value_to_bool, Logger, get_version_from_path, - ) +) from openpype.settings import ( get_project_settings, From fb675ca01cbcbd7482967f56451e4375e50072b0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 13:24:42 +0200 Subject: [PATCH 096/274] reuse 'MAYA_ROOT_DIR' from public --- openpype/hosts/maya/__init__.py | 6 +++++- openpype/hosts/maya/api/pipeline.py | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/__init__.py b/openpype/hosts/maya/__init__.py index 860db766f3..bb940a881b 100644 --- a/openpype/hosts/maya/__init__.py +++ b/openpype/hosts/maya/__init__.py @@ -1,6 +1,10 @@ -from .addon import MayaAddon +from .addon import ( + MayaAddon, + MAYA_ROOT_DIR, +) __all__ = ( "MayaAddon", + "MAYA_ROOT_DIR", ) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index c9f22fe119..2401cc6aba 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -9,7 +9,6 @@ import maya.api.OpenMaya as om import pyblish.api from openpype.settings import get_project_settings -import openpype.hosts.maya from openpype.host import ( HostBase, IWorkfileHost, @@ -32,7 +31,9 @@ from openpype.pipeline import ( AVALON_CONTAINER_ID, ) from openpype.pipeline.load import any_outdated_containers +from openpype.hosts.maya import MAYA_ROOT_DIR from openpype.hosts.maya.lib import copy_workspace_mel + from . import menu, lib from .workio import ( open_file, @@ -45,8 +46,7 @@ from .workio import ( log = logging.getLogger("openpype.hosts.maya") -HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.maya.__file__)) -PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") +PLUGINS_DIR = os.path.join(MAYA_ROOT_DIR, "plugins") PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "load") CREATE_PATH = os.path.join(PLUGINS_DIR, "create") From 6f4f87418eabdf1248dbd4db29cff77ff018b0ab Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 13:31:28 +0200 Subject: [PATCH 097/274] integrate thumbnail does not require 'AVALON_THUMBNAIL_ROOT' to be set if template does not use it --- .../plugins/publish/integrate_thumbnail.py | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/openpype/plugins/publish/integrate_thumbnail.py b/openpype/plugins/publish/integrate_thumbnail.py index 8ae0dd2d60..445c563d27 100644 --- a/openpype/plugins/publish/integrate_thumbnail.py +++ b/openpype/plugins/publish/integrate_thumbnail.py @@ -6,10 +6,9 @@ import copy import six import pyblish.api -from bson.objectid import ObjectId from openpype.client import get_version_by_id -from openpype.pipeline import legacy_io +from openpype.client.operations import OperationsSession, new_thumbnail_doc class IntegrateThumbnails(pyblish.api.InstancePlugin): @@ -24,13 +23,9 @@ class IntegrateThumbnails(pyblish.api.InstancePlugin): ] def process(self, instance): - - if not os.environ.get("AVALON_THUMBNAIL_ROOT"): - self.log.warning( - "AVALON_THUMBNAIL_ROOT is not set." - " Skipping thumbnail integration." - ) - return + env_key = "AVALON_THUMBNAIL_ROOT" + thumbnail_root_format_key = "{thumbnail_root}" + thumbnail_root = os.environ.get(env_key) or "" published_repres = instance.data.get("published_representations") if not published_repres: @@ -51,6 +46,16 @@ class IntegrateThumbnails(pyblish.api.InstancePlugin): ).format(project_name)) return + thumbnail_template = anatomy.templates["publish"]["thumbnail"] + if ( + not thumbnail_root + and thumbnail_root_format_key in thumbnail_template + ): + self.log.warning(( + "{} is not set. Skipping thumbnail integration." + ).format(env_key)) + return + thumb_repre = None thumb_repre_anatomy_data = None for repre_info in published_repres.values(): From 503d64ec11be7c9af86992df3ffbe6a14534d97f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 13:31:57 +0200 Subject: [PATCH 098/274] thumbnail resolver does not need to have 'AVALON_THUMBNAIL_ROOT' set if thumbnail template does not need it --- openpype/pipeline/thumbnail.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/openpype/pipeline/thumbnail.py b/openpype/pipeline/thumbnail.py index eb383b16d9..5530d29614 100644 --- a/openpype/pipeline/thumbnail.py +++ b/openpype/pipeline/thumbnail.py @@ -73,19 +73,20 @@ class ThumbnailResolver(object): class TemplateResolver(ThumbnailResolver): - priority = 90 def process(self, thumbnail_entity, thumbnail_type): - - if not os.environ.get("AVALON_THUMBNAIL_ROOT"): - return - template = thumbnail_entity["data"].get("template") if not template: self.log.debug("Thumbnail entity does not have set template") return + thumbnail_root_format_key = "{thumbnail_root}" + thumbnail_root = os.environ.get("AVALON_THUMBNAIL_ROOT") or "" + # Check if template require thumbnail root and if is avaiable + if thumbnail_root_format_key in template and not thumbnail_root: + return + project_name = self.dbcon.active_project() project = get_project(project_name, fields=["name", "data.code"]) @@ -95,7 +96,7 @@ class TemplateResolver(ThumbnailResolver): template_data.update({ "_id": str(thumbnail_entity["_id"]), "thumbnail_type": thumbnail_type, - "thumbnail_root": os.environ.get("AVALON_THUMBNAIL_ROOT"), + "thumbnail_root": thumbnail_root, "project": { "name": project["name"], "code": project["data"].get("code") From 88d914811647427e52d86b5b99a0eb1afd8f1b6c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 13:32:12 +0200 Subject: [PATCH 099/274] added creation of new thumbnail document into operations --- openpype/client/operations.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/openpype/client/operations.py b/openpype/client/operations.py index c0716ee109..9daaa3e116 100644 --- a/openpype/client/operations.py +++ b/openpype/client/operations.py @@ -24,6 +24,7 @@ CURRENT_SUBSET_SCHEMA = "openpype:subset-3.0" CURRENT_VERSION_SCHEMA = "openpype:version-3.0" CURRENT_REPRESENTATION_SCHEMA = "openpype:representation-2.0" CURRENT_WORKFILE_INFO_SCHEMA = "openpype:workfile-1.0" +CURRENT_THUMBNAIL_SCHEMA = "openpype:thumbnail-1.0" def _create_or_convert_to_mongo_id(mongo_id): @@ -195,6 +196,29 @@ def new_representation_doc( } +def new_thumbnail_doc(data=None, entity_id=None): + """Create skeleton data of thumbnail document. + + Args: + data (Dict[str, Any]): Thumbnail document data. + entity_id (Union[str, ObjectId]): Predefined id of document. New id is + created if not passed. + + Returns: + Dict[str, Any]: Skeleton of thumbnail document. + """ + + if data is None: + data = {} + + return { + "_id": _create_or_convert_to_mongo_id(entity_id), + "type": "thumbnail", + "schema": CURRENT_THUMBNAIL_SCHEMA, + "data": data + } + + def new_workfile_info_doc( filename, asset_id, task_name, files, data=None, entity_id=None ): From 46553deec9cdb85937b298ba7aa6e1482b5aa673 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 13:32:23 +0200 Subject: [PATCH 100/274] use perations in integrate thumbnail --- .../plugins/publish/integrate_thumbnail.py | 55 ++++++++++--------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/openpype/plugins/publish/integrate_thumbnail.py b/openpype/plugins/publish/integrate_thumbnail.py index 445c563d27..d86cec10ad 100644 --- a/openpype/plugins/publish/integrate_thumbnail.py +++ b/openpype/plugins/publish/integrate_thumbnail.py @@ -71,10 +71,6 @@ class IntegrateThumbnails(pyblish.api.InstancePlugin): ) return - legacy_io.install() - - thumbnail_template = anatomy.templates["publish"]["thumbnail"] - version = get_version_by_id(project_name, thumb_repre["parent"]) if not version: raise AssertionError( @@ -93,14 +89,15 @@ class IntegrateThumbnails(pyblish.api.InstancePlugin): filename, file_extension = os.path.splitext(src_full_path) # Create id for mongo entity now to fill anatomy template - thumbnail_id = ObjectId() + thumbnail_doc = new_thumbnail_doc() + thumbnail_id = thumbnail_doc["_id"] # Prepare anatomy template fill data template_data = copy.deepcopy(thumb_repre_anatomy_data) template_data.update({ "_id": str(thumbnail_id), - "thumbnail_root": os.environ.get("AVALON_THUMBNAIL_ROOT"), "ext": file_extension[1:], + "thumbnail_root": thumbnail_root, "thumbnail_type": "thumbnail" }) @@ -122,8 +119,8 @@ class IntegrateThumbnails(pyblish.api.InstancePlugin): shutil.copy(src_full_path, dst_full_path) # Clean template data from keys that are dynamic - template_data.pop("_id") - template_data.pop("thumbnail_root") + for key in ("_id", "thumbnail_root"): + template_data.pop(key, None) repre_context = template_filled.used_values for key in self.required_context_keys: @@ -132,34 +129,40 @@ class IntegrateThumbnails(pyblish.api.InstancePlugin): continue repre_context[key] = template_data[key] - thumbnail_entity = { - "_id": thumbnail_id, - "type": "thumbnail", - "schema": "openpype:thumbnail-1.0", - "data": { - "template": thumbnail_template, - "template_data": repre_context - } + op_session = OperationsSession() + + thumbnail_doc["data"] = { + "template": thumbnail_template, + "template_data": repre_context } - # Create thumbnail entity - legacy_io.insert_one(thumbnail_entity) - self.log.debug( - "Creating entity in database {}".format(str(thumbnail_entity)) + op_session.create_entity( + project_name, thumbnail_doc["type"], thumbnail_doc ) + # Create thumbnail entity + self.log.debug( + "Creating entity in database {}".format(str(thumbnail_doc)) + ) + # Set thumbnail id for version - legacy_io.update_many( - {"_id": version["_id"]}, - {"$set": {"data.thumbnail_id": thumbnail_id}} + op_session.update_entity( + project_name, + version["type"], + version["_id"], + {"data.thumbnail_id": thumbnail_id} ) self.log.debug("Setting thumbnail for version \"{}\" <{}>".format( version["name"], str(version["_id"]) )) asset_entity = instance.data["assetEntity"] - legacy_io.update_many( - {"_id": asset_entity["_id"]}, - {"$set": {"data.thumbnail_id": thumbnail_id}} + op_session.update_entity( + project_name, + asset_entity["type"], + asset_entity["_id"], + {"data.thumbnail_id": thumbnail_id} ) self.log.debug("Setting thumbnail for asset \"{}\" <{}>".format( asset_entity["name"], str(version["_id"]) )) + + op_session.commit() From f56658737a9abc52ede2f971a136d44e749c4771 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 13:35:50 +0200 Subject: [PATCH 101/274] use also anatomy roots --- openpype/pipeline/thumbnail.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/thumbnail.py b/openpype/pipeline/thumbnail.py index 5530d29614..d95f5e35c9 100644 --- a/openpype/pipeline/thumbnail.py +++ b/openpype/pipeline/thumbnail.py @@ -4,6 +4,7 @@ import logging from openpype.client import get_project from . import legacy_io +from .anatomy import Anatomy from .plugin_discover import ( discover, register_plugin, @@ -89,6 +90,7 @@ class TemplateResolver(ThumbnailResolver): project_name = self.dbcon.active_project() project = get_project(project_name, fields=["name", "data.code"]) + anatomy = Anatomy(project_name) template_data = copy.deepcopy( thumbnail_entity["data"].get("template_data") or {} @@ -100,7 +102,8 @@ class TemplateResolver(ThumbnailResolver): "project": { "name": project["name"], "code": project["data"].get("code") - } + }, + "root": anatomy.roots }) try: From f99d9d3d77149b084b75ebf3a5621bf49c4eb9b6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 13:36:58 +0200 Subject: [PATCH 102/274] use project anatomy if needed --- openpype/pipeline/thumbnail.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/thumbnail.py b/openpype/pipeline/thumbnail.py index d95f5e35c9..39f3e17893 100644 --- a/openpype/pipeline/thumbnail.py +++ b/openpype/pipeline/thumbnail.py @@ -90,7 +90,6 @@ class TemplateResolver(ThumbnailResolver): project_name = self.dbcon.active_project() project = get_project(project_name, fields=["name", "data.code"]) - anatomy = Anatomy(project_name) template_data = copy.deepcopy( thumbnail_entity["data"].get("template_data") or {} @@ -103,8 +102,11 @@ class TemplateResolver(ThumbnailResolver): "name": project["name"], "code": project["data"].get("code") }, - "root": anatomy.roots }) + # Add anatomy roots if is in template + if "{root" in template: + anatomy = Anatomy(project_name) + template_data["root"] = anatomy.roots try: filepath = os.path.normpath(template.format(**template_data)) From 3618e8f856859106714bc9c550af7ac8aac9f8c6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 14:10:23 +0200 Subject: [PATCH 103/274] create formatting function for file sizes 'format_file_size' --- openpype/lib/path_tools.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/openpype/lib/path_tools.py b/openpype/lib/path_tools.py index 4f28be3302..f807917f5b 100644 --- a/openpype/lib/path_tools.py +++ b/openpype/lib/path_tools.py @@ -14,6 +14,27 @@ from .profiles_filtering import filter_profiles log = logging.getLogger(__name__) +def format_file_size(file_size, suffix=None): + """Returns formatted string with size in appropriate unit. + + Args: + file_size (int): Size of file in bytes. + suffix (str): Suffix for formatted size. Default is 'B' (as bytes). + + Returns: + str: Formatted size using proper unit and passed suffix (e.g. 7 MiB). + """ + + if suffix is None: + suffix = "B" + + for unit in ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"]: + if abs(file_size) < 1024.0: + return "%3.1f%s%s" % (file_size, unit, suffix) + file_size /= 1024.0 + return "%.1f%s%s" % (file_size, "Yi", suffix) + + def create_hard_link(src_path, dst_path): """Create hardlink of file. From 6398f021092d2de440218acec0d8a024aa55d75d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 14:10:48 +0200 Subject: [PATCH 104/274] copied function to collect frames 'collect_frames' --- openpype/lib/path_tools.py | 39 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/openpype/lib/path_tools.py b/openpype/lib/path_tools.py index f807917f5b..45aa54d6cb 100644 --- a/openpype/lib/path_tools.py +++ b/openpype/lib/path_tools.py @@ -6,6 +6,8 @@ import logging import six import platform +import clique + from openpype.client import get_project from openpype.settings import get_project_settings @@ -71,6 +73,43 @@ def create_hard_link(src_path, dst_path): ) +def collect_frames(files): + """Returns dict of source path and its frame, if from sequence + + Uses clique as most precise solution, used when anatomy template that + created files is not known. + + Assumption is that frames are separated by '.', negative frames are not + allowed. + + Args: + files(list) or (set with single value): list of source paths + + Returns: + (dict): {'/asset/subset_v001.0001.png': '0001', ....} + """ + + patterns = [clique.PATTERNS["frames"]] + collections, remainder = clique.assemble( + files, minimum_items=1, patterns=patterns) + + sources_and_frames = {} + if collections: + for collection in collections: + src_head = collection.head + src_tail = collection.tail + + for index in collection.indexes: + src_frame = collection.format("{padding}") % index + src_file_name = "{}{}{}".format( + src_head, src_frame, src_tail) + sources_and_frames[src_file_name] = src_frame + else: + sources_and_frames[remainder.pop()] = None + + return sources_and_frames + + def _rreplace(s, a, b, n=1): """Replace a with b in string s from right side n times.""" return b.join(s.rsplit(a, n)) From c26119cc9f6a9fe4c330842f9dddbf7865a63425 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 14:13:08 +0200 Subject: [PATCH 105/274] use new functions in code --- openpype/lib/__init__.py | 4 ++++ .../publish/submit_aftereffects_deadline.py | 6 ++++-- .../validate_expected_and_rendered_files.py | 2 +- .../action_delete_old_versions.py | 17 +++++++---------- openpype/plugins/load/delete_old_versions.py | 11 ++--------- openpype/plugins/load/delivery.py | 11 +++++++---- 6 files changed, 25 insertions(+), 26 deletions(-) diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py index adb857a056..17aafc3e8b 100644 --- a/openpype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -192,6 +192,8 @@ from .plugin_tools import ( ) from .path_tools import ( + format_file_size, + collect_frames, create_hard_link, version_up, get_version_from_path, @@ -353,6 +355,8 @@ __all__ = [ "set_plugin_attributes_from_settings", "source_hash", + "format_file_size", + "collect_frames", "create_hard_link", "version_up", "get_version_from_path", diff --git a/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py b/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py index c55f85c8da..1d68793d53 100644 --- a/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py @@ -3,8 +3,10 @@ import attr import getpass import pyblish.api -from openpype.lib import env_value_to_bool -from openpype.lib.delivery import collect_frames +from openpype.lib import ( + env_value_to_bool, + collect_frames, +) from openpype.pipeline import legacy_io from openpype_modules.deadline import abstract_submit_deadline from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo diff --git a/openpype/modules/deadline/plugins/publish/validate_expected_and_rendered_files.py b/openpype/modules/deadline/plugins/publish/validate_expected_and_rendered_files.py index c2426e0d78..f0a3ddd246 100644 --- a/openpype/modules/deadline/plugins/publish/validate_expected_and_rendered_files.py +++ b/openpype/modules/deadline/plugins/publish/validate_expected_and_rendered_files.py @@ -3,7 +3,7 @@ import requests import pyblish.api -from openpype.lib.delivery import collect_frames +from openpype.lib import collect_frames from openpype_modules.deadline.abstract_submit_deadline import requests_get diff --git a/openpype/modules/ftrack/event_handlers_user/action_delete_old_versions.py b/openpype/modules/ftrack/event_handlers_user/action_delete_old_versions.py index 79d04a7854..c543dc8834 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delete_old_versions.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delete_old_versions.py @@ -11,7 +11,11 @@ from openpype.client import ( get_versions, get_representations ) -from openpype.lib import StringTemplate, TemplateUnsolved +from openpype.lib import ( + StringTemplate, + TemplateUnsolved, + format_file_size, +) from openpype.pipeline import AvalonMongoDB, Anatomy from openpype_modules.ftrack.lib import BaseAction, statics_icon @@ -134,13 +138,6 @@ class DeleteOldVersions(BaseAction): "title": self.inteface_title } - def sizeof_fmt(self, num, suffix='B'): - for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']: - if abs(num) < 1024.0: - return "%3.1f%s%s" % (num, unit, suffix) - num /= 1024.0 - return "%.1f%s%s" % (num, 'Yi', suffix) - def launch(self, session, entities, event): values = event["data"].get("values") if not values: @@ -359,7 +356,7 @@ class DeleteOldVersions(BaseAction): dir_paths, file_paths_by_dir, delete=False ) - msg = "Total size of files: " + self.sizeof_fmt(size) + msg = "Total size of files: {}".format(format_file_size(size)) self.log.warning(msg) @@ -430,7 +427,7 @@ class DeleteOldVersions(BaseAction): "message": msg } - msg = "Total size of files deleted: " + self.sizeof_fmt(size) + msg = "Total size of files deleted: {}".format(format_file_size(size)) self.log.warning(msg) diff --git a/openpype/plugins/load/delete_old_versions.py b/openpype/plugins/load/delete_old_versions.py index 6e0b464cc1..ce6f204c64 100644 --- a/openpype/plugins/load/delete_old_versions.py +++ b/openpype/plugins/load/delete_old_versions.py @@ -10,7 +10,7 @@ from Qt import QtWidgets, QtCore from openpype.client import get_versions, get_representations from openpype import style from openpype.pipeline import load, AvalonMongoDB, Anatomy -from openpype.lib import StringTemplate +from openpype.lib import StringTemplate, format_file_size from openpype.modules import ModulesManager @@ -38,13 +38,6 @@ class DeleteOldVersions(load.SubsetLoaderPlugin): ) ] - def sizeof_fmt(self, num, suffix='B'): - for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']: - if abs(num) < 1024.0: - return "%3.1f%s%s" % (num, unit, suffix) - num /= 1024.0 - return "%.1f%s%s" % (num, 'Yi', suffix) - def delete_whole_dir_paths(self, dir_paths, delete=True): size = 0 @@ -456,7 +449,7 @@ class DeleteOldVersions(load.SubsetLoaderPlugin): size += self.main(project_name, data, remove_publish_folder) print("Progressing {}/{}".format(count + 1, len(contexts))) - msg = "Total size of files: " + self.sizeof_fmt(size) + msg = "Total size of files: {}".format(format_file_size(size)) self.log.info(msg) self.message(msg) diff --git a/openpype/plugins/load/delivery.py b/openpype/plugins/load/delivery.py index f6e1d4f06b..2a9f25e0fb 100644 --- a/openpype/plugins/load/delivery.py +++ b/openpype/plugins/load/delivery.py @@ -7,15 +7,17 @@ from openpype.client import get_representations from openpype.pipeline import load, Anatomy from openpype import resources, style +from openpype.lib import ( + format_file_size, + collect_frames, +) from openpype.lib.dateutils import get_datetime_data from openpype.lib.delivery import ( - sizeof_fmt, path_from_representation, get_format_dict, check_destination_path, process_single_file, process_sequence, - collect_frames ) @@ -263,8 +265,9 @@ class DeliveryOptionsDialog(QtWidgets.QDialog): def _prepare_label(self): """Provides text with no of selected files and their size.""" - label = "{} files, size {}".format(self.files_selected, - sizeof_fmt(self.size_selected)) + label = "{} files, size {}".format( + self.files_selected, + format_file_size(self.size_selected)) return label def _get_selected_repres(self): From aeb30b3101c31f8965e80cf40287f5e0d4e4dfe9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 14:13:21 +0200 Subject: [PATCH 106/274] marked functions in delivery as deprecated --- openpype/lib/delivery.py | 78 ++++++++++++++++++++++++++++------------ 1 file changed, 55 insertions(+), 23 deletions(-) diff --git a/openpype/lib/delivery.py b/openpype/lib/delivery.py index ffcfe9fa4d..5244187354 100644 --- a/openpype/lib/delivery.py +++ b/openpype/lib/delivery.py @@ -4,6 +4,8 @@ import shutil import glob import clique import collections +import functools +import warnings from .path_templates import ( StringTemplate, @@ -11,6 +13,52 @@ from .path_templates import ( ) +class DeliveryDeprecatedWarning(DeprecationWarning): + pass + + +def deprecated(new_destination): + """Mark functions as deprecated. + + It will result in a warning being emitted when the function is used. + """ + + func = None + if callable(new_destination): + func = new_destination + new_destination = None + + def _decorator(decorated_func): + if new_destination is None: + warning_message = ( + " Please check content of deprecated function to figure out" + " possible replacement." + ) + else: + warning_message = " Please replace your usage with '{}'.".format( + new_destination + ) + + @functools.wraps(decorated_func) + def wrapper(*args, **kwargs): + warnings.simplefilter("always", DeliveryDeprecatedWarning) + warnings.warn( + ( + "Call to deprecated function '{}'" + "\nFunction was moved or removed.{}" + ).format(decorated_func.__name__, warning_message), + category=DeliveryDeprecatedWarning, + stacklevel=4 + ) + return decorated_func(*args, **kwargs) + return wrapper + + if func is None: + return _decorator + return _decorator(func) + + +@deprecated("openpype.lib.path_tools.collect_frames") def collect_frames(files): """ Returns dict of source path and its frame, if from sequence @@ -26,34 +74,18 @@ def collect_frames(files): Returns: (dict): {'/asset/subset_v001.0001.png': '0001', ....} """ - patterns = [clique.PATTERNS["frames"]] - collections, remainder = clique.assemble(files, minimum_items=1, - patterns=patterns) - sources_and_frames = {} - if collections: - for collection in collections: - src_head = collection.head - src_tail = collection.tail + from .path_tools import collect_frames - for index in collection.indexes: - src_frame = collection.format("{padding}") % index - src_file_name = "{}{}{}".format(src_head, src_frame, - src_tail) - sources_and_frames[src_file_name] = src_frame - else: - sources_and_frames[remainder.pop()] = None - - return sources_and_frames + return collect_frames(files) -def sizeof_fmt(num, suffix='B'): +@deprecated("openpype.lib.path_tools.format_file_size") +def sizeof_fmt(num, suffix=None): """Returns formatted string with size in appropriate unit""" - for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']: - if abs(num) < 1024.0: - return "%3.1f%s%s" % (num, unit, suffix) - num /= 1024.0 - return "%.1f%s%s" % (num, 'Yi', suffix) + + from .path_tools import format_file_size + return format_file_size(num, suffix) def path_from_representation(representation, anatomy): From d58ea894159cb1190fb5bcad5cdf4e949adf39f7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 14:26:23 +0200 Subject: [PATCH 107/274] implemented 'get_representation_path_with_anatomy'. --- openpype/pipeline/load/__init__.py | 5 +++ openpype/pipeline/load/utils.py | 62 +++++++++++++++++++++++++++--- 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/openpype/pipeline/load/__init__.py b/openpype/pipeline/load/__init__.py index b6bdd13d50..4fc8ad1d16 100644 --- a/openpype/pipeline/load/__init__.py +++ b/openpype/pipeline/load/__init__.py @@ -1,6 +1,8 @@ from .utils import ( HeroVersionType, + IncompatibleLoaderError, + InvalidRepresentationContext, get_repres_contexts, get_subset_contexts, @@ -20,6 +22,7 @@ from .utils import ( get_representation_path_from_context, get_representation_path, + get_representation_path_with_anatomy, is_compatible_loader, @@ -46,7 +49,9 @@ from .plugins import ( __all__ = ( # utils.py "HeroVersionType", + "IncompatibleLoaderError", + "InvalidRepresentationContext", "get_repres_contexts", "get_subset_contexts", diff --git a/openpype/pipeline/load/utils.py b/openpype/pipeline/load/utils.py index 99d6876d4b..d4a5c2be5a 100644 --- a/openpype/pipeline/load/utils.py +++ b/openpype/pipeline/load/utils.py @@ -23,10 +23,16 @@ from openpype.client import ( get_representation_by_name, get_representation_parents ) +from openpype.lib import ( + StringTemplate, + TemplateUnsolved, +) from openpype.pipeline import ( schema, legacy_io, Anatomy, + registered_root, + registered_host, ) log = logging.getLogger(__name__) @@ -61,6 +67,11 @@ class IncompatibleLoaderError(ValueError): pass +class InvalidRepresentationContext(ValueError): + """Representation path can't be received using representation document.""" + pass + + def get_repres_contexts(representation_ids, dbcon=None): """Return parenthood context for representation. @@ -515,6 +526,52 @@ def get_representation_path_from_context(context): return get_representation_path(representation, root) +def get_representation_path_with_anatomy(repre_doc, anatomy): + """Receive representation path using representation document and anatomy. + + Anatomy is used to replace 'root' key in representation file. Ideally + should be used instead of 'get_representation_path' which is based on + "current context". + + Future notes: + We want also be able store resources into representation and I can + imagine the result should also contain paths to possible resources. + + Args: + repre_doc (Dict[str, Any]): Representation document. + anatomy (Anatomy): Project anatomy object. + + Returns: + Union[None, TemplateResult]: None if path can't be received + + Raises: + InvalidRepresentationContext: When representation data are probably + invalid or not available. + """ + + try: + template = repre_doc["data"]["template"] + + except KeyError: + raise InvalidRepresentationContext(( + "Representation document does not" + " contain template in data ('data.template')" + )) + + try: + context = repre_doc["context"] + context["root"] = anatomy.roots + path = StringTemplate.format_strict_template(template, context) + + except TemplateUnsolved as exc: + raise InvalidRepresentationContext(( + "Couldn't resolve representation template with available data." + " Reason: {}".format(str(exc)) + )) + + return path.normalized() + + def get_representation_path(representation, root=None, dbcon=None): """Get filename from representation document @@ -533,14 +590,10 @@ def get_representation_path(representation, root=None, dbcon=None): """ - from openpype.lib import StringTemplate, TemplateUnsolved - if dbcon is None: dbcon = legacy_io if root is None: - from openpype.pipeline import registered_root - root = registered_root() def path_from_represenation(): @@ -736,7 +789,6 @@ def get_outdated_containers(host=None, project_name=None): """ if host is None: - from openpype.pipeline import registered_host host = registered_host() if project_name is None: From 315cf40d8baa47bb3a4f6864e49551bdfc6d196b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 14:29:43 +0200 Subject: [PATCH 108/274] fixed import in load utils --- openpype/pipeline/load/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/load/utils.py b/openpype/pipeline/load/utils.py index d4a5c2be5a..83b904e4a7 100644 --- a/openpype/pipeline/load/utils.py +++ b/openpype/pipeline/load/utils.py @@ -31,8 +31,6 @@ from openpype.pipeline import ( schema, legacy_io, Anatomy, - registered_root, - registered_host, ) log = logging.getLogger(__name__) @@ -594,6 +592,8 @@ def get_representation_path(representation, root=None, dbcon=None): dbcon = legacy_io if root is None: + from openpype.pipeline import registered_root + root = registered_root() def path_from_represenation(): @@ -789,6 +789,8 @@ def get_outdated_containers(host=None, project_name=None): """ if host is None: + from openpype.pipeline import registered_host + host = registered_host() if project_name is None: From f2a191861b9264383da3c0b63ed8f4feac629a1c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 14:41:28 +0200 Subject: [PATCH 109/274] marked 'path_from_representation' as deprecated and replaced it's usage with 'get_representation_path_with_anatomy' --- openpype/lib/delivery.py | 29 +++++-------- .../event_handlers_user/action_delivery.py | 6 +-- openpype/pipeline/load/__init__.py | 1 + openpype/plugins/load/delete_old_versions.py | 41 +++++++++++-------- openpype/plugins/load/delivery.py | 8 ++-- 5 files changed, 43 insertions(+), 42 deletions(-) diff --git a/openpype/lib/delivery.py b/openpype/lib/delivery.py index 5244187354..ea757932c9 100644 --- a/openpype/lib/delivery.py +++ b/openpype/lib/delivery.py @@ -7,11 +7,6 @@ import collections import functools import warnings -from .path_templates import ( - StringTemplate, - TemplateUnsolved, -) - class DeliveryDeprecatedWarning(DeprecationWarning): pass @@ -88,24 +83,22 @@ def sizeof_fmt(num, suffix=None): return format_file_size(num, suffix) +@deprecated("openpype.pipeline.load.get_representation_path_with_anatomy") def path_from_representation(representation, anatomy): - try: - template = representation["data"]["template"] + """Get representation path using representation document and anatomy. - except KeyError: - return None + Args: + representation (Dict[str, Any]): Representation document. + anatomy (Anatomy): Project anatomy. - try: - context = representation["context"] - context["root"] = anatomy.roots - path = StringTemplate.format_strict_template(template, context) - return os.path.normpath(path) + Deprecated: + Function was moved to different location and will be removed + after 3.16.* release. + """ - except TemplateUnsolved: - # Template references unavailable data - return None + from openpype.pipeline.load import get_representation_path_with_anatomy - return path + return get_representation_path_with_anatomy(representation, anatomy) def copy_file(src_path, dst_path): diff --git a/openpype/modules/ftrack/event_handlers_user/action_delivery.py b/openpype/modules/ftrack/event_handlers_user/action_delivery.py index eec245070c..59a34b3f85 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delivery.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delivery.py @@ -10,15 +10,15 @@ from openpype.client import ( get_versions, get_representations ) -from openpype.pipeline import Anatomy from openpype_modules.ftrack.lib import BaseAction, statics_icon from openpype_modules.ftrack.lib.avalon_sync import CUST_ATTR_ID_KEY from openpype_modules.ftrack.lib.custom_attributes import ( query_custom_attributes ) from openpype.lib.dateutils import get_datetime_data +from openpype.pipeline import Anatomy +from openpype.pipeline.load import get_representation_path_with_anatomy from openpype.lib.delivery import ( - path_from_representation, get_format_dict, check_destination_path, process_single_file, @@ -580,7 +580,7 @@ class Delivery(BaseAction): if frame: repre["context"]["frame"] = len(str(frame)) * "#" - repre_path = path_from_representation(repre, anatomy) + repre_path = get_representation_path_with_anatomy(repre, anatomy) # TODO add backup solution where root of path from component # is replaced with root args = ( diff --git a/openpype/pipeline/load/__init__.py b/openpype/pipeline/load/__init__.py index 4fc8ad1d16..bf38a0b3c8 100644 --- a/openpype/pipeline/load/__init__.py +++ b/openpype/pipeline/load/__init__.py @@ -71,6 +71,7 @@ __all__ = ( "get_representation_path_from_context", "get_representation_path", + "get_representation_path_with_anatomy", "is_compatible_loader", diff --git a/openpype/plugins/load/delete_old_versions.py b/openpype/plugins/load/delete_old_versions.py index ce6f204c64..8c8546d9c8 100644 --- a/openpype/plugins/load/delete_old_versions.py +++ b/openpype/plugins/load/delete_old_versions.py @@ -7,11 +7,15 @@ from pymongo import UpdateOne import qargparse from Qt import QtWidgets, QtCore -from openpype.client import get_versions, get_representations from openpype import style -from openpype.pipeline import load, AvalonMongoDB, Anatomy -from openpype.lib import StringTemplate, format_file_size +from openpype.client import get_versions, get_representations from openpype.modules import ModulesManager +from openpype.lib import StringTemplate, format_file_size +from openpype.pipeline import load, AvalonMongoDB, Anatomy +from openpype.pipeline.load import ( + get_representation_path_with_anatomy, + InvalidRepresentationContext, +) class DeleteOldVersions(load.SubsetLoaderPlugin): @@ -73,27 +77,28 @@ class DeleteOldVersions(load.SubsetLoaderPlugin): def path_from_representation(self, representation, anatomy): try: - template = representation["data"]["template"] - + context = representation["context"] except KeyError: return (None, None) + try: + path = get_representation_path_with_anatomy( + representation, anatomy + ) + except InvalidRepresentationContext: + return (None, None) + sequence_path = None - try: - context = representation["context"] - context["root"] = anatomy.roots - path = str(StringTemplate.format_template(template, context)) - if "frame" in context: - context["frame"] = self.sequence_splitter - sequence_path = os.path.normpath(str( - StringTemplate.format_template(template, context) - )) + if "frame" in context: + context["frame"] = self.sequence_splitter + sequence_path = get_representation_path_with_anatomy( + representation, anatomy + ) - except KeyError: - # Template references unavailable data - return (None, None) + if sequence_path: + sequence_path = sequence_path.normalized() - return (os.path.normpath(path), sequence_path) + return (path.normalized(), sequence_path) def delete_only_repre_files(self, dir_paths, file_paths, delete=True): size = 0 diff --git a/openpype/plugins/load/delivery.py b/openpype/plugins/load/delivery.py index 2a9f25e0fb..4651efd4a3 100644 --- a/openpype/plugins/load/delivery.py +++ b/openpype/plugins/load/delivery.py @@ -10,10 +10,10 @@ from openpype import resources, style from openpype.lib import ( format_file_size, collect_frames, + get_datetime_data, ) -from openpype.lib.dateutils import get_datetime_data +from openpype.pipeline.load import get_representation_path_with_anatomy from openpype.lib.delivery import ( - path_from_representation, get_format_dict, check_destination_path, process_single_file, @@ -169,7 +169,9 @@ class DeliveryOptionsDialog(QtWidgets.QDialog): if repre["name"] not in selected_repres: continue - repre_path = path_from_representation(repre, self.anatomy) + repre_path = get_representation_path_with_anatomy( + repre, self.anatomy + ) anatomy_data = copy.deepcopy(repre["context"]) new_report_items = check_destination_path(str(repre["_id"]), From ea241ca807837896ac1f8299ec0c6c6bbb1020ad Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 14:41:44 +0200 Subject: [PATCH 110/274] added some docstrings to deprecated functions --- openpype/lib/delivery.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/openpype/lib/delivery.py b/openpype/lib/delivery.py index ea757932c9..e09188d3bb 100644 --- a/openpype/lib/delivery.py +++ b/openpype/lib/delivery.py @@ -55,19 +55,23 @@ def deprecated(new_destination): @deprecated("openpype.lib.path_tools.collect_frames") def collect_frames(files): - """ - Returns dict of source path and its frame, if from sequence + """Returns dict of source path and its frame, if from sequence - Uses clique as most precise solution, used when anatomy template that - created files is not known. + Uses clique as most precise solution, used when anatomy template that + created files is not known. - Assumption is that frames are separated by '.', negative frames are not - allowed. + Assumption is that frames are separated by '.', negative frames are not + allowed. - Args: - files(list) or (set with single value): list of source paths - Returns: - (dict): {'/asset/subset_v001.0001.png': '0001', ....} + Args: + files(list) or (set with single value): list of source paths + + Returns: + (dict): {'/asset/subset_v001.0001.png': '0001', ....} + + Deprecated: + Function was moved to different location and will be removed + after 3.16.* release. """ from .path_tools import collect_frames @@ -77,7 +81,12 @@ def collect_frames(files): @deprecated("openpype.lib.path_tools.format_file_size") def sizeof_fmt(num, suffix=None): - """Returns formatted string with size in appropriate unit""" + """Returns formatted string with size in appropriate unit + + Deprecated: + Function was moved to different location and will be removed + after 3.16.* release. + """ from .path_tools import format_file_size return format_file_size(num, suffix) From 14dc209ab0a42d799cfa37eebd08d090666b537f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 14:53:52 +0200 Subject: [PATCH 111/274] 'get_project_template_data' can access project name --- openpype/pipeline/template_data.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/template_data.py b/openpype/pipeline/template_data.py index 824a25127c..bab46a627d 100644 --- a/openpype/pipeline/template_data.py +++ b/openpype/pipeline/template_data.py @@ -28,27 +28,37 @@ def get_general_template_data(system_settings=None): } -def get_project_template_data(project_doc): +def get_project_template_data(project_doc=None, project_name=None): """Extract data from project document that are used in templates. Project document must have 'name' and (at this moment) optional key 'data.code'. + One of 'project_name' or 'project_doc' must be passed. With prepared + project document is function much faster because don't have to query. + Output contains formatting keys: - 'project[name]' - Project name - 'project[code]' - Project code Args: project_doc (Dict[str, Any]): Queried project document. + project_name (str): Name of project. Returns: Dict[str, Dict[str, str]]: Template data based on project document. """ + if not project_name: + project_name = project_doc["name"] + + if not project_doc: + project_code = get_project(project_name, fields=["data.code"]) + project_code = project_doc.get("data", {}).get("code") return { "project": { - "name": project_doc["name"], + "name": project_name, "code": project_code } } From e2060b9d65e4b7224fff916b4f492de0d015e9bf Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 15:00:08 +0200 Subject: [PATCH 112/274] marked 'get_format_dict' as deprecated and moved it to pipeline delivery --- openpype/lib/delivery.py | 33 +++++++++---------- .../event_handlers_user/action_delivery.py | 4 ++- openpype/pipeline/delivery.py | 26 +++++++++++++++ openpype/plugins/load/delivery.py | 4 ++- 4 files changed, 47 insertions(+), 20 deletions(-) create mode 100644 openpype/pipeline/delivery.py diff --git a/openpype/lib/delivery.py b/openpype/lib/delivery.py index e09188d3bb..1e364c45d7 100644 --- a/openpype/lib/delivery.py +++ b/openpype/lib/delivery.py @@ -125,28 +125,25 @@ def copy_file(src_path, dst_path): shutil.copyfile(src_path, dst_path) +@deprecated("openpype.pipeline.delivery.get_format_dict") def get_format_dict(anatomy, location_path): """Returns replaced root values from user provider value. - Args: - anatomy (Anatomy) - location_path (str): user provided value - Returns: - (dict): prepared for formatting of a template + Args: + anatomy (Anatomy) + location_path (str): user provided value + + Returns: + (dict): prepared for formatting of a template + + Deprecated: + Function was moved to different location and will be removed + after 3.16.* release. """ - format_dict = {} - if location_path: - location_path = location_path.replace("\\", "/") - root_names = anatomy.root_names_from_templates( - anatomy.templates["delivery"] - ) - if root_names is None: - format_dict["root"] = location_path - else: - format_dict["root"] = {} - for name in root_names: - format_dict["root"][name] = location_path - return format_dict + + from openpype.pipeline.delivery import get_format_dict + + return get_format_dict(anatomy, location_path) def check_destination_path(repre_id, diff --git a/openpype/modules/ftrack/event_handlers_user/action_delivery.py b/openpype/modules/ftrack/event_handlers_user/action_delivery.py index 59a34b3f85..08d6e53078 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delivery.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delivery.py @@ -18,8 +18,10 @@ from openpype_modules.ftrack.lib.custom_attributes import ( from openpype.lib.dateutils import get_datetime_data from openpype.pipeline import Anatomy from openpype.pipeline.load import get_representation_path_with_anatomy -from openpype.lib.delivery import ( +from openpype.pipeline.delivery import ( get_format_dict, +) +from openpype.lib.delivery import ( check_destination_path, process_single_file, process_sequence diff --git a/openpype/pipeline/delivery.py b/openpype/pipeline/delivery.py new file mode 100644 index 0000000000..03319f7ddc --- /dev/null +++ b/openpype/pipeline/delivery.py @@ -0,0 +1,26 @@ +"""Functions useful for delivery of published representations.""" + + +def get_format_dict(anatomy, location_path): + """Returns replaced root values from user provider value. + + Args: + anatomy (Anatomy): Project anatomy. + location_path (str): User provided value. + + Returns: + (dict): Prepared data for formatting of a template. + """ + + format_dict = {} + if not location_path: + return format_dict + + location_path = location_path.replace("\\", "/") + root_names = anatomy.root_names_from_templates( + anatomy.templates["delivery"] + ) + format_dict["root"] = {} + for name in root_names: + format_dict["root"][name] = location_path + return format_dict diff --git a/openpype/plugins/load/delivery.py b/openpype/plugins/load/delivery.py index 4651efd4a3..0ea62510a4 100644 --- a/openpype/plugins/load/delivery.py +++ b/openpype/plugins/load/delivery.py @@ -13,8 +13,10 @@ from openpype.lib import ( get_datetime_data, ) from openpype.pipeline.load import get_representation_path_with_anatomy -from openpype.lib.delivery import ( +from openpype.pipeline.delivery import ( get_format_dict, +) +from openpype.lib.delivery import ( check_destination_path, process_single_file, process_sequence, From fe566f4a4b1f5695e73731927601a814a330d8ae Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 15:00:57 +0200 Subject: [PATCH 113/274] copied 'copy_file' to 'pipeline.delivery' and renamed to '_copy_file' --- openpype/pipeline/delivery.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/openpype/pipeline/delivery.py b/openpype/pipeline/delivery.py index 03319f7ddc..5906892d59 100644 --- a/openpype/pipeline/delivery.py +++ b/openpype/pipeline/delivery.py @@ -1,4 +1,26 @@ """Functions useful for delivery of published representations.""" +import os +import shutil + +from openpype.lib import create_hard_link + + +def _copy_file(src_path, dst_path): + """Hardlink file if possible(to save space), copy if not. + + Because of using hardlinks should not be function used in other parts + of pipeline. + """ + + if os.path.exists(dst_path): + return + try: + create_hard_link( + src_path, + dst_path + ) + except OSError: + shutil.copyfile(src_path, dst_path) def get_format_dict(anatomy, location_path): From dc77d4a60908729f3b7ce343216b9af9852f1912 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 15:04:03 +0200 Subject: [PATCH 114/274] marked 'check_destination_path' as deprecated and moved to pipeline.delivery --- openpype/lib/delivery.py | 65 +++++++------------ .../event_handlers_user/action_delivery.py | 2 +- openpype/pipeline/delivery.py | 61 +++++++++++++++++ openpype/plugins/load/delivery.py | 2 +- 4 files changed, 86 insertions(+), 44 deletions(-) diff --git a/openpype/lib/delivery.py b/openpype/lib/delivery.py index 1e364c45d7..543c3d12e5 100644 --- a/openpype/lib/delivery.py +++ b/openpype/lib/delivery.py @@ -3,7 +3,6 @@ import os import shutil import glob import clique -import collections import functools import warnings @@ -146,56 +145,38 @@ def get_format_dict(anatomy, location_path): return get_format_dict(anatomy, location_path) +@deprecated("openpype.pipeline.delivery.check_destination_path") def check_destination_path(repre_id, anatomy, anatomy_data, datetime_data, template_name): """ Try to create destination path based on 'template_name'. - In the case that path cannot be filled, template contains unmatched - keys, provide error message to filter out repre later. + In the case that path cannot be filled, template contains unmatched + keys, provide error message to filter out repre later. - Args: - anatomy (Anatomy) - anatomy_data (dict): context to fill anatomy - datetime_data (dict): values with actual date - template_name (str): to pick correct delivery template - Returns: - (collections.defauldict): {"TYPE_OF_ERROR":"ERROR_DETAIL"} + Args: + anatomy (Anatomy) + anatomy_data (dict): context to fill anatomy + datetime_data (dict): values with actual date + template_name (str): to pick correct delivery template + + Returns: + (collections.defauldict): {"TYPE_OF_ERROR":"ERROR_DETAIL"} + + Deprecated: + Function was moved to different location and will be removed + after 3.16.* release. """ - anatomy_data.update(datetime_data) - anatomy_filled = anatomy.format_all(anatomy_data) - dest_path = anatomy_filled["delivery"][template_name] - report_items = collections.defaultdict(list) - if not dest_path.solved: - msg = ( - "Missing keys in Representation's context" - " for anatomy template \"{}\"." - ).format(template_name) + from openpype.pipeline.delivery import check_destination_path - sub_msg = ( - "Representation: {}
" - ).format(repre_id) - - if dest_path.missing_keys: - keys = ", ".join(dest_path.missing_keys) - sub_msg += ( - "- Missing keys: \"{}\"
" - ).format(keys) - - if dest_path.invalid_types: - items = [] - for key, value in dest_path.invalid_types.items(): - items.append("\"{}\" {}".format(key, str(value))) - - keys = ", ".join(items) - sub_msg += ( - "- Invalid value DataType: \"{}\"
" - ).format(keys) - - report_items[msg].append(sub_msg) - - return report_items + return check_destination_path( + repre_id, + anatomy, + anatomy_data, + datetime_data, + template_name + ) def process_single_file( diff --git a/openpype/modules/ftrack/event_handlers_user/action_delivery.py b/openpype/modules/ftrack/event_handlers_user/action_delivery.py index 08d6e53078..8b314d8f1d 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delivery.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delivery.py @@ -20,9 +20,9 @@ from openpype.pipeline import Anatomy from openpype.pipeline.load import get_representation_path_with_anatomy from openpype.pipeline.delivery import ( get_format_dict, + check_destination_path, ) from openpype.lib.delivery import ( - check_destination_path, process_single_file, process_sequence ) diff --git a/openpype/pipeline/delivery.py b/openpype/pipeline/delivery.py index 5906892d59..79667161a6 100644 --- a/openpype/pipeline/delivery.py +++ b/openpype/pipeline/delivery.py @@ -1,6 +1,7 @@ """Functions useful for delivery of published representations.""" import os import shutil +import collections from openpype.lib import create_hard_link @@ -46,3 +47,63 @@ def get_format_dict(anatomy, location_path): for name in root_names: format_dict["root"][name] = location_path return format_dict + + +def check_destination_path( + repre_id, + anatomy, + anatomy_data, + datetime_data, + template_name +): + """ Try to create destination path based on 'template_name'. + + In the case that path cannot be filled, template contains unmatched + keys, provide error message to filter out repre later. + + Args: + repre_id (str): Representation id. + anatomy (Anatomy): Project anatomy. + anatomy_data (dict): Template data to fill anatomy templates. + datetime_data (dict): Values with actual date. + template_name (str): Name of template which should be used from anatomy + templates. + Returns: + Dict[str, List[str]]: Report of happened errors. Key is message title + value is detailed information. + """ + + anatomy_data.update(datetime_data) + anatomy_filled = anatomy.format_all(anatomy_data) + dest_path = anatomy_filled["delivery"][template_name] + report_items = collections.defaultdict(list) + + if not dest_path.solved: + msg = ( + "Missing keys in Representation's context" + " for anatomy template \"{}\"." + ).format(template_name) + + sub_msg = ( + "Representation: {}
" + ).format(repre_id) + + if dest_path.missing_keys: + keys = ", ".join(dest_path.missing_keys) + sub_msg += ( + "- Missing keys: \"{}\"
" + ).format(keys) + + if dest_path.invalid_types: + items = [] + for key, value in dest_path.invalid_types.items(): + items.append("\"{}\" {}".format(key, str(value))) + + keys = ", ".join(items) + sub_msg += ( + "- Invalid value DataType: \"{}\"
" + ).format(keys) + + report_items[msg].append(sub_msg) + + return report_items diff --git a/openpype/plugins/load/delivery.py b/openpype/plugins/load/delivery.py index 0ea62510a4..1161636cb7 100644 --- a/openpype/plugins/load/delivery.py +++ b/openpype/plugins/load/delivery.py @@ -15,9 +15,9 @@ from openpype.lib import ( from openpype.pipeline.load import get_representation_path_with_anatomy from openpype.pipeline.delivery import ( get_format_dict, + check_destination_path, ) from openpype.lib.delivery import ( - check_destination_path, process_single_file, process_sequence, ) From eaff50b23e29dcb142557e3068c3031e1f1e268a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 15:07:45 +0200 Subject: [PATCH 115/274] Marked 'process_single_file' as deprecated and moved to pipeline delivery as 'deliver_single_file' --- openpype/lib/delivery.py | 61 +++++++------------ .../event_handlers_user/action_delivery.py | 4 +- openpype/pipeline/delivery.py | 59 ++++++++++++++++++ openpype/plugins/load/delivery.py | 6 +- 4 files changed, 87 insertions(+), 43 deletions(-) diff --git a/openpype/lib/delivery.py b/openpype/lib/delivery.py index 543c3d12e5..455401d0fd 100644 --- a/openpype/lib/delivery.py +++ b/openpype/lib/delivery.py @@ -179,53 +179,38 @@ def check_destination_path(repre_id, ) +@deprecated("openpype.pipeline.delivery.deliver_single_file") def process_single_file( src_path, repre, anatomy, template_name, anatomy_data, format_dict, report_items, log ): """Copy single file to calculated path based on template - Args: - src_path(str): path of source representation file - _repre (dict): full repre, used only in process_sequence, here only - as to share same signature - anatomy (Anatomy) - template_name (string): user selected delivery template name - anatomy_data (dict): data from repre to fill anatomy with - format_dict (dict): root dictionary with names and values - report_items (collections.defaultdict): to return error messages - log (Logger): for log printing - Returns: - (collections.defaultdict , int) + Args: + src_path(str): path of source representation file + _repre (dict): full repre, used only in process_sequence, here only + as to share same signature + anatomy (Anatomy) + template_name (string): user selected delivery template name + anatomy_data (dict): data from repre to fill anatomy with + format_dict (dict): root dictionary with names and values + report_items (collections.defaultdict): to return error messages + log (Logger): for log printing + + Returns: + (collections.defaultdict , int) + + Deprecated: + Function was moved to different location and will be removed + after 3.16.* release. """ - # Make sure path is valid for all platforms - src_path = os.path.normpath(src_path.replace("\\", "/")) - if not os.path.exists(src_path): - msg = "{} doesn't exist for {}".format(src_path, repre["_id"]) - report_items["Source file was not found"].append(msg) - return report_items, 0 + from openpype.pipeline.delivery import deliver_single_file - anatomy_filled = anatomy.format(anatomy_data) - if format_dict: - template_result = anatomy_filled["delivery"][template_name] - delivery_path = template_result.rootless.format(**format_dict) - else: - delivery_path = anatomy_filled["delivery"][template_name] - - # Backwards compatibility when extension contained `.` - delivery_path = delivery_path.replace("..", ".") - # Make sure path is valid for all platforms - delivery_path = os.path.normpath(delivery_path.replace("\\", "/")) - - delivery_folder = os.path.dirname(delivery_path) - if not os.path.exists(delivery_folder): - os.makedirs(delivery_folder) - - log.debug("Copying single: {} -> {}".format(src_path, delivery_path)) - copy_file(src_path, delivery_path) - - return report_items, 1 + return deliver_single_file( + src_path, repre, anatomy, template_name, anatomy_data, format_dict, + report_items, log + ) def process_sequence( diff --git a/openpype/modules/ftrack/event_handlers_user/action_delivery.py b/openpype/modules/ftrack/event_handlers_user/action_delivery.py index 8b314d8f1d..fe91670c3d 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delivery.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delivery.py @@ -21,9 +21,9 @@ from openpype.pipeline.load import get_representation_path_with_anatomy from openpype.pipeline.delivery import ( get_format_dict, check_destination_path, + deliver_single_file, ) from openpype.lib.delivery import ( - process_single_file, process_sequence ) @@ -596,7 +596,7 @@ class Delivery(BaseAction): self.log ) if not frame: - process_single_file(*args) + deliver_single_file(*args) else: process_sequence(*args) diff --git a/openpype/pipeline/delivery.py b/openpype/pipeline/delivery.py index 79667161a6..7c5121aa53 100644 --- a/openpype/pipeline/delivery.py +++ b/openpype/pipeline/delivery.py @@ -1,6 +1,8 @@ """Functions useful for delivery of published representations.""" import os import shutil +import glob +import clique import collections from openpype.lib import create_hard_link @@ -107,3 +109,60 @@ def check_destination_path( report_items[msg].append(sub_msg) return report_items + + +def deliver_single_file( + src_path, + repre, + anatomy, + template_name, + anatomy_data, + format_dict, + report_items, + log +): + """Copy single file to calculated path based on template + + Args: + src_path(str): path of source representation file + repre (dict): full repre, used only in process_sequence, here only + as to share same signature + anatomy (Anatomy) + template_name (string): user selected delivery template name + anatomy_data (dict): data from repre to fill anatomy with + format_dict (dict): root dictionary with names and values + report_items (collections.defaultdict): to return error messages + log (logging.Logger): for log printing + + Returns: + (collections.defaultdict, int) + """ + + # Make sure path is valid for all platforms + src_path = os.path.normpath(src_path.replace("\\", "/")) + + if not os.path.exists(src_path): + msg = "{} doesn't exist for {}".format(src_path, repre["_id"]) + report_items["Source file was not found"].append(msg) + return report_items, 0 + + anatomy_filled = anatomy.format(anatomy_data) + if format_dict: + template_result = anatomy_filled["delivery"][template_name] + delivery_path = template_result.rootless.format(**format_dict) + else: + delivery_path = anatomy_filled["delivery"][template_name] + + # Backwards compatibility when extension contained `.` + delivery_path = delivery_path.replace("..", ".") + # Make sure path is valid for all platforms + delivery_path = os.path.normpath(delivery_path.replace("\\", "/")) + + delivery_folder = os.path.dirname(delivery_path) + if not os.path.exists(delivery_folder): + os.makedirs(delivery_folder) + + log.debug("Copying single: {} -> {}".format(src_path, delivery_path)) + _copy_file(src_path, delivery_path) + + return report_items, 1 diff --git a/openpype/plugins/load/delivery.py b/openpype/plugins/load/delivery.py index 1161636cb7..a028ac0a87 100644 --- a/openpype/plugins/load/delivery.py +++ b/openpype/plugins/load/delivery.py @@ -16,9 +16,9 @@ from openpype.pipeline.load import get_representation_path_with_anatomy from openpype.pipeline.delivery import ( get_format_dict, check_destination_path, + deliver_single_file, ) from openpype.lib.delivery import ( - process_single_file, process_sequence, ) @@ -208,7 +208,7 @@ class DeliveryOptionsDialog(QtWidgets.QDialog): args[0] = src_path if frame: anatomy_data["frame"] = frame - new_report_items, uploaded = process_single_file(*args) + new_report_items, uploaded = deliver_single_file(*args) report_items.update(new_report_items) self._update_progress(uploaded) else: # fallback for Pype2 and representations without files @@ -217,7 +217,7 @@ class DeliveryOptionsDialog(QtWidgets.QDialog): repre["context"]["frame"] = len(str(frame)) * "#" if not frame: - new_report_items, uploaded = process_single_file(*args) + new_report_items, uploaded = deliver_single_file(*args) else: new_report_items, uploaded = process_sequence(*args) report_items.update(new_report_items) From d3a7637d1561a411a40a7b74494d692e292f5c4b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 15:10:44 +0200 Subject: [PATCH 116/274] Marked 'process_sequence' as deprecated and moved to pipeline delivery as 'deliver_sequence' --- openpype/lib/delivery.py | 144 +++--------------- .../event_handlers_user/action_delivery.py | 6 +- openpype/pipeline/delivery.py | 144 +++++++++++++++++- openpype/plugins/load/delivery.py | 6 +- 4 files changed, 172 insertions(+), 128 deletions(-) diff --git a/openpype/lib/delivery.py b/openpype/lib/delivery.py index 455401d0fd..d44a4edb3f 100644 --- a/openpype/lib/delivery.py +++ b/openpype/lib/delivery.py @@ -213,6 +213,7 @@ def process_single_file( ) +@deprecated("openpype.pipeline.delivery.deliver_sequence") def process_sequence( src_path, repre, anatomy, template_name, anatomy_data, format_dict, report_items, log @@ -220,128 +221,33 @@ def process_sequence( """ For Pype2(mainly - works in 3 too) where representation might not contain files. - Uses listing physical files (not 'files' on repre as a)might not be - present, b)might not be reliable for representation and copying them. + Uses listing physical files (not 'files' on repre as a)might not be + present, b)might not be reliable for representation and copying them. - TODO Should be refactored when files are sufficient to drive all - representations. + TODO Should be refactored when files are sufficient to drive all + representations. - Args: - src_path(str): path of source representation file - repre (dict): full representation - anatomy (Anatomy) - template_name (string): user selected delivery template name - anatomy_data (dict): data from repre to fill anatomy with - format_dict (dict): root dictionary with names and values - report_items (collections.defaultdict): to return error messages - log (Logger): for log printing - Returns: - (collections.defaultdict , int) + Args: + src_path(str): path of source representation file + repre (dict): full representation + anatomy (Anatomy) + template_name (string): user selected delivery template name + anatomy_data (dict): data from repre to fill anatomy with + format_dict (dict): root dictionary with names and values + report_items (collections.defaultdict): to return error messages + log (Logger): for log printing + + Returns: + (collections.defaultdict , int) + + Deprecated: + Function was moved to different location and will be removed + after 3.16.* release. """ - src_path = os.path.normpath(src_path.replace("\\", "/")) - def hash_path_exist(myPath): - res = myPath.replace('#', '*') - glob_search_results = glob.glob(res) - if len(glob_search_results) > 0: - return True - return False + from openpype.pipeline.delivery import deliver_sequence - if not hash_path_exist(src_path): - msg = "{} doesn't exist for {}".format(src_path, - repre["_id"]) - 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) - if delivery_template is None: - msg = ( - "Delivery template \"{}\" in anatomy of project \"{}\"" - " was not found" - ).format(template_name, anatomy.project_name) - report_items[""].append(msg) - return report_items, 0 - - # Check if 'frame' key is available in template which is required - # for sequence delivery - if "{frame" not in delivery_template: - msg = ( - "Delivery template \"{}\" in anatomy of project \"{}\"" - "does not contain '{{frame}}' key to fill. Delivery of sequence" - " can't be processed." - ).format(template_name, anatomy.project_name) - report_items[""].append(msg) - return report_items, 0 - - dir_path, file_name = os.path.split(str(src_path)) - - context = repre["context"] - ext = context.get("ext", context.get("representation")) - - if not ext: - msg = "Source extension not found, cannot find collection" - report_items[msg].append(src_path) - log.warning("{} <{}>".format(msg, context)) - return report_items, 0 - - ext = "." + ext - # context.representation could be .psd - ext = ext.replace("..", ".") - - src_collections, remainder = clique.assemble(os.listdir(dir_path)) - src_collection = None - for col in src_collections: - if col.tail != ext: - continue - - src_collection = col - break - - if src_collection is None: - msg = "Source collection of files was not found" - report_items[msg].append(src_path) - log.warning("{} <{}>".format(msg, src_path)) - return report_items, 0 - - frame_indicator = "@####@" - - anatomy_data["frame"] = frame_indicator - anatomy_filled = anatomy.format(anatomy_data) - - if format_dict: - template_result = anatomy_filled["delivery"][template_name] - delivery_path = template_result.rootless.format(**format_dict) - else: - delivery_path = anatomy_filled["delivery"][template_name] - - delivery_path = os.path.normpath(delivery_path.replace("\\", "/")) - delivery_folder = os.path.dirname(delivery_path) - dst_head, dst_tail = delivery_path.split(frame_indicator) - dst_padding = src_collection.padding - dst_collection = clique.Collection( - head=dst_head, - tail=dst_tail, - padding=dst_padding + return deliver_sequence( + src_path, repre, anatomy, template_name, anatomy_data, format_dict, + report_items, log ) - - if not os.path.exists(delivery_folder): - os.makedirs(delivery_folder) - - src_head = src_collection.head - src_tail = src_collection.tail - uploaded = 0 - for index in src_collection.indexes: - src_padding = src_collection.format("{padding}") % index - src_file_name = "{}{}{}".format(src_head, src_padding, src_tail) - src = os.path.normpath( - os.path.join(dir_path, src_file_name) - ) - - dst_padding = dst_collection.format("{padding}") % index - dst = "{}{}{}".format(dst_head, dst_padding, dst_tail) - log.debug("Copying single: {} -> {}".format(src, dst)) - copy_file(src, dst) - uploaded += 1 - - return report_items, uploaded diff --git a/openpype/modules/ftrack/event_handlers_user/action_delivery.py b/openpype/modules/ftrack/event_handlers_user/action_delivery.py index fe91670c3d..a400c8f5f0 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delivery.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delivery.py @@ -22,9 +22,7 @@ from openpype.pipeline.delivery import ( get_format_dict, check_destination_path, deliver_single_file, -) -from openpype.lib.delivery import ( - process_sequence + deliver_sequence, ) @@ -598,7 +596,7 @@ class Delivery(BaseAction): if not frame: deliver_single_file(*args) else: - process_sequence(*args) + deliver_sequence(*args) return self.report(report_items) diff --git a/openpype/pipeline/delivery.py b/openpype/pipeline/delivery.py index 7c5121aa53..8cf9a43aac 100644 --- a/openpype/pipeline/delivery.py +++ b/openpype/pipeline/delivery.py @@ -125,7 +125,7 @@ def deliver_single_file( Args: src_path(str): path of source representation file - repre (dict): full repre, used only in process_sequence, here only + repre (dict): full repre, used only in deliver_sequence, here only as to share same signature anatomy (Anatomy) template_name (string): user selected delivery template name @@ -166,3 +166,145 @@ def deliver_single_file( _copy_file(src_path, delivery_path) return report_items, 1 + + +def deliver_sequence( + src_path, + repre, + anatomy, + template_name, + anatomy_data, + format_dict, + report_items, + log +): + """ For Pype2(mainly - works in 3 too) where representation might not + contain files. + + Uses listing physical files (not 'files' on repre as a)might not be + present, b)might not be reliable for representation and copying them. + + TODO Should be refactored when files are sufficient to drive all + representations. + + Args: + src_path(str): path of source representation file + repre (dict): full representation + anatomy (Anatomy) + template_name (string): user selected delivery template name + anatomy_data (dict): data from repre to fill anatomy with + format_dict (dict): root dictionary with names and values + report_items (collections.defaultdict): to return error messages + log (logging.Logger): for log printing + + Returns: + (collections.defaultdict, int) + """ + + src_path = os.path.normpath(src_path.replace("\\", "/")) + + def hash_path_exist(myPath): + res = myPath.replace('#', '*') + glob_search_results = glob.glob(res) + if len(glob_search_results) > 0: + return True + return False + + if not hash_path_exist(src_path): + msg = "{} doesn't exist for {}".format( + src_path, repre["_id"]) + 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) + if delivery_template is None: + msg = ( + "Delivery template \"{}\" in anatomy of project \"{}\"" + " was not found" + ).format(template_name, anatomy.project_name) + report_items[""].append(msg) + return report_items, 0 + + # Check if 'frame' key is available in template which is required + # for sequence delivery + if "{frame" not in delivery_template: + msg = ( + "Delivery template \"{}\" in anatomy of project \"{}\"" + "does not contain '{{frame}}' key to fill. Delivery of sequence" + " can't be processed." + ).format(template_name, anatomy.project_name) + report_items[""].append(msg) + return report_items, 0 + + dir_path, file_name = os.path.split(str(src_path)) + + context = repre["context"] + ext = context.get("ext", context.get("representation")) + + if not ext: + msg = "Source extension not found, cannot find collection" + report_items[msg].append(src_path) + log.warning("{} <{}>".format(msg, context)) + return report_items, 0 + + ext = "." + ext + # context.representation could be .psd + ext = ext.replace("..", ".") + + src_collections, remainder = clique.assemble(os.listdir(dir_path)) + src_collection = None + for col in src_collections: + if col.tail != ext: + continue + + src_collection = col + break + + if src_collection is None: + msg = "Source collection of files was not found" + report_items[msg].append(src_path) + log.warning("{} <{}>".format(msg, src_path)) + return report_items, 0 + + frame_indicator = "@####@" + + anatomy_data["frame"] = frame_indicator + anatomy_filled = anatomy.format(anatomy_data) + + if format_dict: + template_result = anatomy_filled["delivery"][template_name] + delivery_path = template_result.rootless.format(**format_dict) + else: + delivery_path = anatomy_filled["delivery"][template_name] + + delivery_path = os.path.normpath(delivery_path.replace("\\", "/")) + delivery_folder = os.path.dirname(delivery_path) + dst_head, dst_tail = delivery_path.split(frame_indicator) + dst_padding = src_collection.padding + dst_collection = clique.Collection( + head=dst_head, + tail=dst_tail, + padding=dst_padding + ) + + if not os.path.exists(delivery_folder): + os.makedirs(delivery_folder) + + src_head = src_collection.head + src_tail = src_collection.tail + uploaded = 0 + for index in src_collection.indexes: + src_padding = src_collection.format("{padding}") % index + src_file_name = "{}{}{}".format(src_head, src_padding, src_tail) + src = os.path.normpath( + os.path.join(dir_path, src_file_name) + ) + + dst_padding = dst_collection.format("{padding}") % index + dst = "{}{}{}".format(dst_head, dst_padding, dst_tail) + log.debug("Copying single: {} -> {}".format(src, dst)) + _copy_file(src, dst) + uploaded += 1 + + return report_items, uploaded diff --git a/openpype/plugins/load/delivery.py b/openpype/plugins/load/delivery.py index a028ac0a87..89c24f2402 100644 --- a/openpype/plugins/load/delivery.py +++ b/openpype/plugins/load/delivery.py @@ -17,9 +17,7 @@ from openpype.pipeline.delivery import ( get_format_dict, check_destination_path, deliver_single_file, -) -from openpype.lib.delivery import ( - process_sequence, + deliver_sequence, ) @@ -219,7 +217,7 @@ class DeliveryOptionsDialog(QtWidgets.QDialog): if not frame: new_report_items, uploaded = deliver_single_file(*args) else: - new_report_items, uploaded = process_sequence(*args) + new_report_items, uploaded = deliver_sequence(*args) report_items.update(new_report_items) self._update_progress(uploaded) From 19c7d2b8a150d4d052db9a9b4a813bd2922b272f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 15:20:21 +0200 Subject: [PATCH 117/274] marked 'copy_file' as deprecated --- openpype/lib/delivery.py | 3 +-- openpype/lib/path_tools.py | 11 ++++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openpype/lib/delivery.py b/openpype/lib/delivery.py index d44a4edb3f..efb542de75 100644 --- a/openpype/lib/delivery.py +++ b/openpype/lib/delivery.py @@ -1,8 +1,6 @@ """Functions useful for delivery action or loader""" import os import shutil -import glob -import clique import functools import warnings @@ -109,6 +107,7 @@ def path_from_representation(representation, anatomy): return get_representation_path_with_anatomy(representation, anatomy) +@deprecated def copy_file(src_path, dst_path): """Hardlink file if possible(to save space), copy if not""" from openpype.lib import create_hard_link # safer importing diff --git a/openpype/lib/path_tools.py b/openpype/lib/path_tools.py index 45aa54d6cb..1835c71644 100644 --- a/openpype/lib/path_tools.py +++ b/openpype/lib/path_tools.py @@ -179,12 +179,12 @@ def get_version_from_path(file): """Find version number in file path string. Args: - file (string): file path + file (str): file path Returns: - v: version number in string ('001') - + str: version number in string ('001') """ + pattern = re.compile(r"[\._]v([0-9]+)", re.IGNORECASE) try: return pattern.findall(file)[-1] @@ -200,16 +200,17 @@ def get_last_version_from_path(path_dir, filter): """Find last version of given directory content. Args: - path_dir (string): directory path + path_dir (str): directory path filter (list): list of strings used as file name filter Returns: - string: file name with last version + str: file name with last version Example: last_version_file = get_last_version_from_path( "/project/shots/shot01/work", ["shot01", "compositing", "nk"]) """ + assert os.path.isdir(path_dir), "`path_dir` argument needs to be directory" assert isinstance(filter, list) and ( len(filter) != 0), "`filter` argument needs to be list and not empty" From cd09f23b968ee0162441c388172ec0027e825a5e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 16:44:51 +0200 Subject: [PATCH 118/274] removed unused import --- openpype/plugins/load/delete_old_versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/load/delete_old_versions.py b/openpype/plugins/load/delete_old_versions.py index 8c8546d9c8..b7ac015268 100644 --- a/openpype/plugins/load/delete_old_versions.py +++ b/openpype/plugins/load/delete_old_versions.py @@ -10,7 +10,7 @@ from Qt import QtWidgets, QtCore from openpype import style from openpype.client import get_versions, get_representations from openpype.modules import ModulesManager -from openpype.lib import StringTemplate, format_file_size +from openpype.lib import format_file_size from openpype.pipeline import load, AvalonMongoDB, Anatomy from openpype.pipeline.load import ( get_representation_path_with_anatomy, From 0d8cf12618cee76a5e144429a3459074b14e4adf Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 17:14:22 +0200 Subject: [PATCH 119/274] define new source where publish templates are not defined in integrate plubin --- .../defaults/project_settings/global.json | 3 + .../defaults/project_settings/maya.json | 2 +- .../schemas/schema_global_publish.json | 4 ++ .../schemas/schema_global_tools.json | 57 +++++++++++++++++++ 4 files changed, 65 insertions(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 0ff9363ba7..3e00cd725e 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -414,6 +414,9 @@ "filter_families": [] } ] + }, + "publish": { + "template_name_profiles": [] } }, "project_folder_structure": "{\"__project_root__\": {\"prod\": {}, \"resources\": {\"footage\": {\"plates\": {}, \"offline\": {}}, \"audio\": {}, \"art_dept\": {}}, \"editorial\": {}, \"assets\": {\"characters\": {}, \"locations\": {}}, \"shots\": {}}}", diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 28f6d23e4d..38063bc2c1 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -980,4 +980,4 @@ "ValidateNoAnimation": false } } -} +} \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index e1aa230b49..c24c88d04a 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -642,6 +642,10 @@ ] } }, + { + "type": "label", + "label": "NOTE: Publish template profiles settings were moved to Tools/Publish/Template name profiles. Please move values there." + }, { "type": "list", "key": "template_name_profiles", diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json index f8c9482e5f..7dc44c2842 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json @@ -284,6 +284,63 @@ } } ] + }, + { + "type": "dict", + "key": "publish", + "label": "Publish", + "children": [ + { + "type": "label", + "label": "NOTE: For backwards compatibility can be value empty and in that case are used values from IntegrateAssetNew. This will change in future so please move all values here as soon as possible." + }, + { + "type": "list", + "key": "template_name_profiles", + "label": "Template name profiles", + "use_label_wrap": true, + "object_type": { + "type": "dict", + "children": [ + { + "type": "label", + "label": "" + }, + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, + { + "type": "hosts-enum", + "key": "hosts", + "label": "Hosts", + "multiselection": true + }, + { + "key": "task_types", + "label": "Task types", + "type": "task-types-enum" + }, + { + "key": "tasks", + "label": "Task names", + "type": "list", + "object_type": "text" + }, + { + "type": "separator" + }, + { + "type": "text", + "key": "template_name", + "label": "Template name" + } + ] + } + } + ] } ] } From b14bb4b91e2bf7053a3e5d057f6b54d479535072 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 29 Aug 2022 17:23:22 +0200 Subject: [PATCH 120/274] Fix typo for Maya argument `with_focus` -> `withFocus` --- openpype/hosts/maya/plugins/publish/extract_playblast.py | 2 +- openpype/hosts/maya/plugins/publish/extract_thumbnail.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index 54ef09e060..871adda0c3 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -128,7 +128,7 @@ class ExtractPlayblast(openpype.api.Extractor): # Update preset with current panel setting # if override_viewport_options is turned off if not override_viewport_options: - panel = cmds.getPanel(with_focus=True) + panel = cmds.getPanel(withFocus=True) panel_preset = capture.parse_active_view() preset.update(panel_preset) cmds.setFocus(panel) diff --git a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py index 01980578cf..9380da5128 100644 --- a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py @@ -100,9 +100,9 @@ class ExtractThumbnail(openpype.api.Extractor): # camera. if preset.pop("isolate_view", False) and instance.data.get("isolate"): preset["isolate"] = instance.data["setMembers"] - + # Show or Hide Image Plane - image_plane = instance.data.get("imagePlane", True) + image_plane = instance.data.get("imagePlane", True) if "viewport_options" in preset: preset["viewport_options"]["imagePlane"] = image_plane else: @@ -117,7 +117,7 @@ class ExtractThumbnail(openpype.api.Extractor): # Update preset with current panel setting # if override_viewport_options is turned off if not override_viewport_options: - panel = cmds.getPanel(with_focus=True) + panel = cmds.getPanel(withFocus=True) panel_preset = capture.parse_active_view() preset.update(panel_preset) cmds.setFocus(panel) From 2b6c4659237259b6c691dd2b5dc1db927b47fcd3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 18:03:30 +0200 Subject: [PATCH 121/274] added helper functions to get template name --- openpype/pipeline/publish/__init__.py | 4 ++ openpype/pipeline/publish/contants.py | 1 + openpype/pipeline/publish/lib.py | 97 ++++++++++++++++++++++++++- 3 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 openpype/pipeline/publish/contants.py diff --git a/openpype/pipeline/publish/__init__.py b/openpype/pipeline/publish/__init__.py index aa7fe0bdbf..a2aa61c4d5 100644 --- a/openpype/pipeline/publish/__init__.py +++ b/openpype/pipeline/publish/__init__.py @@ -10,6 +10,8 @@ from .publish_plugins import ( ) from .lib import ( + get_publish_template_name, + DiscoverResult, publish_plugins_discover, load_help_content_from_plugin, @@ -33,6 +35,8 @@ __all__ = ( "OpenPypePyblishPluginMixin", "OptionalPyblishPluginMixin", + "get_publish_template_name", + "DiscoverResult", "publish_plugins_discover", "load_help_content_from_plugin", diff --git a/openpype/pipeline/publish/contants.py b/openpype/pipeline/publish/contants.py new file mode 100644 index 0000000000..958675ecc1 --- /dev/null +++ b/openpype/pipeline/publish/contants.py @@ -0,0 +1 @@ +DEFAULT_PUBLISH_TEMPLATE = "publish" diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 9060a0bf4b..7c3ea22c06 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -8,8 +8,101 @@ import six import pyblish.plugin import pyblish.api -from openpype.lib import Logger -from openpype.settings import get_project_settings, get_system_settings +from openpype.lib import Logger, filter_profiles +from openpype.settings import ( + get_project_settings, + get_system_settings, +) + +from .contants import DEFAULT_PUBLISH_TEMPLATE + + +def get_template_name_profiles(project_name=None, project_settings=None): + """Receive profiles for publish template keys. + + At least one of arguments must be passed. + + Args: + project_name (str): Name of project where to look for templates. + project_settings(Dic[str, Any]): Prepared project settings. + + Returns: + List[Dict[str, Any]]: Publish template profiles. + """ + + if not project_name and not project_settings: + raise ValueError(( + "Both project name and project settings are missing." + " At least one must be entered." + )) + + if not project_settings: + project_settings = get_project_settings(project_name) + + profiles = ( + project_settings + ["global"] + ["tools"] + ["publish"] + ["template_name_profiles"] + ) + if profiles: + return profiles + + # Use legacy approach for cases new settings are not filled yet for the + # project + return ( + project_settings + ["global"] + ["publish"] + ["IntegrateAssetNew"] + ["template_name_profiles"] + ) + + +def get_publish_template_name( + project_name, + host_name, + family, + task_name, + task_type, + project_settings=None, + logger=None +): + """Get template name which should be used for passed context. + + Publish templates are filtered by host name, family, task name and + task type. + + Default template which is used at if profiles are not available or profile + has empty value is defined by 'DEFAULT_PUBLISH_TEMPLATE' constant. + + Args: + project_name (str): Name of project where to look for settings. + host_name (str): Name of host integration. + family (str): Family for which should be found template. + task_name (str): Task name on which is intance working. + task_type (str): Task type on which is intance working. + project_setting (Dict[str, Any]): Prepared project settings. + logger (logging.Logger): Custom logger used for 'filter_profiles' + function. + + Returns: + str: Template name which should be used for integration. + """ + + template = None + filter_criteria = { + "hosts": host_name, + "families": family, + "tasks": task_name, + "task_types": task_type, + } + profiles = get_template_name_profiles(project_name, project_settings) + profile = filter_profiles(profiles, filter_criteria, logger=logger) + if profile: + template = profile["template_name"] + return template or DEFAULT_PUBLISH_TEMPLATE class DiscoverResult: From 96138a0b73ba6a3f9757283853da9cd1aa85c023 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 18:22:49 +0200 Subject: [PATCH 122/274] use new functions in integrators --- openpype/plugins/publish/integrate.py | 61 ++++++-------------- openpype/plugins/publish/integrate_legacy.py | 21 +++---- 2 files changed, 27 insertions(+), 55 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index f99c718f8a..56d2621015 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -26,7 +26,10 @@ from openpype.lib import source_hash from openpype.lib.profiles_filtering import filter_profiles from openpype.lib.file_transaction import FileTransaction from openpype.pipeline import legacy_io -from openpype.pipeline.publish import KnownPublishError +from openpype.pipeline.publish import ( + KnownPublishError, + get_publish_template_name, +) log = logging.getLogger(__name__) @@ -792,52 +795,26 @@ class IntegrateAsset(pyblish.api.InstancePlugin): def get_template_name(self, instance): """Return anatomy template name to use for integration""" - # Define publish template name from profiles - filter_criteria = self.get_profile_filter_criteria(instance) - template_name_profiles = self._get_template_name_profiles(instance) - profile = filter_profiles( - template_name_profiles, - filter_criteria, - logger=self.log - ) - - if profile: - return profile["template_name"] - return self.default_template_name - - def _get_template_name_profiles(self, instance): - """Receive profiles for publish template keys. - - Reuse template name profiles from legacy integrator. Goal is to move - the profile settings out of plugin settings but until that happens we - want to be able set it at one place and don't break backwards - compatibility (more then once). - """ - - return ( - instance.context.data["project_settings"] - ["global"] - ["publish"] - ["IntegrateAssetNew"] - ["template_name_profiles"] - ) - - def get_profile_filter_criteria(self, instance): - """Return filter criteria for `filter_profiles`""" # Anatomy data is pre-filled by Collectors - anatomy_data = instance.data["anatomyData"] + + project_name = legacy_io.active_project() # Task can be optional in anatomy data - task = anatomy_data.get("task", {}) + host_name = instance.context.data["hostName"] + anatomy_data = instance.data["anatomyData"] + family = anatomy_data["family"] + task_info = anatomy_data.get("task") or {} - # Return filter criteria - return { - "families": anatomy_data["family"], - "tasks": task.get("name"), - "task_types": task.get("type"), - "hosts": instance.context.data["hostName"], - } + return get_publish_template_name( + project_name, + host_name, + family, + task_name=task_info.get("name"), + task_type=task_info.get("type"), + project_settings=instance.context.data["project_settings"], + logger=self.log + ) def get_rootless_path(self, anatomy, path): """Returns, if possible, path without absolute portion from root diff --git a/openpype/plugins/publish/integrate_legacy.py b/openpype/plugins/publish/integrate_legacy.py index b90b61f587..fedaae794a 100644 --- a/openpype/plugins/publish/integrate_legacy.py +++ b/openpype/plugins/publish/integrate_legacy.py @@ -33,6 +33,7 @@ from openpype.lib import ( TemplateUnsolved ) from openpype.pipeline import legacy_io +from openpype.pipeline.publish import get_publish_template_name # this is needed until speedcopy for linux is fixed if sys.platform == "win32": @@ -388,22 +389,16 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): family = self.main_family_from_instance(instance) - key_values = { - "families": family, - "tasks": task_name, - "hosts": instance.context.data["hostName"], - "task_types": task_type - } - profile = filter_profiles( - self.template_name_profiles, - key_values, + template_name = get_publish_template_name( + project_name, + instance.context.data["hostName"], + family, + task_name=task_info.get("name"), + task_type=task_info.get("type"), + project_settings=instance.context.data["project_settings"], logger=self.log ) - template_name = "publish" - if profile: - template_name = profile["template_name"] - published_representations = {} for idx, repre in enumerate(repres): published_files = [] From c7108ac7fbad46fe2aafe669498cb3755d9c7730 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 18:23:37 +0200 Subject: [PATCH 123/274] modified imports in integrators --- openpype/plugins/publish/integrate.py | 6 +++--- openpype/plugins/publish/integrate_legacy.py | 11 ++++++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 56d2621015..8b60ea3b51 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -5,6 +5,9 @@ import copy import clique import six +from bson.objectid import ObjectId +import pyblish.api + from openpype.client.operations import ( OperationsSession, new_subset_document, @@ -14,8 +17,6 @@ from openpype.client.operations import ( prepare_version_update_data, prepare_representation_update_data, ) -from bson.objectid import ObjectId -import pyblish.api from openpype.client import ( get_representations, @@ -23,7 +24,6 @@ from openpype.client import ( get_version_by_name, ) from openpype.lib import source_hash -from openpype.lib.profiles_filtering import filter_profiles from openpype.lib.file_transaction import FileTransaction from openpype.pipeline import legacy_io from openpype.pipeline.publish import ( diff --git a/openpype/plugins/publish/integrate_legacy.py b/openpype/plugins/publish/integrate_legacy.py index fedaae794a..0e157c9d1f 100644 --- a/openpype/plugins/publish/integrate_legacy.py +++ b/openpype/plugins/publish/integrate_legacy.py @@ -15,7 +15,6 @@ from bson.objectid import ObjectId from pymongo import DeleteOne, InsertOne import pyblish.api -import openpype.api from openpype.client import ( get_asset_by_name, get_subset_by_id, @@ -25,12 +24,14 @@ from openpype.client import ( get_representations, get_archived_representations, ) -from openpype.lib.profiles_filtering import filter_profiles from openpype.lib import ( prepare_template_data, create_hard_link, StringTemplate, - TemplateUnsolved + TemplateUnsolved, + source_hash, + filter_profiles, + get_local_site_id, ) from openpype.pipeline import legacy_io from openpype.pipeline.publish import get_publish_template_name @@ -1053,7 +1054,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): for _src, dest in resources: path = self.get_rootless_path(anatomy, dest) dest = self.get_dest_temp_url(dest) - file_hash = openpype.api.source_hash(dest) + file_hash = source_hash(dest) if self.TMP_FILE_EXT and \ ',{}'.format(self.TMP_FILE_EXT) in file_hash: file_hash = file_hash.replace(',{}'.format(self.TMP_FILE_EXT), @@ -1163,7 +1164,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): def _get_sites(self, sync_project_presets): """Returns tuple (local_site, remote_site)""" - local_site_id = openpype.api.get_local_site_id() + local_site_id = get_local_site_id() local_site = sync_project_presets["config"]. \ get("active_site", "studio").strip() From c76a1a1dbbe8e705b06ebf02f37237cf7dda98fd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 18:52:43 +0200 Subject: [PATCH 124/274] added settings for hero templates and changed 'tasks' to 'task_names' --- .../defaults/project_settings/global.json | 3 +- .../schemas/schema_global_tools.json | 47 +++++++++++++++++-- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 3e00cd725e..8692f95a04 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -416,7 +416,8 @@ ] }, "publish": { - "template_name_profiles": [] + "template_name_profiles": [], + "hero_template_name_profiles": [] } }, "project_folder_structure": "{\"__project_root__\": {\"prod\": {}, \"resources\": {\"footage\": {\"plates\": {}, \"offline\": {}}, \"audio\": {}, \"art_dept\": {}}, \"editorial\": {}, \"assets\": {\"characters\": {}, \"locations\": {}}, \"shots\": {}}}", diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json index 7dc44c2842..c919cd73c5 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json @@ -303,9 +303,47 @@ "type": "dict", "children": [ { - "type": "label", - "label": "" + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" }, + { + "type": "hosts-enum", + "key": "hosts", + "label": "Hosts", + "multiselection": true + }, + { + "key": "task_types", + "label": "Task types", + "type": "task-types-enum" + }, + { + "key": "task_names", + "label": "Task names", + "type": "list", + "object_type": "text" + }, + { + "type": "separator" + }, + { + "type": "text", + "key": "template_name", + "label": "Template name" + } + ] + } + }, + { + "type": "list", + "key": "hero_template_name_profiles", + "label": "Hero template name profiles", + "use_label_wrap": true, + "object_type": { + "type": "dict", + "children": [ { "key": "families", "label": "Families", @@ -324,7 +362,7 @@ "type": "task-types-enum" }, { - "key": "tasks", + "key": "task_names", "label": "Task names", "type": "list", "object_type": "text" @@ -335,7 +373,8 @@ { "type": "text", "key": "template_name", - "label": "Template name" + "label": "Template name", + "tooltip": "Name of template from Anatomy templates" } ] } From 9d4416719b4a99d50b0d411b5548a8afa8072240 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 18:53:33 +0200 Subject: [PATCH 125/274] convert legacy to new settings by replacing 'tasks' with 'task_names' --- openpype/pipeline/publish/lib.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 7c3ea22c06..03dfbadfcc 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -2,6 +2,7 @@ import os import sys import types import inspect +import copy import xml.etree.ElementTree import six @@ -47,17 +48,23 @@ def get_template_name_profiles(project_name=None, project_settings=None): ["template_name_profiles"] ) if profiles: - return profiles + return copy.deepcopy(profiles) # Use legacy approach for cases new settings are not filled yet for the # project - return ( + legacy_profiles = ( project_settings ["global"] ["publish"] ["IntegrateAssetNew"] ["template_name_profiles"] ) + # Replace "tasks" key with "task_names" + profiles = [] + for profile in copy.deepcopy(legacy_profiles): + profile["task_names"] = profile.pop("tasks", []) + profiles.append(profile) + return profiles def get_publish_template_name( @@ -95,7 +102,7 @@ def get_publish_template_name( filter_criteria = { "hosts": host_name, "families": family, - "tasks": task_name, + "task_names": task_name, "task_types": task_type, } profiles = get_template_name_profiles(project_name, project_settings) From 63f5b5f2ab40a94c7496b8f08fa19204a5687b5a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 18:53:49 +0200 Subject: [PATCH 126/274] added ability to get hero version template name --- openpype/pipeline/publish/contants.py | 1 + openpype/pipeline/publish/lib.py | 64 +++++++++++++++++++++++++-- 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/openpype/pipeline/publish/contants.py b/openpype/pipeline/publish/contants.py index 958675ecc1..169eca2e5c 100644 --- a/openpype/pipeline/publish/contants.py +++ b/openpype/pipeline/publish/contants.py @@ -1 +1,2 @@ DEFAULT_PUBLISH_TEMPLATE = "publish" +DEFAULT_HERO_PUBLISH_TEMPLATE = "hero" diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 03dfbadfcc..85a64da721 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -15,7 +15,10 @@ from openpype.settings import ( get_system_settings, ) -from .contants import DEFAULT_PUBLISH_TEMPLATE +from .contants import ( + DEFAULT_PUBLISH_TEMPLATE, + DEFAULT_HERO_PUBLISH_TEMPLATE, +) def get_template_name_profiles(project_name=None, project_settings=None): @@ -67,6 +70,49 @@ def get_template_name_profiles(project_name=None, project_settings=None): return profiles +def get_hero_template_name_profiles(project_name=None, project_settings=None): + """Receive profiles for hero publish template keys. + + At least one of arguments must be passed. + + Args: + project_name (str): Name of project where to look for templates. + project_settings(Dic[str, Any]): Prepared project settings. + + Returns: + List[Dict[str, Any]]: Publish template profiles. + """ + + if not project_name and not project_settings: + raise ValueError(( + "Both project name and project settings are missing." + " At least one must be entered." + )) + + if not project_settings: + project_settings = get_project_settings(project_name) + + profiles = ( + project_settings + ["global"] + ["tools"] + ["publish"] + ["hero_template_name_profiles"] + ) + if profiles: + return copy.deepcopy(profiles) + + # Use legacy approach for cases new settings are not filled yet for the + # project + return copy.deepcopy( + project_settings + ["global"] + ["publish"] + ["IntegrateHeroVersion"] + ["template_name_profiles"] + ) + + def get_publish_template_name( project_name, host_name, @@ -74,6 +120,7 @@ def get_publish_template_name( task_name, task_type, project_settings=None, + hero=False, logger=None ): """Get template name which should be used for passed context. @@ -105,11 +152,22 @@ def get_publish_template_name( "task_names": task_name, "task_types": task_type, } - profiles = get_template_name_profiles(project_name, project_settings) + if hero: + default_template = DEFAULT_HERO_PUBLISH_TEMPLATE + profiles = get_hero_template_name_profiles( + project_name, project_settings + ) + + else: + profiles = get_template_name_profiles( + project_name, project_settings + ) + default_template = DEFAULT_PUBLISH_TEMPLATE + profile = filter_profiles(profiles, filter_criteria, logger=logger) if profile: template = profile["template_name"] - return template or DEFAULT_PUBLISH_TEMPLATE + return template or default_template class DiscoverResult: From 1698aefcfbc887ba6f29fc59dbdfbc2595d5c6a8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 18:57:00 +0200 Subject: [PATCH 127/274] use 'get_publish_template_name' in hero integration --- .../plugins/publish/integrate_hero_version.py | 41 ++++++++----------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/openpype/plugins/publish/integrate_hero_version.py b/openpype/plugins/publish/integrate_hero_version.py index 7d698ff98d..2938c61f8e 100644 --- a/openpype/plugins/publish/integrate_hero_version.py +++ b/openpype/plugins/publish/integrate_hero_version.py @@ -14,14 +14,12 @@ from openpype.client import ( get_archived_representations, get_representations, ) -from openpype.lib import ( - create_hard_link, - filter_profiles -) +from openpype.lib import create_hard_link from openpype.pipeline import ( schema, legacy_io, ) +from openpype.pipeline.publish import get_publish_template_name class IntegrateHeroVersion(pyblish.api.InstancePlugin): @@ -68,10 +66,11 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): ) return - template_key = self._get_template_key(instance) - anatomy = instance.context.data["anatomy"] project_name = anatomy.project_name + + template_key = self._get_template_key(project_name, instance) + if template_key not in anatomy.templates: self.log.warning(( "!!! Anatomy of project \"{}\" does not have set" @@ -527,30 +526,24 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): return publish_folder - def _get_template_key(self, instance): + def _get_template_key(self, project_name, instance): anatomy_data = instance.data["anatomyData"] - task_data = anatomy_data.get("task") or {} - task_name = task_data.get("name") - task_type = task_data.get("type") + task_info = anatomy_data.get("task") or {} host_name = instance.context.data["hostName"] + # TODO raise error if Hero not set? family = self.main_family_from_instance(instance) - key_values = { - "families": family, - "task_names": task_name, - "task_types": task_type, - "hosts": host_name - } - profile = filter_profiles( - self.template_name_profiles, - key_values, + + return get_publish_template_name( + project_name, + host_name, + family, + task_info.get("name"), + task_info.get("type"), + project_settings=instance.context.data["project_settings"], + hero=True, logger=self.log ) - if profile: - template_name = profile["template_name"] - else: - template_name = self._default_template_name - return template_name def main_family_from_instance(self, instance): """Returns main family of entered instance.""" From 9b7384e1ae96b0f348911e9e163a23857dd2ca7f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 18:57:27 +0200 Subject: [PATCH 128/274] removed unused attribute --- openpype/plugins/publish/integrate_legacy.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/plugins/publish/integrate_legacy.py b/openpype/plugins/publish/integrate_legacy.py index 0e157c9d1f..bbf30c9ab7 100644 --- a/openpype/plugins/publish/integrate_legacy.py +++ b/openpype/plugins/publish/integrate_legacy.py @@ -140,7 +140,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): integrated_file_sizes = {} # Attributes set by settings - template_name_profiles = None subset_grouping_profiles = None def process(self, instance): From c6a6e3b21a4aaa6c98450de918c78907fed91f5e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 19:07:34 +0200 Subject: [PATCH 129/274] added warning for access to legacy settings --- openpype/pipeline/publish/lib.py | 35 +++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 85a64da721..29c745ed15 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -21,7 +21,9 @@ from .contants import ( ) -def get_template_name_profiles(project_name=None, project_settings=None): +def get_template_name_profiles( + project_name, project_settings=None, logger=None +): """Receive profiles for publish template keys. At least one of arguments must be passed. @@ -62,6 +64,16 @@ def get_template_name_profiles(project_name=None, project_settings=None): ["IntegrateAssetNew"] ["template_name_profiles"] ) + if legacy_profiles: + if not logger: + logger = Logger.get_logger("get_template_name_profiles") + + logger.warning(( + "Project \"{}\" is using legacy access to publish template." + " It is recommended to move settings to new location" + " 'project_settings/global/tools/publish/template_name_profiles'." + ).format(project_name)) + # Replace "tasks" key with "task_names" profiles = [] for profile in copy.deepcopy(legacy_profiles): @@ -70,7 +82,9 @@ def get_template_name_profiles(project_name=None, project_settings=None): return profiles -def get_hero_template_name_profiles(project_name=None, project_settings=None): +def get_hero_template_name_profiles( + project_name, project_settings=None, logger=None +): """Receive profiles for hero publish template keys. At least one of arguments must be passed. @@ -104,13 +118,24 @@ def get_hero_template_name_profiles(project_name=None, project_settings=None): # Use legacy approach for cases new settings are not filled yet for the # project - return copy.deepcopy( + legacy_profiles = copy.deepcopy( project_settings ["global"] ["publish"] ["IntegrateHeroVersion"] ["template_name_profiles"] ) + if legacy_profiles: + if not logger: + logger = Logger.get_logger("get_hero_template_name_profiles") + + logger.warning(( + "Project \"{}\" is using legacy access to hero publish template." + " It is recommended to move settings to new location" + " 'project_settings/global/tools/publish/" + "hero_template_name_profiles'." + ).format(project_name)) + return legacy_profiles def get_publish_template_name( @@ -155,12 +180,12 @@ def get_publish_template_name( if hero: default_template = DEFAULT_HERO_PUBLISH_TEMPLATE profiles = get_hero_template_name_profiles( - project_name, project_settings + project_name, project_settings, logger ) else: profiles = get_template_name_profiles( - project_name, project_settings + project_name, project_settings, logger ) default_template = DEFAULT_PUBLISH_TEMPLATE From dc4c32b6fc6aaeaedb6bb9d76c7b72d9f5c45c45 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 29 Aug 2022 19:14:58 +0200 Subject: [PATCH 130/274] Fix representation data for workfile --- openpype/modules/deadline/abstract_submit_deadline.py | 6 +++--- .../deadline/plugins/publish/submit_maya_deadline.py | 3 --- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index 577378335e..d198542370 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -554,9 +554,9 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin): "Workfile (scene) must be published along") # determine published path from Anatomy. template_data = i.data.get("anatomyData") - rep = i.data.get("representations")[0].get("ext") - template_data["representation"] = rep - template_data["ext"] = rep + rep = i.data.get("representations")[0] + template_data["representation"] = rep.get("name") + template_data["ext"] = rep.get("ext") template_data["comment"] = None anatomy_filled = anatomy.format(template_data) template_filled = anatomy_filled["publish"]["path"] diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 5a7d0b98c6..68e8eaaa73 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -258,10 +258,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): ) self._patch_workfile(filepath, patches) - # todo: on self.use_published originally use template_data["representation"] using .get("name") instead of .get("ext") # todo: on self.use_published replace path for publishRenderMetadataFolder - # rep = i.data.get("representations")[0].get("name") - # if instance.data.get("publishRenderMetadataFolder"): # instance.data["publishRenderMetadataFolder"] = \ # instance.data["publishRenderMetadataFolder"].replace( From 67b8664be07fbb8a0061b7c8a62bf4073ef79307 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 29 Aug 2022 19:15:20 +0200 Subject: [PATCH 131/274] Remove comment for patched file code refactor since it's already implemented --- .../modules/deadline/plugins/publish/submit_maya_deadline.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 68e8eaaa73..07ed237c94 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -266,10 +266,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # self.log.info("Scene name was switched {} -> {}".format( # orig_scene, new_scene # )) - # # patch workfile is needed - # if filepath not in patched_files: - # patched_file = self._patch_workfile(filepath, patches) - # patched_files.append(patched_file) # Gather needed data ------------------------------------------------ workspace = context.data["workspaceDir"] From 2da8f036dee501be62da07d07fa9efafc9e8839f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 29 Aug 2022 19:19:37 +0200 Subject: [PATCH 132/274] Refactor logic for less indentation --- .../deadline/abstract_submit_deadline.py | 103 +++++++++--------- 1 file changed, 52 insertions(+), 51 deletions(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index d198542370..55e16d8d21 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -546,65 +546,66 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin): anatomy = self._instance.context.data['anatomy'] file_path = None for i in self._instance.context: - if "workfile" in i.data["families"] \ - or i.data["family"] == "workfile": - # test if there is instance of workfile waiting - # to be published. - assert i.data["publish"] is True, ( - "Workfile (scene) must be published along") - # determine published path from Anatomy. - template_data = i.data.get("anatomyData") - rep = i.data.get("representations")[0] - template_data["representation"] = rep.get("name") - template_data["ext"] = rep.get("ext") - template_data["comment"] = None - anatomy_filled = anatomy.format(template_data) - template_filled = anatomy_filled["publish"]["path"] - file_path = os.path.normpath(template_filled) - self.log.info("Using published scene for render {}".format( - file_path)) + is_workfile = + if not is_workfile: + continue - if not os.path.exists(file_path): - self.log.error("published scene does not exist!") - raise + # test if there is instance of workfile waiting + # to be published. + assert i.data["publish"] is True, ( + "Workfile (scene) must be published along") + # determine published path from Anatomy. + template_data = i.data.get("anatomyData") + rep = i.data.get("representations")[0] + template_data["representation"] = rep.get("name") + template_data["ext"] = rep.get("ext") + template_data["comment"] = None + anatomy_filled = anatomy.format(template_data) + template_filled = anatomy_filled["publish"]["path"] + file_path = os.path.normpath(template_filled) - if not replace_in_path: - return file_path + self.log.info("Using published scene for render {}".format( + file_path)) - # now we need to switch scene in expected files - # because token will now point to published - # scene file and that might differ from current one - new_scene = os.path.splitext( - os.path.basename(file_path))[0] - orig_scene = os.path.splitext( - os.path.basename( - self._instance.context.data["currentFile"]))[0] - exp = self._instance.data.get("expectedFiles") + if not os.path.exists(file_path): + self.log.error("published scene does not exist!") + raise - if isinstance(exp[0], dict): - # we have aovs and we need to iterate over them - new_exp = {} - for aov, files in exp[0].items(): - replaced_files = [] - for f in files: - replaced_files.append( - str(f).replace(orig_scene, new_scene) - ) - new_exp[aov] = replaced_files - # [] might be too much here, TODO - self._instance.data["expectedFiles"] = [new_exp] - else: - new_exp = [] - for f in exp: - new_exp.append( + if not replace_in_path: + return file_path + + # now we need to switch scene in expected files + # because token will now point to published + # scene file and that might differ from current one + new_scene = os.path.splitext(os.path.basename(file_path))[0] + orig_scene = os.path.splitext(os.path.basename( + self._instance.context.data["currentFile"]))[0] + exp = self._instance.data.get("expectedFiles") + + if isinstance(exp[0], dict): + # we have aovs and we need to iterate over them + new_exp = {} + for aov, files in exp[0].items(): + replaced_files = [] + for f in files: + replaced_files.append( str(f).replace(orig_scene, new_scene) ) - self._instance.data["expectedFiles"] = new_exp + new_exp[aov] = replaced_files + # [] might be too much here, TODO + self._instance.data["expectedFiles"] = [new_exp] + else: + new_exp = [] + for f in exp: + new_exp.append( + str(f).replace(orig_scene, new_scene) + ) + self._instance.data["expectedFiles"] = new_exp - self.log.info("Scene name was switched {} -> {}".format( - orig_scene, new_scene - )) + self.log.info("Scene name was switched {} -> {}".format( + orig_scene, new_scene + )) return file_path From 21a319b10c46dec6efc2aaf6fe6ac1fe09bfc512 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 19:20:33 +0200 Subject: [PATCH 133/274] added 'deprecated' to integrator labels and added new location for hero templates as note --- .../projects_schema/schemas/schema_global_publish.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index c24c88d04a..2cb0cebf95 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -649,7 +649,7 @@ { "type": "list", "key": "template_name_profiles", - "label": "Template name profiles", + "label": "Template name profiles (DEPRECATED)", "use_label_wrap": true, "object_type": { "type": "dict", @@ -754,10 +754,14 @@ "type": "list", "object_type": "text" }, + { + "type": "label", + "label": "NOTE: Hero publish template profiles settings were moved to Tools/Publish/Hero template name profiles. Please move values there." + }, { "type": "list", "key": "template_name_profiles", - "label": "Template name profiles", + "label": "Template name profiles (DEPRECATED)", "use_label_wrap": true, "object_type": { "type": "dict", From e81e3a7a1021db4e442aa3147ed03ccf8d92d8c6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 29 Aug 2022 19:22:06 +0200 Subject: [PATCH 134/274] Fix missing line --- openpype/modules/deadline/abstract_submit_deadline.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index 55e16d8d21..86eebc0d35 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -547,7 +547,10 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin): file_path = None for i in self._instance.context: - is_workfile = + is_workfile = ( + "workfile" in i.data.get("families", []) or + i.data["family"] == "workfile" + ) if not is_workfile: continue From 2933b37ef7711aac4e04284120b6c9b0ce2c9612 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 29 Aug 2022 19:34:27 +0200 Subject: [PATCH 135/274] Refactor code for readability --- .../deadline/abstract_submit_deadline.py | 124 ++++++++++-------- 1 file changed, 68 insertions(+), 56 deletions(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index 86eebc0d35..46baa9ee57 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -543,72 +543,84 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin): published. """ - anatomy = self._instance.context.data['anatomy'] - file_path = None - for i in self._instance.context: - is_workfile = ( - "workfile" in i.data.get("families", []) or - i.data["family"] == "workfile" - ) - if not is_workfile: - continue + def _get_workfile_instance(context): + """Find workfile instance in context""" + for i in context: - # test if there is instance of workfile waiting - # to be published. - assert i.data["publish"] is True, ( - "Workfile (scene) must be published along") - # determine published path from Anatomy. - template_data = i.data.get("anatomyData") - rep = i.data.get("representations")[0] - template_data["representation"] = rep.get("name") - template_data["ext"] = rep.get("ext") - template_data["comment"] = None - anatomy_filled = anatomy.format(template_data) - template_filled = anatomy_filled["publish"]["path"] - file_path = os.path.normpath(template_filled) + is_workfile = ( + "workfile" in i.data.get("families", []) or + i.data["family"] == "workfile" + ) + if not is_workfile: + continue - self.log.info("Using published scene for render {}".format( - file_path)) + # test if there is instance of workfile waiting + # to be published. + assert i.data["publish"] is True, ( + "Workfile (scene) must be published along") - if not os.path.exists(file_path): - self.log.error("published scene does not exist!") - raise + return i - if not replace_in_path: - return file_path + instance = self._instance + workfile_instance = _get_workfile_instance(instance.context) + if not workfile_instance: + return - # now we need to switch scene in expected files - # because token will now point to published - # scene file and that might differ from current one - new_scene = os.path.splitext(os.path.basename(file_path))[0] - orig_scene = os.path.splitext(os.path.basename( - self._instance.context.data["currentFile"]))[0] - exp = self._instance.data.get("expectedFiles") + # determine published path from Anatomy. + template_data = workfile_instance.data.get("anatomyData") + rep = workfile_instance.data.get("representations")[0] + template_data["representation"] = rep.get("name") + template_data["ext"] = rep.get("ext") + template_data["comment"] = None - if isinstance(exp[0], dict): - # we have aovs and we need to iterate over them - new_exp = {} - for aov, files in exp[0].items(): - replaced_files = [] - for f in files: - replaced_files.append( - str(f).replace(orig_scene, new_scene) - ) - new_exp[aov] = replaced_files - # [] might be too much here, TODO - self._instance.data["expectedFiles"] = [new_exp] - else: - new_exp = [] - for f in exp: - new_exp.append( + anatomy = instance.context.data['anatomy'] + anatomy_filled = anatomy.format(template_data) + template_filled = anatomy_filled["publish"]["path"] + file_path = os.path.normpath(template_filled) + + self.log.info("Using published scene for render {}".format(file_path)) + + if not os.path.exists(file_path): + self.log.error("published scene does not exist!") + raise + + if not replace_in_path: + return file_path + + # now we need to switch scene in expected files + # because token will now point to published + # scene file and that might differ from current one + def _clean_name(path): + return os.path.splitext(os.path.basename(path))[0] + + new_scene = _clean_name(file_path) + orig_scene = _clean_name(instance.context.data["currentFile"]) + expected_files = instance.data.get("expectedFiles") + + if isinstance(expected_files[0], dict): + # we have aovs and we need to iterate over them + new_exp = {} + for aov, files in expected_files[0].items(): + replaced_files = [] + for f in files: + replaced_files.append( str(f).replace(orig_scene, new_scene) ) - self._instance.data["expectedFiles"] = new_exp + new_exp[aov] = replaced_files + # [] might be too much here, TODO + instance.data["expectedFiles"] = [new_exp] + else: + new_exp = [] + for f in expected_files: + new_exp.append( + str(f).replace(orig_scene, new_scene) + ) + instance.data["expectedFiles"] = new_exp - self.log.info("Scene name was switched {} -> {}".format( - orig_scene, new_scene - )) + self.log.info("Scene name was switched {} -> {}".format( + orig_scene, new_scene + )) return file_path From c725ff5b42c2f3f248a6af8f835020c9efb23182 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 29 Aug 2022 19:35:05 +0200 Subject: [PATCH 136/274] Move replacing in `publishRenderMetadataFolder` to abstract base class --- openpype/modules/deadline/abstract_submit_deadline.py | 6 ++++++ .../deadline/plugins/publish/submit_maya_deadline.py | 9 --------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index 46baa9ee57..f56cf49f6d 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -618,6 +618,12 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin): ) instance.data["expectedFiles"] = new_exp + metadata_folder = instance.data.get("publishRenderMetadataFolder") + if metadata_folder: + metadata_folder = metadata_folder.replace(orig_scene, + new_scene) + instance.data["publishRenderMetadataFolder"] = metadata_folder + self.log.info("Scene name was switched {} -> {}".format( orig_scene, new_scene )) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 07ed237c94..26c26a124c 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -258,15 +258,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): ) self._patch_workfile(filepath, patches) - # todo: on self.use_published replace path for publishRenderMetadataFolder - # if instance.data.get("publishRenderMetadataFolder"): - # instance.data["publishRenderMetadataFolder"] = \ - # instance.data["publishRenderMetadataFolder"].replace( - # orig_scene, new_scene) - # self.log.info("Scene name was switched {} -> {}".format( - # orig_scene, new_scene - # )) - # Gather needed data ------------------------------------------------ workspace = context.data["workspaceDir"] default_render_file = instance.context.data.get('project_settings')\ From 23e652a51f41a6c65d6feba6edb0268f85feccb8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 29 Aug 2022 19:54:36 +0200 Subject: [PATCH 137/274] Patch plug-in payload with settings --- .../maya/plugins/publish/collect_render.py | 1 + .../plugins/publish/submit_maya_deadline.py | 17 +++++++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py index ebda5e190d..768a53329f 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -293,6 +293,7 @@ class CollectMayaRender(pyblish.api.ContextPlugin): "source": filepath, "expectedFiles": full_exp_files, "publishRenderMetadataFolder": common_publish_meta_path, + "renderProducts": layer_render_products, "resolutionWidth": lib.get_attr_in_layer( "defaultResolution.width", layer=layer_name ), diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 26c26a124c..854a66eaa5 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -215,16 +215,21 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): renderlayer = instance.data['setMembers'] # rs_beauty - self.payload_skeleton["PluginInfo"]["RenderLayer"] = renderlayer - self.payload_skeleton["PluginInfo"]["RenderSetupIncludeLights"] = instance.data.get("renderSetupIncludeLights") # noqa - # Output driver to render plugin_info = DeadlinePluginInfo( SceneFile=context.data["currentFile"], Version=cmds.about(version=True), + RenderLayer=renderlayer, + RenderSetupIncludeLights=instance.data.get("renderSetupIncludeLights") # noqa ) - return attr.asdict(plugin_info) + plugin_payload = attr.asdict(plugin_info) + + # Patching with pluginInfo from settings + for key, value in self.pluginInfo.items(): + plugin_payload[key] = value + + return plugin_payload def process_submission(self): @@ -338,10 +343,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # Store output dir for unified publisher (filesequence) instance.data["outputDir"] = os.path.dirname(output_filename_0) - # add jobInfo and pluginInfo variables from Settings - payload["JobInfo"].update(self.jobInfo) - payload["PluginInfo"].update(self.pluginInfo) - if instance.data.get("tileRendering"): # Prepare tiles data self._tile_render(instance, payload) From 4abddd027de9d4a1814ddff1b971bb9a99c47008 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 29 Aug 2022 19:55:07 +0200 Subject: [PATCH 138/274] Use collected render products for image prefix --- .../deadline/plugins/publish/submit_maya_deadline.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 854a66eaa5..bb7ae380b6 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -307,7 +307,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): "jobname": jobname, "comment": comment, "output_filename_0": output_filename_0, - "render_variables": render_variables, "renderlayer": renderlayer, "workspace": workspace, "dirname": dirname, @@ -564,6 +563,11 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): renderer = self._instance.data["renderer"] + # Get layer prefix + render_products = self._instance.data["renderProducts"] + layer_metadata = render_products.layer_data + layer_prefix = layer_metadata.filePrefix + # This hack is here because of how Deadline handles Renderman version. # it considers everything with `renderman` set as version older than # Renderman 22, and so if we are using renderman > 21 we need to set @@ -583,7 +587,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): "SceneFile": data["filepath"], # Output directory and filename "OutputFilePath": data["dirname"].replace("\\", "/"), - "OutputFilePrefix": data["render_variables"]["filename_prefix"], # noqa: E501 + "OutputFilePrefix": layer_prefix, # Only render layers are considered renderable in this pipeline "UsingRenderLayers": True, From 6f5fcecfae7ec92bf2df80fb673bfff3e1049231 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 29 Aug 2022 19:55:24 +0200 Subject: [PATCH 139/274] Use existing variable `renderer` --- .../modules/deadline/plugins/publish/submit_maya_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index bb7ae380b6..c7f91905ea 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -573,7 +573,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # Renderman 22, and so if we are using renderman > 21 we need to set # renderer string on the job to `renderman22`. We will have to change # this when Deadline releases new version handling this. - if self._instance.data["renderer"] == "renderman": + if renderer == "renderman": try: from rfm2.config import cfg # noqa except ImportError: From ae250c4a100dcc4474512913dbc858326ced3c8a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 29 Aug 2022 19:55:40 +0200 Subject: [PATCH 140/274] Remove unused `comment` key-value --- .../modules/deadline/plugins/publish/submit_maya_deadline.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index c7f91905ea..b2b877ab0e 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -305,7 +305,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): "filename": filename, "filepath": filepath, "jobname": jobname, - "comment": comment, "output_filename_0": output_filename_0, "renderlayer": renderlayer, "workspace": workspace, From 7af7f71edacea21a00c370e4ad1e92b2fe576b66 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 29 Aug 2022 19:58:04 +0200 Subject: [PATCH 141/274] Remove logging of plugin name --- .../modules/deadline/plugins/publish/submit_maya_deadline.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index b2b877ab0e..db796f25a9 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -335,9 +335,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): if export_job: payload["JobInfo"]["JobDependency0"] = export_job - plugin = payload["JobInfo"]["Plugin"] - self.log.info("using render plugin : {}".format(plugin)) - # Store output dir for unified publisher (filesequence) instance.data["outputDir"] = os.path.dirname(output_filename_0) From f91e33c0385762a7a73cca192f4d36716377ee1e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 29 Aug 2022 23:21:30 +0200 Subject: [PATCH 142/274] More refactoring/cleanup (WIP) --- .../plugins/publish/submit_maya_deadline.py | 304 +++++++----------- 1 file changed, 110 insertions(+), 194 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index db796f25a9..8f12a9518f 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -70,7 +70,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): tile_assembler_plugin = "OpenPypeTileAssembler" priority = 50 tile_priority = 50 - limit_groups = [] + limit = [] # limit groups jobInfo = {} pluginInfo = {} group = "none" @@ -112,23 +112,18 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): job_info.ChunkSize = instance.data.get("chunkSize", 10) job_info.Comment = context.data.get("comment") job_info.Priority = instance.data.get("priority", self.priority) + job_info.FramesPerTask = instance.data.get("framesPerTask", 1) if self.group != "none" and self.group: job_info.Group = self.group - if self.limit_groups: - job_info.LimitGroups = ",".join(self.limit_groups) + if self.limit: + job_info.LimitGroups = ",".join(self.limit) - # Optional, enable double-click to preview rendered - # frames from Deadline Monitor - self.payload_skeleton["JobInfo"]["OutputDirectory0"] = \ - os.path.dirname(output_filename_0).replace("\\", "/") - self.payload_skeleton["JobInfo"]["OutputFilename0"] = \ - output_filename_0.replace("\\", "/") - - # Add options from RenderGlobals------------------------------------- + # Add options from RenderGlobals render_globals = instance.data.get("renderGlobals", {}) - self.payload_skeleton["JobInfo"].update(render_globals) + for key, value in render_globals: + setattr(job_info, key, value) keys = [ "FTRACK_API_KEY", @@ -140,7 +135,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): "AVALON_TASK", "AVALON_APP_NAME", "OPENPYPE_DEV", - "OPENPYPE_LOG_NO_COLORS", "OPENPYPE_VERSION" ] # Add mongo url if it's enabled @@ -150,10 +144,8 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): environment = dict({key: os.environ[key] for key in keys if key in os.environ}, **legacy_io.Session) - # TODO: Taken from old publish class - test whether still needed environment["OPENPYPE_LOG_NO_COLORS"] = "1" - environment["OPENPYPE_MAYA_VERSION"] = cmds.about(v=True) # to recognize job from PYPE for turning Event On/Off environment["OPENPYPE_RENDER_JOB"] = "1" @@ -166,7 +158,10 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): ) # to recognize job from PYPE for turning Event On/Off job_info.EnvironmentKeyValue = "OPENPYPE_RENDER_JOB=1" + job_info.EnvironmentKeyValue = "OPENPYPE_LOG_NO_COLORS=1" + # Optional, enable double-click to preview rendered + # frames from Deadline Monitor for i, filepath in enumerate(instance.data["files"]): dirname = os.path.dirname(filepath) fname = os.path.basename(filepath) @@ -213,14 +208,13 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): instance = self._instance context = instance.context - renderlayer = instance.data['setMembers'] # rs_beauty - - # Output driver to render plugin_info = DeadlinePluginInfo( - SceneFile=context.data["currentFile"], + SceneFile=self.scene_path, Version=cmds.about(version=True), - RenderLayer=renderlayer, - RenderSetupIncludeLights=instance.data.get("renderSetupIncludeLights") # noqa + RenderLayer=instance.data['setMembers'], + RenderSetupIncludeLights=instance.data.get("renderSetupIncludeLights"), # noqa + ProjectPath=context.data["workspaceDir"], + UsingRenderLayers=True, ) plugin_payload = attr.asdict(plugin_info) @@ -236,12 +230,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): instance = self._instance context = instance.context - # Generated by AbstractSubmitDeadline. The `job_info`, `plugin_info` - # and `aux_files` are the skeleton payloads that are the basis for - # all the maya submissions - job_info = self.job_info - plugin_info = self.plugin_info - aux_files = self.aux_files filepath = self.scene_path # publish if `use_publish` else workfile # TODO: Avoid the need for this logic here, needed for submit publish @@ -250,18 +238,9 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): instance.data["outputDir"] = output_dir instance.data["toBeRenderedOn"] = "deadline" - self.limit_groups = self.limit - # Patch workfile (only when use_published is enabled) if self.use_published: - patches = ( - context.data["project_settings"].get( - "deadline", {}).get( - "publish", {}).get( - "MayaSubmitDeadline", {}).get( - "scene_patches", {}) - ) - self._patch_workfile(filepath, patches) + self._patch_workfile() # Gather needed data ------------------------------------------------ workspace = context.data["workspaceDir"] @@ -271,22 +250,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): .get('default_render_image_folder') filename = os.path.basename(filepath) dirname = os.path.join(workspace, default_render_file) - renderlayer = instance.data['setMembers'] # rs_beauty - - # Get the variables depending on the renderer - # TODO: Find replacement logic for `get_renderer_variables` through - # what is collected for the render or is implemented in maya - # api `lib_renderproducts` - render_variables = get_renderer_variables(renderlayer, dirname) - filename_0 = render_variables["filename_0"] - if self.use_published: - new_scene = os.path.splitext(filename)[0] - orig_scene = os.path.splitext( - os.path.basename(context.data["currentFile"]))[0] - filename_0 = render_variables["filename_0"].replace( - orig_scene, new_scene) - - output_filename_0 = filename_0 # this is needed because renderman handles directory and file # prefixes separately @@ -301,16 +264,18 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): pass # Fill in common data to payload ------------------------------------ + # TODO: Replace these with collected data from CollectRender payload_data = { "filename": filename, "filepath": filepath, - "jobname": jobname, "output_filename_0": output_filename_0, "renderlayer": renderlayer, - "workspace": workspace, "dirname": dirname, } + # Store output dir for unified publisher (filesequence) + instance.data["outputDir"] = os.path.dirname(output_filename_0) + # Submit preceding export jobs ------------------------------------- export_job = None assert not all(x in instance.data["families"] @@ -333,17 +298,16 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # Add export job as dependency -------------------------------------- if export_job: - payload["JobInfo"]["JobDependency0"] = export_job - - # Store output dir for unified publisher (filesequence) - instance.data["outputDir"] = os.path.dirname(output_filename_0) + job_info, _ = payload + job_info.JobDependency = export_job if instance.data.get("tileRendering"): # Prepare tiles data self._tile_render(instance, payload) else: # Submit main render job - self.submit(payload) + job_info, plugin_info = payload + self.submit(self.assemble_payload(job_info, plugin_info)) def _tile_render(self, instance, payload): @@ -546,18 +510,12 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): instance.data["jobBatchName"])) def _get_maya_payload(self, data): - payload = copy.deepcopy(self.payload_skeleton) - if not self.asset_dependencies: - job_info_ext = {} + job_info = copy.deepcopy(self.job_info) - else: - job_info_ext = { - # Asset dependency to wait for at least the scene file to sync. - "AssetDependency0": data["filepath"], - } - - renderer = self._instance.data["renderer"] + if self.asset_dependencies: + # Asset dependency to wait for at least the scene file to sync. + job_info.AssetDependency = self.scene_path # Get layer prefix render_products = self._instance.data["renderProducts"] @@ -569,6 +527,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # Renderman 22, and so if we are using renderman > 21 we need to set # renderer string on the job to `renderman22`. We will have to change # this when Deadline releases new version handling this. + renderer = self._instance.data["renderer"] if renderer == "renderman": try: from rfm2.config import cfg # noqa @@ -580,29 +539,20 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): renderer = "renderman22" plugin_info = { - "SceneFile": data["filepath"], # Output directory and filename "OutputFilePath": data["dirname"].replace("\\", "/"), "OutputFilePrefix": layer_prefix, - - # Only render layers are considered renderable in this pipeline - "UsingRenderLayers": True, - - # Render only this layer - "RenderLayer": data["renderlayer"], - - # Determine which renderer to use from the file itself - "Renderer": renderer, - - # Resolve relative references - "ProjectPath": data["workspace"], } - payload["JobInfo"].update(job_info_ext) - payload["PluginInfo"].update(plugin_info) - return payload + + return job_info, plugin_info def _get_vray_export_payload(self, data): - payload = copy.deepcopy(self.payload_skeleton) + + job_info = copy.deepcopy(self.job_info) + + job_info.Name = self._job_info_label("Export") + + # Get V-Ray settings info to compute output path vray_settings = cmds.ls(type="VRaySettingsNode") node = vray_settings[0] template = cmds.getAttr("{}.vrscene_filename".format(node)) @@ -610,34 +560,15 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): first_file = self.format_vray_output_filename(scene, template) first_file = "{}/{}".format(data["workspace"], first_file) output = os.path.dirname(first_file) - job_info_ext = { - # Job name, as seen in Monitor - "Name": "Export {} [{}-{}]".format( - data["jobname"], - int(self._instance.data["frameStartHandle"]), - int(self._instance.data["frameEndHandle"])), - "Plugin": self._instance.data.get( - "mayaRenderPlugin", "MayaPype"), - "FramesPerTask": self._instance.data.get("framesPerTask", 1) - } - - plugin_info_ext = { - # Renderer + plugin_info = { "Renderer": "vray", - # Input - "SceneFile": data["filepath"], "SkipExistingFrames": True, - "UsingRenderLayers": True, "UseLegacyRenderLayers": True, - "RenderLayer": data["renderlayer"], - "ProjectPath": data["workspace"], "OutputFilePath": output } - payload["JobInfo"].update(job_info_ext) - payload["PluginInfo"].update(plugin_info_ext) - return payload + return job_info, plugin_info def _get_arnold_export_payload(self, data): @@ -653,76 +584,55 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): script = os.path.normpath(module_path) - payload = copy.deepcopy(self.payload_skeleton) - job_info_ext = { - # Job name, as seen in Monitor - "Name": "Export {} [{}-{}]".format( - data["jobname"], - int(self._instance.data["frameStartHandle"]), - int(self._instance.data["frameEndHandle"])), + job_info = copy.deepcopy(self.job_info) + plugin_info = copy.deepcopy(self.plugin_info) - "Plugin": "Python", - "FramesPerTask": self._instance.data.get("framesPerTask", 1), - "Frames": 1 + job_info.Name = self._job_info_label("Export") + + # Force a single frame Python job + job_info.Plugin = "Python" + job_info.Frames = 1 + + # add required env vars for the export script + envs = { + "AVALON_APP_NAME": os.environ.get("AVALON_APP_NAME"), + "OPENPYPE_ASS_EXPORT_RENDER_LAYER": data["renderlayer"], + "OPENPYPE_ASS_EXPORT_SCENE_FILE": self.scene_path, + "OPENPYPE_ASS_EXPORT_OUTPUT": payload['JobInfo']['OutputFilename0'], # noqa + "OPENPYPE_ASS_EXPORT_START": int(self._instance.data["frameStartHandle"]), # noqa + "OPENPYPE_ASS_EXPORT_END": int(self._instance.data["frameEndHandle"]), # noqa + "OPENPYPE_ASS_EXPORT_STEP": 1 } + for key, value in envs.items(): + job_info.EnvironmentKeyValue = "{key}={value}".format(key=key, + value=value) - plugin_info_ext = { + plugin_info.update({ "Version": "3.6", "ScriptFile": script, "Arguments": "", "SingleFrameOnly": "True", - } - payload["JobInfo"].update(job_info_ext) - payload["PluginInfo"].update(plugin_info_ext) + }) - envs = [ - v - for k, v in payload["JobInfo"].items() - if k.startswith("EnvironmentKeyValue") - ] - - # add app name to environment - envs.append( - "AVALON_APP_NAME={}".format(os.environ.get("AVALON_APP_NAME"))) - envs.append( - "OPENPYPE_ASS_EXPORT_RENDER_LAYER={}".format(data["renderlayer"])) - envs.append( - "OPENPYPE_ASS_EXPORT_SCENE_FILE={}".format(data["filepath"])) - envs.append( - "OPENPYPE_ASS_EXPORT_OUTPUT={}".format( - payload['JobInfo']['OutputFilename0'])) - envs.append( - "OPENPYPE_ASS_EXPORT_START={}".format( - int(self._instance.data["frameStartHandle"]))) - envs.append( - "OPENPYPE_ASS_EXPORT_END={}".format( - int(self._instance.data["frameEndHandle"]))) - envs.append( - "OPENPYPE_ASS_EXPORT_STEP={}".format(1)) - - for i, e in enumerate(envs): - payload["JobInfo"]["EnvironmentKeyValue{}".format(i)] = e - return payload + return job_info, plugin_info def _get_vray_render_payload(self, data): - payload = copy.deepcopy(self.payload_skeleton) + + # Job Info + job_info = copy.deepcopy(self.job_info) + job_info.Name = self._job_info_label("Render") + job_info.Plugin = "Vray" + job_info.OverrideTaskExtraInfoNames = False + + # Plugin Info vray_settings = cmds.ls(type="VRaySettingsNode") node = vray_settings[0] template = cmds.getAttr("{}.vrscene_filename".format(node)) # "vrayscene//_/" - scene, _ = os.path.splitext(data["filename"]) + scene, _ = os.path.splitext(self.scene_path) first_file = self.format_vray_output_filename(scene, template) first_file = "{}/{}".format(data["workspace"], first_file) - job_info_ext = { - "Name": "Render {} [{}-{}]".format( - data["jobname"], - int(self._instance.data["frameStartHandle"]), - int(self._instance.data["frameEndHandle"])), - - "Plugin": "Vray", - "OverrideTaskExtraInfoNames": False, - } plugin_info = { "InputFilename": first_file, @@ -731,35 +641,28 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): "Width": self._instance.data["resolutionWidth"], "Height": self._instance.data["resolutionHeight"], - "OutputFilePath": payload["JobInfo"]["OutputDirectory0"], - "OutputFileName": payload["JobInfo"]["OutputFilename0"] + "OutputFilePath": job_info.OutputDirectory[0], + "OutputFileName": job_info.OutputFilename[0] } - payload["JobInfo"].update(job_info_ext) - payload["PluginInfo"].update(plugin_info) - return payload + return job_info, plugin_info def _get_arnold_render_payload(self, data): - payload = copy.deepcopy(self.payload_skeleton) + + # Job Info + job_info = copy.deepcopy(self.job_info) + job_info.Name = self._job_info_label("Render") + job_info.Plugin = "Arnold" + job_info.OverrideTaskExtraInfoNames = False + + # Plugin Info ass_file, _ = os.path.splitext(data["output_filename_0"]) first_file = ass_file + ".ass" - job_info_ext = { - "Name": "Render {} [{}-{}]".format( - data["jobname"], - int(self._instance.data["frameStartHandle"]), - int(self._instance.data["frameEndHandle"])), - - "Plugin": "Arnold", - "OverrideTaskExtraInfoNames": False, - } - plugin_info = { "ArnoldFile": first_file, } - payload["JobInfo"].update(job_info_ext) - payload["PluginInfo"].update(plugin_info) - return payload + return job_info, plugin_info def format_vray_output_filename(self, filename, template, dir=False): """Format the expected output file of the Export job. @@ -804,7 +707,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): return result - def _patch_workfile(self, file, patches): + def _patch_workfile(self): # type: (str, dict) -> [str, None] """Patch Maya scene. @@ -818,19 +721,25 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): "line": "line to insert" } - Args: - file (str): File to patch. - patches (dict): Dictionary defining patches. - - Returns: - str: Patched file path or None - """ - if not patches or os.path.splitext(file)[1].lower() != ".ma": + project_settings = self._instance.context.data["project_settings"] + patches = ( + project_settings.get( + "deadline", {}).get( + "publish", {}).get( + "MayaSubmitDeadline", {}).get( + "scene_patches", {}) + ) + if not patches: + return + + if not os.path.splitext(self.scene_path)[1].lower() != ".ma": + self.log.debug("Skipping workfile patch since workfile is not " + ".ma file") return compiled_regex = [re.compile(p["regex"]) for p in patches] - with open(file, "r+") as pf: + with open(self.scene_path, "r+") as pf: scene_data = pf.readlines() for ln, line in enumerate(scene_data): for i, r in enumerate(compiled_regex): @@ -839,10 +748,17 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): pf.seek(0) pf.writelines(scene_data) pf.truncate() - self.log.info( - "Applied {} patch to scene.".format( - patches[i]["name"])) - return file + self.log.info("Applied {} patch to scene.".format( + patches[i]["name"] + )) + + def _job_info_label(self, label): + return "{label} {job.Name} [{start}-{end}]".format( + label=label, + job=self.job_info, + start=int(self._instance.data["frameStartHandle"]), + end=int(self._instance.data["frameEndHandle"]), + ) def _format_tiles( From f9bbda244bee373dd3bfb025528923d061808525 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 29 Aug 2022 23:22:19 +0200 Subject: [PATCH 143/274] More explicit PluginInfo name --- .../modules/deadline/plugins/publish/submit_maya_deadline.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 8f12a9518f..87ef4e6db9 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -38,7 +38,7 @@ from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo @attr.s -class DeadlinePluginInfo(): +class MayaPluginInfo: SceneFile = attr.ib(default=None) # Input OutputFilePath = attr.ib(default=None) # Output directory and filename OutputFilePrefix = attr.ib(default=None) @@ -208,7 +208,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): instance = self._instance context = instance.context - plugin_info = DeadlinePluginInfo( + plugin_info = MayaPluginInfo( SceneFile=self.scene_path, Version=cmds.about(version=True), RenderLayer=instance.data['setMembers'], From ecf2a89081f19e14c65b0fd7b1992fe80519e983 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 29 Aug 2022 23:39:11 +0200 Subject: [PATCH 144/274] More temp restructuring --- .../plugins/publish/submit_maya_deadline.py | 52 +++++++------------ 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 87ef4e6db9..a77ccd73d4 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -273,9 +273,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): "dirname": dirname, } - # Store output dir for unified publisher (filesequence) - instance.data["outputDir"] = os.path.dirname(output_filename_0) - # Submit preceding export jobs ------------------------------------- export_job = None assert not all(x in instance.data["families"] @@ -326,26 +323,19 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): plugin_info["ImageWidth"] = instance.data.get("resolutionWidth") plugin_info["RegionRendering"] = True - assembly_payload = { - "AuxFiles": [], - "JobInfo": { - "BatchName": payload["JobInfo"]["BatchName"], - "Frames": 1, - "Name": "{} - Tile Assembly Job".format( - payload["JobInfo"]["Name"]), - "OutputDirectory0": - payload["JobInfo"]["OutputDirectory0"].replace( - "\\", "/"), - "Plugin": self.tile_assembler_plugin, - "MachineLimit": 1 - }, - "PluginInfo": { + assembly_job_info = copy.deepcopy(job_info) + assembly_job_info.Plugin = self.tile_assembler_plugin + assembly_job_info.Name = "{job.Name} - Tile Assembly Job".format( + job=job_info) + assembly_job_info.Frames = 1 + assembly_job_info.MachineLimit = 1 + assembly_job_info.Priority = instance.data.get("tile_priority", + self.tile_priority) + + assembly_plugin_info = { "CleanupTiles": 1, "ErrorOnMissing": True - } } - assembly_payload["JobInfo"]["Priority"] = self._instance.data.get( - "tile_priority", self.tile_priority) frame_payloads = [] assembly_payloads = [] @@ -414,6 +404,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): file_index = 1 for file in assembly_files: frame = re.search(R_FRAME_NUMBER, file).group("frame") + new_assembly_payload = copy.deepcopy(assembly_payload) new_assembly_payload["JobInfo"]["Name"] = \ "{} (Frame {})".format( @@ -434,7 +425,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): self.log.info( "Submitting tile job(s) [{}] ...".format(len(frame_payloads))) - url = "{}/api/jobs".format(self.deadline_url) tiles_count = instance.data.get("tilesX") * instance.data.get( "tilesY") # noqa: E501 @@ -444,9 +434,11 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): job_id = response.json()["_id"] hash = response.json()["Props"]["Ex0"] + # Add assembly job dependencies for assembly_job in assembly_payloads: - if assembly_job["JobInfo"]["ExtraInfo0"] == hash: - assembly_job["JobInfo"]["JobDependency0"] = job_id + assembly_job_info = assembly_job["JobInfo"] + if assembly_job_info.ExtraInfo[0] == hash: + assembly_job.JobDependency = job_id for assembly_job in assembly_payloads: file = assembly_job["JobInfo"]["ExtraInfo1"] @@ -461,14 +453,14 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): ) ) + config_file_dir = os.path.dirname(config_file) try: - if not os.path.isdir(os.path.dirname(config_file)): - os.makedirs(os.path.dirname(config_file)) + if not os.path.isdir(config_file_dir): + os.makedirs(config_file_dir) except OSError: # directory is not available - self.log.warning( - "Path is unreachable: `{}`".format( - os.path.dirname(config_file))) + self.log.warning("Path is unreachable: " + "`{}`".format(config_file_dir)) # add config file as job auxFile assembly_job["AuxFiles"] = [config_file] @@ -505,10 +497,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): response.json()["_id"]) job_idx += 1 - instance.data["jobBatchName"] = payload["JobInfo"]["BatchName"] - self.log.info("Setting batch name on instance: {}".format( - instance.data["jobBatchName"])) - def _get_maya_payload(self, data): job_info = copy.deepcopy(self.job_info) From 6abafd0aca1ca06204f5e5bc11907a0a6a855900 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 00:41:30 +0200 Subject: [PATCH 145/274] Refactor tile logic --- .../plugins/publish/submit_maya_deadline.py | 120 ++++++++---------- 1 file changed, 55 insertions(+), 65 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index a77ccd73d4..920adf7e4a 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -314,11 +314,12 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # if we have sequence of files, we need to create tile job for # every frame - job_info.TileJob = True job_info.TileJobTilesInX = instance.data.get("tilesX") job_info.TileJobTilesInY = instance.data.get("tilesY") + tiles_count = job_info.TileJobTilesInX * job_info.TileJobTilesInY + plugin_info["ImageHeight"] = instance.data.get("resolutionHeight") plugin_info["ImageWidth"] = instance.data.get("resolutionWidth") plugin_info["RegionRendering"] = True @@ -334,7 +335,8 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): assembly_plugin_info = { "CleanupTiles": 1, - "ErrorOnMissing": True + "ErrorOnMissing": True, + "Renderer": self._instance.data["renderer"] } frame_payloads = [] @@ -367,81 +369,69 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): file_index = 1 for file in files: frame = re.search(R_FRAME_NUMBER, file).group("frame") - new_payload = copy.deepcopy(payload) - new_payload["JobInfo"]["Name"] = \ - "{} (Frame {} - {} tiles)".format( + + new_job_info = copy.deepcopy(job_info) + new_job_info.Name = "{} (Frame {} - {} tiles)".format( payload["JobInfo"]["Name"], frame, instance.data.get("tilesX") * instance.data.get("tilesY") - # noqa: E501 - ) - self.log.info( - "... preparing job {}".format( - new_payload["JobInfo"]["Name"])) - new_payload["JobInfo"]["TileJobFrame"] = frame + ) + new_job_info.TileJobFrame = frame - tiles_data = _format_tiles( + new_plugin_info = copy.deepcopy(plugin_info) + + # Add tile data into job info and plugin info + tiles_out, _ = _format_tiles( file, 0, instance.data.get("tilesX"), instance.data.get("tilesY"), instance.data.get("resolutionWidth"), instance.data.get("resolutionHeight"), payload["PluginInfo"]["OutputFilePrefix"] - )[0] - new_payload["JobInfo"].update(tiles_data["JobInfo"]) - new_payload["PluginInfo"].update(tiles_data["PluginInfo"]) + ) + new_job_info.update(tiles_out["JobInfo"]) + new_plugin_info.update(tiles_out["PluginInfo"]) self.log.info("hashing {} - {}".format(file_index, file)) job_hash = hashlib.sha256( ("{}_{}".format(file_index, file)).encode("utf-8")) frame_jobs[frame] = job_hash.hexdigest() - new_payload["JobInfo"]["ExtraInfo0"] = job_hash.hexdigest() - new_payload["JobInfo"]["ExtraInfo1"] = file - frame_payloads.append(new_payload) - file_index += 1 + new_job_info.ExtraInfo[0] = job_hash.hexdigest() + new_job_info.ExtraInfo[1] = file - file_index = 1 - for file in assembly_files: - frame = re.search(R_FRAME_NUMBER, file).group("frame") - - new_assembly_payload = copy.deepcopy(assembly_payload) - new_assembly_payload["JobInfo"]["Name"] = \ - "{} (Frame {})".format( - assembly_payload["JobInfo"]["Name"], - frame) - new_assembly_payload["JobInfo"]["OutputFilename0"] = re.sub( - REPL_FRAME_NUMBER, - "\\1{}\\3".format("#" * len(frame)), file) - - new_assembly_payload["PluginInfo"]["Renderer"] = \ - self._instance.data["renderer"] # noqa: E501 - new_assembly_payload["JobInfo"]["ExtraInfo0"] = frame_jobs[ - frame] # noqa: E501 - new_assembly_payload["JobInfo"]["ExtraInfo1"] = file - assembly_payloads.append(new_assembly_payload) + frame_payloads.append(self.assemble_payload( + job_info=new_job_info, + plugin_info=new_plugin_info + )) file_index += 1 self.log.info( "Submitting tile job(s) [{}] ...".format(len(frame_payloads))) - tiles_count = instance.data.get("tilesX") * instance.data.get( - "tilesY") # noqa: E501 - - for tile_job in frame_payloads: - response = self.submit(tile_job) - + frame_tile_job_id = {} + for tile_job_payload in frame_payloads: + response = self.submit(tile_job_payload) job_id = response.json()["_id"] - hash = response.json()["Props"]["Ex0"] + frame_tile_job_id[frame] = job_id - # Add assembly job dependencies - for assembly_job in assembly_payloads: - assembly_job_info = assembly_job["JobInfo"] - if assembly_job_info.ExtraInfo[0] == hash: - assembly_job.JobDependency = job_id + assembly_jobs = [] + for i, file in enumerate(assembly_files): + frame = re.search(R_FRAME_NUMBER, file).group("frame") + + frame_assembly_job_info = copy.deepcopy(assembly_job_info) + frame_assembly_job_info.Name += " (Frame {})".format(frame) + frame_assembly_job_info.OutputFilename[0] = re.sub( + REPL_FRAME_NUMBER, + "\\1{}\\3".format("#" * len(frame)), file) + + hash = frame_jobs[frame] + tile_job_id = frame_tile_job_id[frame] + + frame_assembly_job_info.ExtraInfo[0] = hash + frame_assembly_job_info.ExtraInfo[1] = file + frame_assembly_job_info.JobDependency = tile_job_id - for assembly_job in assembly_payloads: - file = assembly_job["JobInfo"]["ExtraInfo1"] # write assembly job config files now = datetime.now() @@ -462,9 +452,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): self.log.warning("Path is unreachable: " "`{}`".format(config_file_dir)) - # add config file as job auxFile - assembly_job["AuxFiles"] = [config_file] - with open(config_file, "w") as cf: print("TileCount={}".format(tiles_count), file=cf) print("ImageFileName={}".format(file), file=cf) @@ -485,17 +472,20 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): for k, v in tiles.items(): print("{}={}".format(k, v), file=cf) - job_idx = 1 - instance.data["assemblySubmissionJobs"] = [] - for ass_job in assembly_payloads: - self.log.info("submitting assembly job {} of {}".format( - job_idx, len(assembly_payloads) - )) - response = self.submit(ass_job) + payload = self.assemble_payload( + job_info=frame_assembly_job_info, + plugin_info=assembly_plugin_info.copy(), + # add config file as job auxFile + aux_files=[config_file] + ) - instance.data["assemblySubmissionJobs"].append( - response.json()["_id"]) - job_idx += 1 + self.log.info("submitting assembly job {} of {}".format( + i+1, len(assembly_payloads) + )) + response = self.submit(payload) + assembly_jobs.append(response.json()["_id"]) + + instance.data["assemblySubmissionJobs"] = assembly_jobs def _get_maya_payload(self, data): From a6002de641e1ad500192be433f620f17680ea056 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 00:58:23 +0200 Subject: [PATCH 146/274] Refactor _format_tiles for readability --- .../plugins/publish/submit_maya_deadline.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 920adf7e4a..00d8eb7859 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -800,23 +800,23 @@ def _format_tiles( tiles_x, tiles_y ) - out_tile_index = "OutputFilename{}Tile{}".format( - str(index), tile - ) + + # Job Info new_filename = "{}/{}{}".format( os.path.dirname(filename), tile_prefix, os.path.basename(filename) ) - out["JobInfo"][out_tile_index] = new_filename - out["PluginInfo"]["RegionPrefix{}".format(tile)] = \ - "/{}".format(tile_prefix).join(prefix.rsplit("/", 1)) + out["JobInfo"]["OutputFilename{}Tile{}".format(index, tile)] = new_filename # noqa + # Plugin Info + out["PluginInfo"]["RegionPrefix{}".format(tile)] = "/{}".format(tile_prefix).join(prefix.rsplit("/", 1)) # noqa: E501 out["PluginInfo"]["RegionTop{}".format(tile)] = int(height) - (tile_y * h_space) # noqa: E501 out["PluginInfo"]["RegionBottom{}".format(tile)] = int(height) - ((tile_y - 1) * h_space) - 1 # noqa: E501 out["PluginInfo"]["RegionLeft{}".format(tile)] = (tile_x - 1) * w_space # noqa: E501 out["PluginInfo"]["RegionRight{}".format(tile)] = (tile_x * w_space) - 1 # noqa: E501 + # Tile config cfg["Tile{}".format(tile)] = new_filename cfg["Tile{}Tile".format(tile)] = new_filename cfg["Tile{}FileName".format(tile)] = new_filename @@ -828,4 +828,5 @@ def _format_tiles( cfg["Tile{}Height".format(tile)] = h_space tile += 1 + return out, cfg From a9fe806fec1a5e5ecf98327cfa4845b8b6d3edc0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 01:02:24 +0200 Subject: [PATCH 147/274] Calculate once --- .../plugins/publish/submit_maya_deadline.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 00d8eb7859..d0348119dc 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -800,6 +800,10 @@ def _format_tiles( tiles_x, tiles_y ) + top = int(height) - (tile_y * h_space) + bottom = int(height) - ((tile_y - 1) * h_space) - 1 + left = (tile_x - 1) * w_space + right = (tile_x * w_space) - 1 # Job Info new_filename = "{}/{}{}".format( @@ -811,19 +815,17 @@ def _format_tiles( # Plugin Info out["PluginInfo"]["RegionPrefix{}".format(tile)] = "/{}".format(tile_prefix).join(prefix.rsplit("/", 1)) # noqa: E501 - out["PluginInfo"]["RegionTop{}".format(tile)] = int(height) - (tile_y * h_space) # noqa: E501 - out["PluginInfo"]["RegionBottom{}".format(tile)] = int(height) - ((tile_y - 1) * h_space) - 1 # noqa: E501 - out["PluginInfo"]["RegionLeft{}".format(tile)] = (tile_x - 1) * w_space # noqa: E501 - out["PluginInfo"]["RegionRight{}".format(tile)] = (tile_x * w_space) - 1 # noqa: E501 + out["PluginInfo"]["RegionTop{}".format(tile)] = top + out["PluginInfo"]["RegionBottom{}".format(tile)] = bottom + out["PluginInfo"]["RegionLeft{}".format(tile)] = left + out["PluginInfo"]["RegionRight{}".format(tile)] = right # Tile config cfg["Tile{}".format(tile)] = new_filename cfg["Tile{}Tile".format(tile)] = new_filename cfg["Tile{}FileName".format(tile)] = new_filename - cfg["Tile{}X".format(tile)] = (tile_x - 1) * w_space - - cfg["Tile{}Y".format(tile)] = int(height) - (tile_y * h_space) - + cfg["Tile{}X".format(tile)] = left + cfg["Tile{}Y".format(tile)] = top cfg["Tile{}Width".format(tile)] = w_space cfg["Tile{}Height".format(tile)] = h_space From d7c72f97b30f85aca15b4a8148c140595f0b2a3a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 01:10:19 +0200 Subject: [PATCH 148/274] Batch submit assembly jobs --- .../plugins/publish/submit_maya_deadline.py | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index d0348119dc..265c0f79ec 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -339,7 +339,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): "Renderer": self._instance.data["renderer"] } - frame_payloads = [] assembly_payloads = [] R_FRAME_NUMBER = re.compile( @@ -358,14 +357,15 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): itertools.chain.from_iterable( [f for _, f in exp[0].items()])) if not files: - # if beauty doesn't exists, use first aov we found + # if beauty doesn't exist, use first aov we found files = exp[0].get(list(exp[0].keys())[0]) else: files = exp assembly_files = files + # Define frame tile jobs frame_jobs = {} - + frame_payloads = {} file_index = 1 for file in files: frame = re.search(R_FRAME_NUMBER, file).group("frame") @@ -400,22 +400,24 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): new_job_info.ExtraInfo[0] = job_hash.hexdigest() new_job_info.ExtraInfo[1] = file - frame_payloads.append(self.assemble_payload( + frame_payloads[frame] = self.assemble_payload( job_info=new_job_info, plugin_info=new_plugin_info - )) + ) file_index += 1 self.log.info( "Submitting tile job(s) [{}] ...".format(len(frame_payloads))) + # Submit frame tile jobs frame_tile_job_id = {} - for tile_job_payload in frame_payloads: + for frame, tile_job_payload in frame_payloads.items(): response = self.submit(tile_job_payload) job_id = response.json()["_id"] frame_tile_job_id[frame] = job_id - assembly_jobs = [] + # Define assembly payloads + assembly_payloads = [] for i, file in enumerate(assembly_files): frame = re.search(R_FRAME_NUMBER, file).group("frame") @@ -478,14 +480,18 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # add config file as job auxFile aux_files=[config_file] ) + assembly_payloads.append(payload) + # Submit assembly jobs + assembly_job_ids = [] + for i, payload in enumerate(assembly_payloads): self.log.info("submitting assembly job {} of {}".format( i+1, len(assembly_payloads) )) response = self.submit(payload) - assembly_jobs.append(response.json()["_id"]) + assembly_job_ids.append(response.json()["_id"]) - instance.data["assemblySubmissionJobs"] = assembly_jobs + instance.data["assemblySubmissionJobs"] = assembly_job_ids def _get_maya_payload(self, data): From 965522585b98e441907480caee57af5dad92c2d2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 01:11:12 +0200 Subject: [PATCH 149/274] Remove redundant docstring --- .../deadline/plugins/publish/submit_maya_deadline.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 265c0f79ec..cd9f426977 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -51,16 +51,6 @@ class MayaPluginInfo: class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): - """Submit available render layers to Deadline. - - Renders are submitted to a Deadline Web Service as - supplied via settings key "DEADLINE_REST_URL". - - Attributes: - use_published (bool): Use published scene to render instead of the - one in work area. - - """ label = "Submit Render to Deadline" hosts = ["maya"] From 8af88e115723e6abc73fff279773043e4a520326 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 09:08:44 +0200 Subject: [PATCH 150/274] More cleanup --- .../plugins/publish/submit_maya_deadline.py | 98 +++++++------------ 1 file changed, 38 insertions(+), 60 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index cd9f426977..95140a082f 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -86,8 +86,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): job_info.Name = "%s - %s" % (src_filename, instance.name) job_info.BatchName = src_filename job_info.Plugin = instance.data.get("mayaRenderPlugin", "MayaBatch") - job_info.UserName = context.data.get( - "deadlineUser", getpass.getuser()) + job_info.UserName = context.data.get("deadlineUser", getpass.getuser()) # Deadline requires integers in frame range frames = "{start}-{end}x{step}".format( @@ -134,25 +133,18 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): environment = dict({key: os.environ[key] for key in keys if key in os.environ}, **legacy_io.Session) - # TODO: Taken from old publish class - test whether still needed - environment["OPENPYPE_LOG_NO_COLORS"] = "1" # to recognize job from PYPE for turning Event On/Off environment["OPENPYPE_RENDER_JOB"] = "1" + environment["OPENPYPE_LOG_NO_COLORS"] = "1" - for key in keys: - val = environment.get(key) - if val: - job_info.EnvironmentKeyValue = "{key}={value}".format( - key=key, - value=val - ) - # to recognize job from PYPE for turning Event On/Off - job_info.EnvironmentKeyValue = "OPENPYPE_RENDER_JOB=1" - job_info.EnvironmentKeyValue = "OPENPYPE_LOG_NO_COLORS=1" + for key, value in environment.items(): + if not value: + continue + job_info.EnvironmentKeyValue = "{key}={value}".format(key=key, + value=value) - # Optional, enable double-click to preview rendered - # frames from Deadline Monitor - for i, filepath in enumerate(instance.data["files"]): + # Enable double-click to preview rendered frames from Deadline Monitor + for filepath in instance.data["files"]: dirname = os.path.dirname(filepath) fname = os.path.basename(filepath) job_info.OutputDirectory = dirname.replace("\\", "/") @@ -241,25 +233,10 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): filename = os.path.basename(filepath) dirname = os.path.join(workspace, default_render_file) - # this is needed because renderman handles directory and file - # prefixes separately - if self._instance.data["renderer"] == "renderman": - dirname = os.path.dirname(output_filename_0) - - # Create render folder ---------------------------------------------- - try: - # Ensure render folder exists - os.makedirs(dirname) - except OSError: - pass - # Fill in common data to payload ------------------------------------ # TODO: Replace these with collected data from CollectRender payload_data = { "filename": filename, - "filepath": filepath, - "output_filename_0": output_filename_0, - "renderlayer": renderlayer, "dirname": dirname, } @@ -299,8 +276,8 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): def _tile_render(self, instance, payload): # As collected by super process() - job_info = self.job_info - plugin_info = self.pluginInfo + job_info = copy.deepcopy(self.job_info) + plugin_info = copy.deepcopy(self.plugin_info) # if we have sequence of files, we need to create tile job for # every frame @@ -314,23 +291,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): plugin_info["ImageWidth"] = instance.data.get("resolutionWidth") plugin_info["RegionRendering"] = True - assembly_job_info = copy.deepcopy(job_info) - assembly_job_info.Plugin = self.tile_assembler_plugin - assembly_job_info.Name = "{job.Name} - Tile Assembly Job".format( - job=job_info) - assembly_job_info.Frames = 1 - assembly_job_info.MachineLimit = 1 - assembly_job_info.Priority = instance.data.get("tile_priority", - self.tile_priority) - - assembly_plugin_info = { - "CleanupTiles": 1, - "ErrorOnMissing": True, - "Renderer": self._instance.data["renderer"] - } - - assembly_payloads = [] - R_FRAME_NUMBER = re.compile( r".+\.(?P[0-9]+)\..+") # noqa: N806, E501 REPL_FRAME_NUMBER = re.compile( @@ -407,7 +367,23 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): frame_tile_job_id[frame] = job_id # Define assembly payloads + assembly_job_info = copy.deepcopy(job_info) + assembly_job_info.Plugin = self.tile_assembler_plugin + assembly_job_info.Name = "{job.Name} - Tile Assembly Job".format( + job=job_info) + assembly_job_info.Frames = 1 + assembly_job_info.MachineLimit = 1 + assembly_job_info.Priority = instance.data.get("tile_priority", + self.tile_priority) + + assembly_plugin_info = { + "CleanupTiles": 1, + "ErrorOnMissing": True, + "Renderer": self._instance.data["renderer"] + } + assembly_payloads = [] + output_dir = self.job_info.OutputDirectory[0] for i, file in enumerate(assembly_files): frame = re.search(R_FRAME_NUMBER, file).group("frame") @@ -427,22 +403,19 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # write assembly job config files now = datetime.now() - config_file = os.path.join( - os.path.dirname(output_filename_0), + config_file = os.path.join(output_dir, "{}_config_{}.txt".format( os.path.splitext(file)[0], now.strftime("%Y_%m_%d_%H_%M_%S") ) ) - - config_file_dir = os.path.dirname(config_file) try: - if not os.path.isdir(config_file_dir): - os.makedirs(config_file_dir) + if not os.path.isdir(output_dir): + os.makedirs(output_dir) except OSError: # directory is not available self.log.warning("Path is unreachable: " - "`{}`".format(config_file_dir)) + "`{}`".format(output_dir)) with open(config_file, "w") as cf: print("TileCount={}".format(tiles_count), file=cf) @@ -567,17 +540,22 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): job_info.Plugin = "Python" job_info.Frames = 1 + renderlayer = self._instance.data["setMembers"] + # add required env vars for the export script envs = { "AVALON_APP_NAME": os.environ.get("AVALON_APP_NAME"), - "OPENPYPE_ASS_EXPORT_RENDER_LAYER": data["renderlayer"], + "OPENPYPE_ASS_EXPORT_RENDER_LAYER": renderlayer, "OPENPYPE_ASS_EXPORT_SCENE_FILE": self.scene_path, - "OPENPYPE_ASS_EXPORT_OUTPUT": payload['JobInfo']['OutputFilename0'], # noqa + "OPENPYPE_ASS_EXPORT_OUTPUT": job_info.OutputFilename[0], "OPENPYPE_ASS_EXPORT_START": int(self._instance.data["frameStartHandle"]), # noqa "OPENPYPE_ASS_EXPORT_END": int(self._instance.data["frameEndHandle"]), # noqa "OPENPYPE_ASS_EXPORT_STEP": 1 } for key, value in envs.items(): + if not value: + continue + job_info.EnvironmentKeyValue = "{key}={value}".format(key=key, value=value) From e8aa926cb7d338427ce7ba558f8ffa1609fde8ef Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 09:28:42 +0200 Subject: [PATCH 151/274] Move single use of in-line function to the class for readability --- .../deadline/abstract_submit_deadline.py | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index f56cf49f6d..a3db3feac9 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -544,26 +544,8 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin): """ - def _get_workfile_instance(context): - """Find workfile instance in context""" - for i in context: - - is_workfile = ( - "workfile" in i.data.get("families", []) or - i.data["family"] == "workfile" - ) - if not is_workfile: - continue - - # test if there is instance of workfile waiting - # to be published. - assert i.data["publish"] is True, ( - "Workfile (scene) must be published along") - - return i - instance = self._instance - workfile_instance = _get_workfile_instance(instance.context) + workfile_instance = self._get_workfile_instance(instance.context) if not workfile_instance: return @@ -689,3 +671,22 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin): self._instance.data["deadlineSubmissionJob"] = result return result["_id"] + + @staticmethod + def _get_workfile_instance(context): + """Find workfile instance in context""" + for i in context: + + is_workfile = ( + "workfile" in i.data.get("families", []) or + i.data["family"] == "workfile" + ) + if not is_workfile: + continue + + # test if there is instance of workfile waiting + # to be published. + assert i.data["publish"] is True, ( + "Workfile (scene) must be published along") + + return i From c6a0a199e1810e5c63c484d5871d34d14594e5be Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 09:40:08 +0200 Subject: [PATCH 152/274] Cosmetics --- .../deadline/plugins/publish/submit_maya_deadline.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 95140a082f..873005e051 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -267,15 +267,16 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): if instance.data.get("tileRendering"): # Prepare tiles data - self._tile_render(instance, payload) + self._tile_render(payload) else: # Submit main render job job_info, plugin_info = payload self.submit(self.assemble_payload(job_info, plugin_info)) - def _tile_render(self, instance, payload): + def _tile_render(self, payload): # As collected by super process() + instance = self._instance job_info = copy.deepcopy(self.job_info) plugin_info = copy.deepcopy(self.plugin_info) @@ -321,11 +322,8 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): frame = re.search(R_FRAME_NUMBER, file).group("frame") new_job_info = copy.deepcopy(job_info) - new_job_info.Name = "{} (Frame {} - {} tiles)".format( - payload["JobInfo"]["Name"], - frame, - instance.data.get("tilesX") * instance.data.get("tilesY") - ) + new_job_info.Name += " (Frame {} - {} tiles)".format(frame, + tiles_count) new_job_info.TileJobFrame = frame new_plugin_info = copy.deepcopy(plugin_info) From e429e2ec41d23c097b301c31485a38aea634d6a3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 10:19:38 +0200 Subject: [PATCH 153/274] Remove json dump since `renderProducts` are not serializable --- openpype/hosts/maya/plugins/publish/collect_render.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py index 768a53329f..14aac2f206 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -360,7 +360,6 @@ class CollectMayaRender(pyblish.api.ContextPlugin): instance.data["label"] = label instance.data["farm"] = True instance.data.update(data) - self.log.debug("data: {}".format(json.dumps(data, indent=4))) def parse_options(self, render_globals): """Get all overrides with a value, skip those without. From 40a4262916043b364dbd697b30713a1154692da3 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Tue, 30 Aug 2022 08:20:59 +0000 Subject: [PATCH 154/274] [Automated] Bump version --- CHANGELOG.md | 18 +++++++----------- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a8e962085..0a7d93711a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [3.14.1-nightly.3](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.14.1-nightly.4](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.0...HEAD) @@ -12,33 +12,37 @@ **🆕 New features** - Webpublisher:change create flatten image into tri state [\#3678](https://github.com/pypeclub/OpenPype/pull/3678) +- Blender: validators code correction with settings and defaults [\#3662](https://github.com/pypeclub/OpenPype/pull/3662) **🚀 Enhancements** +- General: Thumbnail can use project roots [\#3750](https://github.com/pypeclub/OpenPype/pull/3750) - Settings: Remove settings lock on tray exit [\#3720](https://github.com/pypeclub/OpenPype/pull/3720) - General: Added helper getters to modules manager [\#3712](https://github.com/pypeclub/OpenPype/pull/3712) - Unreal: Define unreal as module and use host class [\#3701](https://github.com/pypeclub/OpenPype/pull/3701) - Settings: Lock settings UI session [\#3700](https://github.com/pypeclub/OpenPype/pull/3700) - General: Benevolent context label collector [\#3686](https://github.com/pypeclub/OpenPype/pull/3686) - Ftrack: Store ftrack entities on hierarchy integration to instances [\#3677](https://github.com/pypeclub/OpenPype/pull/3677) -- Ftrack: More logs related to auto sync value change [\#3671](https://github.com/pypeclub/OpenPype/pull/3671) - Blender: ops refresh manager after process events [\#3663](https://github.com/pypeclub/OpenPype/pull/3663) **🐛 Bug fixes** +- Maya: Fix typo in getPanel argument `with\_focus` -\> `withFocus` [\#3753](https://github.com/pypeclub/OpenPype/pull/3753) +- General: Smaller fixes of imports [\#3748](https://github.com/pypeclub/OpenPype/pull/3748) - General: Logger tweaks [\#3741](https://github.com/pypeclub/OpenPype/pull/3741) +- Nuke: missing job dependency if multiple bake streams [\#3737](https://github.com/pypeclub/OpenPype/pull/3737) - Nuke: color-space settings from anatomy is working [\#3721](https://github.com/pypeclub/OpenPype/pull/3721) - Settings: Fix studio default anatomy save [\#3716](https://github.com/pypeclub/OpenPype/pull/3716) - Maya: Use project name instead of project code [\#3709](https://github.com/pypeclub/OpenPype/pull/3709) - Settings: Fix project overrides save [\#3708](https://github.com/pypeclub/OpenPype/pull/3708) - Workfiles tool: Fix published workfile filtering [\#3704](https://github.com/pypeclub/OpenPype/pull/3704) - PS, AE: Provide default variant value for workfile subset [\#3703](https://github.com/pypeclub/OpenPype/pull/3703) -- RoyalRender: handle host name that is not set [\#3695](https://github.com/pypeclub/OpenPype/pull/3695) - Flame: retime is working on clip publishing [\#3684](https://github.com/pypeclub/OpenPype/pull/3684) - Webpublisher: added check for empty context [\#3682](https://github.com/pypeclub/OpenPype/pull/3682) **🔀 Refactored code** +- General: Move delivery logic to pipeline [\#3751](https://github.com/pypeclub/OpenPype/pull/3751) - General: Host addons cleanup [\#3744](https://github.com/pypeclub/OpenPype/pull/3744) - Webpublisher: Webpublisher is used as addon [\#3740](https://github.com/pypeclub/OpenPype/pull/3740) - Photoshop: Defined photoshop as addon [\#3736](https://github.com/pypeclub/OpenPype/pull/3736) @@ -105,10 +109,6 @@ [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.13.0-nightly.1...3.13.0) -**🆕 New features** - -- Support for mutliple installed versions - 3.13 [\#3605](https://github.com/pypeclub/OpenPype/pull/3605) - **🚀 Enhancements** - Editorial: Mix audio use side file for ffmpeg filters [\#3630](https://github.com/pypeclub/OpenPype/pull/3630) @@ -123,14 +123,10 @@ - General: Extract review aspect ratio scale is calculated by ffmpeg [\#3620](https://github.com/pypeclub/OpenPype/pull/3620) - Maya: Fix types of default settings [\#3617](https://github.com/pypeclub/OpenPype/pull/3617) - Integrator: Don't force to have dot before frame [\#3611](https://github.com/pypeclub/OpenPype/pull/3611) -- AfterEffects: refactored integrate doesnt work formulti frame publishes [\#3610](https://github.com/pypeclub/OpenPype/pull/3610) -- Maya look data contents fails with custom attribute on group [\#3607](https://github.com/pypeclub/OpenPype/pull/3607) -- TrayPublisher: Fix wrong conflict merge [\#3600](https://github.com/pypeclub/OpenPype/pull/3600) **🔀 Refactored code** - General: Plugin settings handled by plugins [\#3623](https://github.com/pypeclub/OpenPype/pull/3623) -- General: Naive implementation of document create, update, delete [\#3601](https://github.com/pypeclub/OpenPype/pull/3601) **Merged pull requests:** diff --git a/openpype/version.py b/openpype/version.py index 7894bb8bf4..a6ebacb910 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.14.1-nightly.3" +__version__ = "3.14.1-nightly.4" diff --git a/pyproject.toml b/pyproject.toml index 75e4721d7f..a2954f9c9c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.14.1-nightly.3" # OpenPype +version = "3.14.1-nightly.4" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 3a425fcf386ae96ead5a8b98744deb2aa90287ab Mon Sep 17 00:00:00 2001 From: OpenPype Date: Tue, 30 Aug 2022 08:33:14 +0000 Subject: [PATCH 155/274] [Automated] Release --- CHANGELOG.md | 4 ++-- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a7d93711a..cee0183273 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -## [3.14.1-nightly.4](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.14.1](https://github.com/pypeclub/OpenPype/tree/3.14.1) (2022-08-30) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.0...HEAD) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.0...3.14.1) ### 📖 Documentation diff --git a/openpype/version.py b/openpype/version.py index a6ebacb910..963f9171e2 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.14.1-nightly.4" +__version__ = "3.14.1" diff --git a/pyproject.toml b/pyproject.toml index a2954f9c9c..2fe2573baf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.14.1-nightly.4" # OpenPype +version = "3.14.1" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 9472cbe271af4be7c0328bf05200f44c521c58ae Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 11:23:14 +0200 Subject: [PATCH 156/274] Fix submission --- .../collect_deadline_server_from_instance.py | 2 +- .../plugins/publish/submit_maya_deadline.py | 71 +++++++++---------- 2 files changed, 33 insertions(+), 40 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py b/openpype/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py index a7035cd99f..9981bead3e 100644 --- a/openpype/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py +++ b/openpype/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py @@ -13,7 +13,7 @@ class CollectDeadlineServerFromInstance(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.415 label = "Deadline Webservice from the Instance" - families = ["rendering"] + families = ["rendering", "renderlayer"] def process(self, instance): instance.data["deadlineUrl"] = self._collect_deadline_url(instance) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 873005e051..2afa1883c4 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -27,7 +27,6 @@ import itertools from collections import OrderedDict import attr -import clique from maya import cmds @@ -111,7 +110,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # Add options from RenderGlobals render_globals = instance.data.get("renderGlobals", {}) - for key, value in render_globals: + for key, value in render_globals.items(): setattr(job_info, key, value) keys = [ @@ -143,13 +142,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): job_info.EnvironmentKeyValue = "{key}={value}".format(key=key, value=value) - # Enable double-click to preview rendered frames from Deadline Monitor - for filepath in instance.data["files"]: - dirname = os.path.dirname(filepath) - fname = os.path.basename(filepath) - job_info.OutputDirectory = dirname.replace("\\", "/") - job_info.OutputFilename = fname - # Adding file dependencies. if self.asset_dependencies: dependencies = instance.context.data["fileDependencies"] @@ -160,28 +152,9 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # Add list of expected files to job # --------------------------------- exp = instance.data.get("expectedFiles") - - def _get_output_filename(files): - col, rem = clique.assemble(files) - if not col and rem: - # we couldn't find any collections but have - # individual files. - assert len(rem) == 1, ( - "Found multiple non related files " - "to render, don't know what to do " - "with them.") - return rem[0] - else: - return col[0].format('{head}{padding}{tail}') - - if isinstance(exp[0], dict): - # we have aovs and we need to iterate over them - for _aov, files in exp[0].items(): - output_file = _get_output_filename(files) - job_info.OutputFilename = output_file - else: - output_file = _get_output_filename(exp) - job_info.OutputFilename = output_file + for filepath in self._iter_expected_files(exp): + job_info.OutputDirectory = os.path.dirname(filepath) + job_info.OutputFilename = os.path.basename(filepath) return job_info @@ -194,6 +167,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): SceneFile=self.scene_path, Version=cmds.about(version=True), RenderLayer=instance.data['setMembers'], + Renderer=instance.data["renderer"], RenderSetupIncludeLights=instance.data.get("renderSetupIncludeLights"), # noqa ProjectPath=context.data["workspaceDir"], UsingRenderLayers=True, @@ -216,7 +190,9 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # TODO: Avoid the need for this logic here, needed for submit publish # Store output dir for unified publisher (filesequence) - output_dir = os.path.dirname(instance.data["files"][0]) + expected_files = instance.data["expectedFiles"] + first_file = next(self._iter_expected_files(expected_files)) + output_dir = os.path.dirname(first_file) instance.data["outputDir"] = output_dir instance.data["toBeRenderedOn"] = "deadline" @@ -247,17 +223,20 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): "Vray Scene and Ass Scene options are mutually exclusive") if "vrayscene" in instance.data["families"]: + self.log.debug("Submitting V-Ray scene render..") vray_export_payload = self._get_vray_export_payload(payload_data) export_job = self.submit(vray_export_payload) payload = self._get_vray_render_payload(payload_data) elif "assscene" in instance.data["families"]: + self.log.debug("Submitting Arnold .ass standalone render..") ass_export_payload = self._get_arnold_export_payload(payload_data) export_job = self.submit(ass_export_payload) payload = self._get_arnold_render_payload(payload_data) else: + self.log.debug("Submitting MayaBatch render..") payload = self._get_maya_payload(payload_data) # Add export job as dependency -------------------------------------- @@ -274,6 +253,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): self.submit(self.assemble_payload(job_info, plugin_info)) def _tile_render(self, payload): + """Submit as tile render per frame with dependent assembly jobs.""" # As collected by super process() instance = self._instance @@ -315,7 +295,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): assembly_files = files # Define frame tile jobs - frame_jobs = {} + frame_file_hash = {} frame_payloads = {} file_index = 1 for file in files: @@ -343,9 +323,11 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): self.log.info("hashing {} - {}".format(file_index, file)) job_hash = hashlib.sha256( ("{}_{}".format(file_index, file)).encode("utf-8")) - frame_jobs[frame] = job_hash.hexdigest() - new_job_info.ExtraInfo[0] = job_hash.hexdigest() + file_hash = job_hash.hexdigest() + frame_file_hash[frame] = file_hash + + new_job_info.ExtraInfo[0] = file_hash new_job_info.ExtraInfo[1] = file frame_payloads[frame] = self.assemble_payload( @@ -391,10 +373,10 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): REPL_FRAME_NUMBER, "\\1{}\\3".format("#" * len(frame)), file) - hash = frame_jobs[frame] + file_hash = frame_file_hash[frame] tile_job_id = frame_tile_job_id[frame] - frame_assembly_job_info.ExtraInfo[0] = hash + frame_assembly_job_info.ExtraInfo[0] = file_hash frame_assembly_job_info.ExtraInfo[1] = file frame_assembly_job_info.JobDependency = tile_job_id @@ -483,11 +465,12 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): if int(rman_version.split(".")[0]) > 22: renderer = "renderman22" - plugin_info = { + plugin_info = copy.deepcopy(self.plugin_info) + plugin_info.update({ # Output directory and filename "OutputFilePath": data["dirname"].replace("\\", "/"), "OutputFilePrefix": layer_prefix, - } + }) return job_info, plugin_info @@ -710,6 +693,16 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): end=int(self._instance.data["frameEndHandle"]), ) + @staticmethod + def _iter_expected_files(exp): + if isinstance(exp[0], dict): + for _aov, files in exp[0].items(): + for file in files: + yield file + else: + for file in exp: + yield file + def _format_tiles( filename, index, tiles_x, tiles_y, From fb0ec16bf3ff959a6f5471d03b788d2669ca9864 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 30 Aug 2022 11:54:50 +0200 Subject: [PATCH 157/274] fix return value of 'get_instance_staging_dir' --- openpype/pipeline/publish/lib.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 4f06f0e6fd..bb0bd7bd4b 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -443,8 +443,9 @@ def get_instance_staging_dir(instance): staging_dir = instance.data.get("stagingDir") if not staging_dir: - instance.data["stagingDir"] = os.path.normpath( + staging_dir = os.path.normpath( tempfile.mkdtemp(prefix="pyblish_tmp_") ) + instance.data["stagingDir"] = staging_dir return staging_dir From 4cdd23021b4b251c13ae6139b06dac951d9ba6e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 30 Aug 2022 13:17:18 +0200 Subject: [PATCH 158/274] :bug: fix version resolution --- igniter/bootstrap_repos.py | 2 +- tools/create_zip.ps1 | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/igniter/bootstrap_repos.py b/igniter/bootstrap_repos.py index c5003b062e..ccc9d4ac52 100644 --- a/igniter/bootstrap_repos.py +++ b/igniter/bootstrap_repos.py @@ -63,7 +63,7 @@ class OpenPypeVersion(semver.VersionInfo): """ staging = False path = None - _VERSION_REGEX = re.compile(r"(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$") # noqa: E501 + _VERSION_REGEX = re.compile(r"(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?") # noqa: E501 _installed_version = None def __init__(self, *args, **kwargs): diff --git a/tools/create_zip.ps1 b/tools/create_zip.ps1 index 7b852b7c54..e5ebc7678b 100644 --- a/tools/create_zip.ps1 +++ b/tools/create_zip.ps1 @@ -96,6 +96,7 @@ Write-Color -Text ">>> ", "Cleaning cache files ... " -Color Green, Gray -NoNewl Get-ChildItem $openpype_root -Filter "__pycache__" -Force -Recurse| Where-Object {( $_.FullName -inotmatch '\\build\\' ) -and ( $_.FullName -inotmatch '\\.venv' )} | Remove-Item -Force -Recurse Get-ChildItem $openpype_root -Filter "*.pyc" -Force -Recurse | Where-Object {( $_.FullName -inotmatch '\\build\\' ) -and ( $_.FullName -inotmatch '\\.venv' )} | Remove-Item -Force Get-ChildItem $openpype_root -Filter "*.pyo" -Force -Recurse | Where-Object {( $_.FullName -inotmatch '\\build\\' ) -and ( $_.FullName -inotmatch '\\.venv' )} | Remove-Item -Force +Write-Color -Text "OK" -Color Green Write-Color -Text ">>> ", "Generating zip from current sources ..." -Color Green, Gray $env:PYTHONPATH="$($openpype_root);$($env:PYTHONPATH)" From e23ed382f8149648c404f15e18ea567bdceebfb0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 30 Aug 2022 13:23:07 +0200 Subject: [PATCH 159/274] added python 3.9 startup script for houdini --- .../hosts/houdini/startup/python3.9libs/pythonrc.py | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 openpype/hosts/houdini/startup/python3.9libs/pythonrc.py diff --git a/openpype/hosts/houdini/startup/python3.9libs/pythonrc.py b/openpype/hosts/houdini/startup/python3.9libs/pythonrc.py new file mode 100644 index 0000000000..afadbffd3e --- /dev/null +++ b/openpype/hosts/houdini/startup/python3.9libs/pythonrc.py @@ -0,0 +1,10 @@ +from openpype.pipeline import install_host +from openpype.hosts.houdini import api + + +def main(): + print("Installing OpenPype ...") + install_host(api) + + +main() From 90b474b365385804cd25ad3d0a1db8e281552292 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 30 Aug 2022 13:39:44 +0200 Subject: [PATCH 160/274] removed OpenColorIO submodule --- vendor/configs/OpenColorIO-Configs | 1 - 1 file changed, 1 deletion(-) delete mode 160000 vendor/configs/OpenColorIO-Configs diff --git a/vendor/configs/OpenColorIO-Configs b/vendor/configs/OpenColorIO-Configs deleted file mode 160000 index 0bb079c08b..0000000000 --- a/vendor/configs/OpenColorIO-Configs +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0bb079c08be410030669cbf5f19ff869b88af953 From 57d55a4091392b9da815850a5e72c84680b81bf4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 30 Aug 2022 13:49:24 +0200 Subject: [PATCH 161/274] remove unused variable --- openpype/pipeline/publish/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index bb0bd7bd4b..e7a3ef73fe 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -418,7 +418,7 @@ def context_plugin_should_run(plugin, context): bool: Context plugin should run based on valid instances. """ - for instance in filter_instances_for_context_plugin(plugin, context): + for _ in filter_instances_for_context_plugin(plugin, context): return True return False From 227b8405479e3a87b507e63a9b59fe473d7c9276 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 14:12:54 +0200 Subject: [PATCH 162/274] Refactor AbstractSubmitDeadline vars to allow easier access to indices --- .../deadline/abstract_submit_deadline.py | 251 +++++++----------- .../plugins/publish/submit_maya_deadline.py | 35 ++- 2 files changed, 115 insertions(+), 171 deletions(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index a3db3feac9..427faec115 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -9,6 +9,7 @@ import os from abc import abstractmethod import platform import getpass +from functools import partial from collections import OrderedDict import six @@ -66,6 +67,58 @@ def requests_get(*args, **kwargs): return requests.get(*args, **kwargs) +class DeadlineIndexedVar(dict): + """ + + Allows to set and query values by integer indices: + Query: var[1] or var.get(1) + Set: var[1] = "my_value" + Append: var += "value" + + Note: Iterating the instance is not guarantueed to be the order of the + indices. To do so iterate with `sorted()` + + """ + def __init__(self, key): + self.__key = key + + def next_available_index(self): + # Add as first unused entry + i = 0 + while i in self.keys(): + i += 1 + return i + + def serialize(self): + key = self.__key + + # Allow custom location for index in serialized string + if "{}" not in key: + key = key + "{}" + + return { + key.format(index): value for index, value in sorted(self.items()) + } + + def update(self, data): + # Force the integer key check + for key, value in data.items(): + self.__setitem__(key, value) + + def __iadd__(self, other): + index = self.next_available_index() + self[index] = other + return self + + def __setitem__(self, key, value): + if not isinstance(key, int): + raise TypeError("Key must be an integer: {}".format(key)) + + if key < 0: + raise ValueError("Negative index can't be set: {}".format(key)) + dict.__setitem__(self, key, value) + + @attr.s class DeadlineJobInfo(object): """Mapping of all Deadline *JobInfo* attributes. @@ -218,24 +271,8 @@ class DeadlineJobInfo(object): # Environment # ---------------------------------------------- - _environmentKeyValue = attr.ib(factory=list) - - @property - def EnvironmentKeyValue(self): # noqa: N802 - """Return all environment key values formatted for Deadline. - - Returns: - dict: as `{'EnvironmentKeyValue0', 'key=value'}` - - """ - out = {} - for index, v in enumerate(self._environmentKeyValue): - out["EnvironmentKeyValue{}".format(index)] = v - return out - - @EnvironmentKeyValue.setter - def EnvironmentKeyValue(self, val): # noqa: N802 - self._environmentKeyValue.append(val) + EnvironmentKeyValue = attr.ib(factory=partial(DeadlineIndexedVar, + "EnvironmentKeyValue")) IncludeEnvironment = attr.ib(default=None) # Default: false UseJobEnvironmentOnly = attr.ib(default=None) # Default: false @@ -243,142 +280,29 @@ class DeadlineJobInfo(object): # Job Extra Info # ---------------------------------------------- - _extraInfos = attr.ib(factory=list) - _extraInfoKeyValues = attr.ib(factory=list) - - @property - def ExtraInfo(self): # noqa: N802 - """Return all ExtraInfo values formatted for Deadline. - - Returns: - dict: as `{'ExtraInfo0': 'value'}` - - """ - out = {} - for index, v in enumerate(self._extraInfos): - out["ExtraInfo{}".format(index)] = v - return out - - @ExtraInfo.setter - def ExtraInfo(self, val): # noqa: N802 - self._extraInfos.append(val) - - @property - def ExtraInfoKeyValue(self): # noqa: N802 - """Return all ExtraInfoKeyValue values formatted for Deadline. - - Returns: - dict: as {'ExtraInfoKeyValue0': 'key=value'}` - - """ - out = {} - for index, v in enumerate(self._extraInfoKeyValues): - out["ExtraInfoKeyValue{}".format(index)] = v - return out - - @ExtraInfoKeyValue.setter - def ExtraInfoKeyValue(self, val): # noqa: N802 - self._extraInfoKeyValues.append(val) + ExtraInfo = attr.ib(factory=partial(DeadlineIndexedVar, "ExtraInfo")) + ExtraInfoKeyValue = attr.ib(factory=partial(DeadlineIndexedVar, + "ExtraInfoKeyValue")) # Task Extra Info Names # ---------------------------------------------- OverrideTaskExtraInfoNames = attr.ib(default=None) # Default: false - _taskExtraInfos = attr.ib(factory=list) - - @property - def TaskExtraInfoName(self): # noqa: N802 - """Return all TaskExtraInfoName values formatted for Deadline. - - Returns: - dict: as `{'TaskExtraInfoName0': 'value'}` - - """ - out = {} - for index, v in enumerate(self._taskExtraInfos): - out["TaskExtraInfoName{}".format(index)] = v - return out - - @TaskExtraInfoName.setter - def TaskExtraInfoName(self, val): # noqa: N802 - self._taskExtraInfos.append(val) + TaskExtraInfoName = attr.ib(factory=partial(DeadlineIndexedVar, + "TaskExtraInfoName")) # Output # ---------------------------------------------- - _outputFilename = attr.ib(factory=list) - _outputFilenameTile = attr.ib(factory=list) - _outputDirectory = attr.ib(factory=list) - - @property - def OutputFilename(self): # noqa: N802 - """Return all OutputFilename values formatted for Deadline. - - Returns: - dict: as `{'OutputFilename0': 'filename'}` - - """ - out = {} - for index, v in enumerate(self._outputFilename): - out["OutputFilename{}".format(index)] = v - return out - - @OutputFilename.setter - def OutputFilename(self, val): # noqa: N802 - self._outputFilename.append(val) - - @property - def OutputFilenameTile(self): # noqa: N802 - """Return all OutputFilename#Tile values formatted for Deadline. - - Returns: - dict: as `{'OutputFilenme#Tile': 'tile'}` - - """ - out = {} - for index, v in enumerate(self._outputFilenameTile): - out["OutputFilename{}Tile".format(index)] = v - return out - - @OutputFilenameTile.setter - def OutputFilenameTile(self, val): # noqa: N802 - self._outputFilenameTile.append(val) - - @property - def OutputDirectory(self): # noqa: N802 - """Return all OutputDirectory values formatted for Deadline. - - Returns: - dict: as `{'OutputDirectory0': 'dir'}` - - """ - out = {} - for index, v in enumerate(self._outputDirectory): - out["OutputDirectory{}".format(index)] = v - return out - - @OutputDirectory.setter - def OutputDirectory(self, val): # noqa: N802 - self._outputDirectory.append(val) + OutputFilename = attr.ib(factory=partial(DeadlineIndexedVar, + "OutputFilename")) + OutputFilenameTile = attr.ib(factory=partial(DeadlineIndexedVar, + "OutputFilename{}Tile")) + OutputDirectory = attr.ib(factory=partial(DeadlineIndexedVar, + "OutputDirectory")) # Asset Dependency # ---------------------------------------------- - _assetDependency = attr.ib(factory=list) - - @property - def AssetDependency(self): # noqa: N802 - """Return all OutputDirectory values formatted for Deadline. - - Returns: - dict: as `{'OutputDirectory0': 'dir'}` - - """ - out = {} - for index, v in enumerate(self._assetDependency): - out["AssetDependency{}".format(index)] = v - return out - - @OutputDirectory.setter - def AssetDependency(self, val): # noqa: N802 - self._assetDependency.append(val) + AssetDependency = attr.ib(factory=partial(DeadlineIndexedVar, + "AssetDependency")) # Tile Job # ---------------------------------------------- @@ -402,7 +326,7 @@ class DeadlineJobInfo(object): """ def filter_data(a, v): - if a.name.startswith("_"): + if isinstance(v, DeadlineIndexedVar): return False if v is None: return False @@ -410,16 +334,37 @@ class DeadlineJobInfo(object): serialized = attr.asdict( self, dict_factory=OrderedDict, filter=filter_data) - serialized.update(self.EnvironmentKeyValue) - serialized.update(self.ExtraInfo) - serialized.update(self.ExtraInfoKeyValue) - serialized.update(self.TaskExtraInfoName) - serialized.update(self.OutputFilename) - serialized.update(self.OutputFilenameTile) - serialized.update(self.OutputDirectory) - serialized.update(self.AssetDependency) + + # Custom serialize these attributes + for attribute in [ + self.EnvironmentKeyValue, + self.ExtraInfo, + self.ExtraInfoKeyValue, + self.TaskExtraInfoName, + self.OutputFilename, + self.OutputFilenameTile, + self.OutputDirectory, + self.AssetDependency + ]: + serialized.update(attribute.serialize()) + return serialized + def update(self, data): + """Update instance with data dict""" + for key, value in data.items(): + setattr(self, key, value) + + def __setattr__(self, key, value): + # Backwards compatibility: Allow appending to index vars by setting + # it on Job Info directly like: JobInfo.OutputFilename = filename + existing = getattr(self, key, None) + if isinstance(existing, DeadlineIndexedVar): + existing += value + return + + object.__setattr__(self, key, value) + @six.add_metaclass(AbstractMetaInstancePlugin) class AbstractSubmitDeadline(pyblish.api.InstancePlugin): diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 2afa1883c4..d979c92814 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -110,8 +110,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # Add options from RenderGlobals render_globals = instance.data.get("renderGlobals", {}) - for key, value in render_globals.items(): - setattr(job_info, key, value) + job_info.update(render_globals) keys = [ "FTRACK_API_KEY", @@ -257,8 +256,10 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # As collected by super process() instance = self._instance - job_info = copy.deepcopy(self.job_info) - plugin_info = copy.deepcopy(self.plugin_info) + + payload_job_info, payload_plugin_info = payload + job_info = copy.deepcopy(payload_job_info) + plugin_info = copy.deepcopy(payload_plugin_info) # if we have sequence of files, we need to create tile job for # every frame @@ -309,16 +310,17 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): new_plugin_info = copy.deepcopy(plugin_info) # Add tile data into job info and plugin info - tiles_out, _ = _format_tiles( + tiles_data = _format_tiles( file, 0, instance.data.get("tilesX"), instance.data.get("tilesY"), instance.data.get("resolutionWidth"), instance.data.get("resolutionHeight"), - payload["PluginInfo"]["OutputFilePrefix"] - ) - new_job_info.update(tiles_out["JobInfo"]) - new_plugin_info.update(tiles_out["PluginInfo"]) + payload_plugin_info["OutputFilePrefix"] + )[0] + + new_job_info.update(tiles_data["JobInfo"]) + new_plugin_info.update(tiles_data["PluginInfo"]) self.log.info("hashing {} - {}".format(file_index, file)) job_hash = hashlib.sha256( @@ -342,15 +344,13 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # Submit frame tile jobs frame_tile_job_id = {} for frame, tile_job_payload in frame_payloads.items(): - response = self.submit(tile_job_payload) - job_id = response.json()["_id"] + job_id = self.submit(tile_job_payload) frame_tile_job_id[frame] = job_id # Define assembly payloads assembly_job_info = copy.deepcopy(job_info) assembly_job_info.Plugin = self.tile_assembler_plugin - assembly_job_info.Name = "{job.Name} - Tile Assembly Job".format( - job=job_info) + assembly_job_info.Name += " - Tile Assembly Job" assembly_job_info.Frames = 1 assembly_job_info.MachineLimit = 1 assembly_job_info.Priority = instance.data.get("tile_priority", @@ -411,10 +411,9 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): instance.data.get("tilesY"), instance.data.get("resolutionWidth"), instance.data.get("resolutionHeight"), - payload["PluginInfo"]["OutputFilePrefix"] + payload_plugin_info["OutputFilePrefix"] )[1] - sorted(tiles) - for k, v in tiles.items(): + for k, v in sorted(tiles.items()): print("{}={}".format(k, v), file=cf) payload = self.assemble_payload( @@ -431,8 +430,8 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): self.log.info("submitting assembly job {} of {}".format( i+1, len(assembly_payloads) )) - response = self.submit(payload) - assembly_job_ids.append(response.json()["_id"]) + assembly_job_id = self.submit(payload) + assembly_job_ids.append(assembly_job_id) instance.data["assemblySubmissionJobs"] = assembly_job_ids From a7293f2a4f6a30297eea15297f5b25100e11e9f1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 14:13:14 +0200 Subject: [PATCH 163/274] Fix indentation --- openpype/modules/deadline/abstract_submit_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index 427faec115..e1bdcb10d9 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -295,7 +295,7 @@ class DeadlineJobInfo(object): OutputFilename = attr.ib(factory=partial(DeadlineIndexedVar, "OutputFilename")) OutputFilenameTile = attr.ib(factory=partial(DeadlineIndexedVar, - "OutputFilename{}Tile")) + "OutputFilename{}Tile")) OutputDirectory = attr.ib(factory=partial(DeadlineIndexedVar, "OutputDirectory")) From 91a3d8494bf8b65fe37560a02018edf59433caa6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 14:13:39 +0200 Subject: [PATCH 164/274] Disable aux files for now since it's not supported by Deadline Webservice --- .../modules/deadline/plugins/publish/submit_maya_deadline.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index d979c92814..7694e80e9a 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -419,8 +419,9 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): payload = self.assemble_payload( job_info=frame_assembly_job_info, plugin_info=assembly_plugin_info.copy(), + # todo: aux file transfers don't work with deadline webservice # add config file as job auxFile - aux_files=[config_file] + # aux_files=[config_file] ) assembly_payloads.append(payload) From 39d216797dacad00a3f42102e146c2806b9f8244 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 14:17:40 +0200 Subject: [PATCH 165/274] Force integer pixel values --- .../deadline/plugins/publish/submit_maya_deadline.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 7694e80e9a..3fbff0153b 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -765,10 +765,10 @@ def _format_tiles( tiles_x, tiles_y ) - top = int(height) - (tile_y * h_space) - bottom = int(height) - ((tile_y - 1) * h_space) - 1 - left = (tile_x - 1) * w_space - right = (tile_x * w_space) - 1 + top = int(height - (tile_y * h_space)) + bottom = int(height - ((tile_y - 1) * h_space) - 1) + left = int((tile_x - 1) * w_space) + right = int((tile_x * w_space) - 1) # Job Info new_filename = "{}/{}{}".format( From 2fb7cabca49bae51162e2a82dfdc7d225094ed36 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 14:39:28 +0200 Subject: [PATCH 166/274] Shush hound --- .../deadline/abstract_submit_deadline.py | 4 ++-- .../plugins/publish/submit_maya_deadline.py | 21 +++++++++++-------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index e1bdcb10d9..35b114da95 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -623,8 +623,8 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin): for i in context: is_workfile = ( - "workfile" in i.data.get("families", []) or - i.data["family"] == "workfile" + "workfile" in i.data.get("families", []) or + i.data["family"] == "workfile" ) if not is_workfile: continue diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 3fbff0153b..1b69f8b4e9 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -357,14 +357,14 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): self.tile_priority) assembly_plugin_info = { - "CleanupTiles": 1, - "ErrorOnMissing": True, - "Renderer": self._instance.data["renderer"] + "CleanupTiles": 1, + "ErrorOnMissing": True, + "Renderer": self._instance.data["renderer"] } assembly_payloads = [] output_dir = self.job_info.OutputDirectory[0] - for i, file in enumerate(assembly_files): + for file in assembly_files: frame = re.search(R_FRAME_NUMBER, file).group("frame") frame_assembly_job_info = copy.deepcopy(assembly_job_info) @@ -383,7 +383,8 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # write assembly job config files now = datetime.now() - config_file = os.path.join(output_dir, + config_file = os.path.join( + output_dir, "{}_config_{}.txt".format( os.path.splitext(file)[0], now.strftime("%Y_%m_%d_%H_%M_%S") @@ -427,10 +428,12 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # Submit assembly jobs assembly_job_ids = [] + num_assemblies = len(assembly_payloads) for i, payload in enumerate(assembly_payloads): - self.log.info("submitting assembly job {} of {}".format( - i+1, len(assembly_payloads) - )) + self.log.info( + "submitting assembly job {} of {}".format(i + 1, + num_assemblies) + ) assembly_job_id = self.submit(payload) assembly_job_ids.append(assembly_job_id) @@ -682,7 +685,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): pf.writelines(scene_data) pf.truncate() self.log.info("Applied {} patch to scene.".format( - patches[i]["name"] + patches[i]["name"] )) def _job_info_label(self, label): From e9e01e3163079bc9f6c48fe633aed92592928328 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 14:43:18 +0200 Subject: [PATCH 167/274] Use update method --- .../modules/deadline/plugins/publish/submit_maya_deadline.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 1b69f8b4e9..9692b136e9 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -69,8 +69,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # todo: test whether this works for existing production cases # where custom jobInfo was stored in the project settings - for key, value in self.jobInfo.items(): - setattr(job_info, key, value) + job_info.update(self.jobInfo) instance = self._instance context = instance.context From e07fdf2e9157aa96f8f28bc4945df462eeafd43d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 30 Aug 2022 17:48:46 +0200 Subject: [PATCH 168/274] added settings for nuke --- openpype/settings/defaults/project_settings/maya.json | 2 +- openpype/settings/defaults/project_settings/nuke.json | 3 +++ .../entities/schemas/projects_schema/schema_project_nuke.json | 4 ++++ .../schemas/schema_templated_workfile_build.json | 2 +- 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 28f6d23e4d..38063bc2c1 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -980,4 +980,4 @@ "ValidateNoAnimation": false } } -} +} \ No newline at end of file diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index f40ec1fe9e..c3eda2cbb4 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -325,5 +325,8 @@ } ] }, + "templated_workfile_build": { + "profiles": [] + }, "filters": {} } \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json b/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json index 03d67a57ba..7cf82b9e69 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json @@ -308,6 +308,10 @@ "type": "schema_template", "name": "template_workfile_options" }, + { + "type": "schema", + "name": "schema_templated_workfile_build" + }, { "type": "schema", "name": "schema_publish_gui_filter" diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_templated_workfile_build.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_templated_workfile_build.json index a591facf98..99a29beb27 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_templated_workfile_build.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_templated_workfile_build.json @@ -17,7 +17,7 @@ "type": "task-types-enum" }, { - "key": "tasks", + "key": "task_names", "label": "Task names", "type": "list", "object_type": "text" From c6b8b9182587448e9024f2f1f31ec7f16b72135f Mon Sep 17 00:00:00 2001 From: murphy Date: Tue, 30 Aug 2022 18:06:19 +0200 Subject: [PATCH 169/274] Ftrack status fix typo prgoress -> progress there is a typo in status name in settings prgoress -> progress --- openpype/settings/defaults/project_settings/ftrack.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/ftrack.json b/openpype/settings/defaults/project_settings/ftrack.json index 41bed7751b..09b194e21c 100644 --- a/openpype/settings/defaults/project_settings/ftrack.json +++ b/openpype/settings/defaults/project_settings/ftrack.json @@ -56,7 +56,7 @@ "Not Ready" ], "__ignore__": [ - "in prgoress", + "in progress", "omitted", "on hold" ] From 767f20ef1ba0fdc68084e3280b5518b654b3b424 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 20:02:01 +0200 Subject: [PATCH 170/274] Open main file of representation For image sequences this is the same first file as uses before --- openpype/plugins/load/open_file.py | 36 ++++++------------------------ 1 file changed, 7 insertions(+), 29 deletions(-) diff --git a/openpype/plugins/load/open_file.py b/openpype/plugins/load/open_file.py index f21cd07c7f..00b2ecd7c5 100644 --- a/openpype/plugins/load/open_file.py +++ b/openpype/plugins/load/open_file.py @@ -15,8 +15,8 @@ def open(filepath): subprocess.call(('xdg-open', filepath)) -class Openfile(load.LoaderPlugin): - """Open Image Sequence with system default""" +class OpenFile(load.LoaderPlugin): + """Open Image Sequence or Video with system default""" families = ["render2d"] representations = ["*"] @@ -27,32 +27,10 @@ class Openfile(load.LoaderPlugin): color = "orange" def load(self, context, name, namespace, data): - import clique - directory = os.path.dirname(self.fname) - pattern = clique.PATTERNS["frames"] + path = self.fname + if not os.path.exists(path): + raise RuntimeError("File not found: {}".format(path)) - files = os.listdir(directory) - representation = context["representation"] - - ext = representation["name"] - path = representation["data"]["path"] - - if ext in ["#"]: - collections, remainder = clique.assemble(files, - patterns=[pattern], - minimum_items=1) - - seqeunce = collections[0] - - first_image = list(seqeunce)[0] - filepath = os.path.normpath(os.path.join(directory, first_image)) - else: - file = [f for f in files - if ext in f - if "#" not in f][0] - filepath = os.path.normpath(os.path.join(directory, file)) - - self.log.info("Opening : {}".format(filepath)) - - open(filepath) + self.log.info("Opening : {}".format(path)) + open(path) From 3c62ff63ad51f0b370fde3fb431c1579b2196268 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Wed, 31 Aug 2022 04:27:37 +0000 Subject: [PATCH 171/274] [Automated] Bump version --- CHANGELOG.md | 33 ++++++++++++++++++++------------- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cee0183273..7c8834dd49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,27 @@ # Changelog +## [3.14.2-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.1...HEAD) + +**🆕 New features** + +- Houdini: Publishing workfiles [\#3697](https://github.com/pypeclub/OpenPype/pull/3697) + +**🐛 Bug fixes** + +- Ftrack status fix typo prgoress -\> progress [\#3761](https://github.com/pypeclub/OpenPype/pull/3761) + +**🔀 Refactored code** + +- General: Move hostdirname functionality into host [\#3749](https://github.com/pypeclub/OpenPype/pull/3749) +- Webpublisher: Webpublisher is used as addon [\#3740](https://github.com/pypeclub/OpenPype/pull/3740) +- Houdini: Define houdini as addon [\#3735](https://github.com/pypeclub/OpenPype/pull/3735) +- Flame: Defined flame as addon [\#3732](https://github.com/pypeclub/OpenPype/pull/3732) + ## [3.14.1](https://github.com/pypeclub/OpenPype/tree/3.14.1) (2022-08-30) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.0...3.14.1) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.14.1-nightly.4...3.14.1) ### 📖 Documentation @@ -44,7 +63,6 @@ - General: Move delivery logic to pipeline [\#3751](https://github.com/pypeclub/OpenPype/pull/3751) - General: Host addons cleanup [\#3744](https://github.com/pypeclub/OpenPype/pull/3744) -- Webpublisher: Webpublisher is used as addon [\#3740](https://github.com/pypeclub/OpenPype/pull/3740) - Photoshop: Defined photoshop as addon [\#3736](https://github.com/pypeclub/OpenPype/pull/3736) - Harmony: Defined harmony as addon [\#3734](https://github.com/pypeclub/OpenPype/pull/3734) - General: Module interfaces cleanup [\#3731](https://github.com/pypeclub/OpenPype/pull/3731) @@ -75,7 +93,6 @@ **🚀 Enhancements** - Ftrack: Addiotional component metadata [\#3685](https://github.com/pypeclub/OpenPype/pull/3685) -- Ftrack: Set task status on farm publishing [\#3680](https://github.com/pypeclub/OpenPype/pull/3680) - Ftrack: Set task status on task creation in integrate hierarchy [\#3675](https://github.com/pypeclub/OpenPype/pull/3675) - Maya: Disable rendering of all lights for render instances submitted through Deadline. [\#3661](https://github.com/pypeclub/OpenPype/pull/3661) - General: Optimized OCIO configs [\#3650](https://github.com/pypeclub/OpenPype/pull/3650) @@ -112,27 +129,17 @@ **🚀 Enhancements** - Editorial: Mix audio use side file for ffmpeg filters [\#3630](https://github.com/pypeclub/OpenPype/pull/3630) -- Ftrack: Comment template can contain optional keys [\#3615](https://github.com/pypeclub/OpenPype/pull/3615) -- Ftrack: Add more metadata to ftrack components [\#3612](https://github.com/pypeclub/OpenPype/pull/3612) **🐛 Bug fixes** - Maya: fix aov separator in Redshift [\#3625](https://github.com/pypeclub/OpenPype/pull/3625) - Fix for multi-version build on Mac [\#3622](https://github.com/pypeclub/OpenPype/pull/3622) - Ftrack: Sync hierarchical attributes can handle new created entities [\#3621](https://github.com/pypeclub/OpenPype/pull/3621) -- General: Extract review aspect ratio scale is calculated by ffmpeg [\#3620](https://github.com/pypeclub/OpenPype/pull/3620) -- Maya: Fix types of default settings [\#3617](https://github.com/pypeclub/OpenPype/pull/3617) -- Integrator: Don't force to have dot before frame [\#3611](https://github.com/pypeclub/OpenPype/pull/3611) **🔀 Refactored code** - General: Plugin settings handled by plugins [\#3623](https://github.com/pypeclub/OpenPype/pull/3623) -**Merged pull requests:** - -- Webpublisher: timeout for PS studio processing [\#3619](https://github.com/pypeclub/OpenPype/pull/3619) -- Core: translated validate\_containers.py into New publisher style [\#3614](https://github.com/pypeclub/OpenPype/pull/3614) - ## [3.12.2](https://github.com/pypeclub/OpenPype/tree/3.12.2) (2022-07-27) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.12.2-nightly.4...3.12.2) diff --git a/openpype/version.py b/openpype/version.py index 963f9171e2..0c114b6060 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.14.1" +__version__ = "3.14.2-nightly.1" diff --git a/pyproject.toml b/pyproject.toml index 2fe2573baf..9ed1872eac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.14.1" # OpenPype +version = "3.14.2-nightly.1" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 2397577dbdaf386c55d58ba5167aba70d08c24cb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 10:48:40 +0200 Subject: [PATCH 172/274] node temp file can create more then one tempfile at once --- openpype/hosts/nuke/api/lib.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index b14f1a1eb1..e51f1f7586 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2708,26 +2708,19 @@ def _duplicate_node_temp(): This is to avoid using clipboard for node duplication. """ - duplicate_node_temp_path = os.path.join( - tempfile.gettempdir(), - "openpype_nuke_duplicate_temp_{}".format(os.getpid()) + tmp_file = tempfile.NamedTemporaryFile( + mode="w", prefix="openpype_nuke_temp_", suffix=".nk", delete=False ) - - # This can happen only if 'duplicate_node' would be - if os.path.exists(duplicate_node_temp_path): - log.warning(( - "Temp file for node duplication already exists." - " Trying to remove {}" - ).format(duplicate_node_temp_path)) - os.remove(duplicate_node_temp_path) + tmp_file.close() + node_tempfile_path = tmp_file.name try: # Yield the path where node can be copied - yield duplicate_node_temp_path + yield node_tempfile_path finally: # Remove the file at the end - os.remove(duplicate_node_temp_path) + os.remove(node_tempfile_path) def duplicate_node(node): From c2fdf6d1d0ec9764717a962c291353a3c3634539 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 10:49:04 +0200 Subject: [PATCH 173/274] _duplicate_node_temp is public and renamed to node_tempfile --- openpype/hosts/nuke/api/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index e51f1f7586..6675e3ca5e 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2702,7 +2702,7 @@ class DirmapCache: @contextlib.contextmanager -def _duplicate_node_temp(): +def node_tempfile(): """Create a temp file where node is pasted during duplication. This is to avoid using clipboard for node duplication. @@ -2729,7 +2729,7 @@ def duplicate_node(node): # select required node for duplication node.setSelected(True) - with _duplicate_node_temp() as filepath: + with node_tempfile() as filepath: # copy selected to temp filepath nuke.nodeCopy(filepath) From 4cbc82778ea0aaa9fbeb333d4047b59544d68577 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 10:49:32 +0200 Subject: [PATCH 174/274] moved 'get_main_window' to nuke lib --- openpype/hosts/nuke/api/lib.py | 17 +++++++++++++++++ openpype/hosts/nuke/api/pipeline.py | 20 ++------------------ 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 6675e3ca5e..9f28194a93 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -76,6 +76,23 @@ class Context: _project_doc = None +def get_main_window(): + """Acquire Nuke's main window""" + if Context.main_window is None: + from Qt import QtWidgets + + top_widgets = QtWidgets.QApplication.topLevelWidgets() + name = "Foundry::UI::DockMainWindow" + for widget in top_widgets: + if ( + widget.inherits("QMainWindow") + and widget.metaObject().className() == name + ): + Context.main_window = widget + break + return Context.main_window + + class Knobby(object): """For creating knob which it's type isn't mapped in `create_knobs` diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py index c1cd8f771a..c680cd9119 100644 --- a/openpype/hosts/nuke/api/pipeline.py +++ b/openpype/hosts/nuke/api/pipeline.py @@ -26,6 +26,8 @@ from openpype.tools.utils import host_tools from .command import viewer_update_and_undo_stop from .lib import ( + Context, + get_main_window, add_publish_knob, WorkfileSettings, process_workfile_builder, @@ -33,7 +35,6 @@ from .lib import ( check_inventory_versions, set_avalon_knob_data, read_avalon_data, - Context ) log = Logger.get_logger(__name__) @@ -53,23 +54,6 @@ if os.getenv("PYBLISH_GUI", None): pyblish.api.register_gui(os.getenv("PYBLISH_GUI", None)) -def get_main_window(): - """Acquire Nuke's main window""" - if Context.main_window is None: - from Qt import QtWidgets - - top_widgets = QtWidgets.QApplication.topLevelWidgets() - name = "Foundry::UI::DockMainWindow" - for widget in top_widgets: - if ( - widget.inherits("QMainWindow") - and widget.metaObject().className() == name - ): - Context.main_window = widget - break - return Context.main_window - - def reload_config(): """Attempt to reload pipeline at run-time. From d31d004f67c62918ea48a66e982a1489d0ca49cb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 10:50:04 +0200 Subject: [PATCH 175/274] added few helper functions to lib --- openpype/hosts/nuke/api/lib.py | 97 ++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 9f28194a93..b8fbecd874 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2817,3 +2817,100 @@ def ls_img_sequence(path): } return False + + +def get_group_io_nodes(nodes): + """Get the input and the output of a group of nodes.""" + + if not nodes: + raise ValueError("there is no nodes in the list") + + input_node = None + output_node = None + + if len(nodes) == 1: + input_node = output_node = nodes[0] + + else: + for node in nodes: + if "Input" in node.name(): + input_node = node + + if "Output" in node.name(): + output_node = node + + if input_node is not None and output_node is not None: + break + + if input_node is None: + raise ValueError("No Input found") + + if output_node is None: + raise ValueError("No Output found") + return input_node, output_node + + +def get_extreme_positions(nodes): + """Get the 4 numbers that represent the box of a group of nodes.""" + + if not nodes: + raise ValueError("there is no nodes in the list") + + nodes_xpos = [n.xpos() for n in nodes] + \ + [n.xpos() + n.screenWidth() for n in nodes] + + nodes_ypos = [n.ypos() for n in nodes] + \ + [n.ypos() + n.screenHeight() for n in nodes] + + min_x, min_y = (min(nodes_xpos), min(nodes_ypos)) + max_x, max_y = (max(nodes_xpos), max(nodes_ypos)) + return min_x, min_y, max_x, max_y + + +def refresh_node(node): + """Correct a bug caused by the multi-threading of nuke. + + Refresh the node to make sure that it takes the desired attributes. + """ + + x = node.xpos() + y = node.ypos() + nuke.autoplaceSnap(node) + node.setXYpos(x, y) + + +def refresh_nodes(nodes): + for node in nodes: + refresh_node(node) + + +def get_names_from_nodes(nodes): + """Get list of nodes names. + + Args: + nodes(List[nuke.Node]): List of nodes to convert into names. + + Returns: + List[str]: Name of passed nodes. + """ + + return [ + node.name() + for node in nodes + ] + + +def get_nodes_by_names(names): + """Get list of nuke nodes based on their names. + + Args: + names (List[str]): List of node names to be found. + + Returns: + List[nuke.Node]: List of nodes found by name. + """ + + return [ + nuke.toNode(name) + for name in names + ] From f339eba67036260c819c364b844552ee32df220f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 11:39:22 +0200 Subject: [PATCH 176/274] copied template build logic from https://github.com/pypeclub/OpenPype/pull/3681 --- .../hosts/nuke/api/lib_template_builder.py | 206 +++++++ openpype/hosts/nuke/api/template_loader.py | 534 ++++++++++++++++++ 2 files changed, 740 insertions(+) create mode 100644 openpype/hosts/nuke/api/lib_template_builder.py create mode 100644 openpype/hosts/nuke/api/template_loader.py diff --git a/openpype/hosts/nuke/api/lib_template_builder.py b/openpype/hosts/nuke/api/lib_template_builder.py new file mode 100644 index 0000000000..b95a6edf7b --- /dev/null +++ b/openpype/hosts/nuke/api/lib_template_builder.py @@ -0,0 +1,206 @@ +from collections import OrderedDict + +from openpype.vendor.python.common import qargparse +from openpype.tools.utils.widgets import OptionDialog +from openpype.hosts.nuke.api.lib import imprint +import nuke + + +# To change as enum +build_types = ["context_asset", "linked_asset", "all_assets"] + + +def get_placeholder_attributes(node, enumerate=False): + list_atts = ['builder_type', 'family', 'representation', 'loader', + 'loader_args', 'order', 'asset', 'subset', + 'hierarchy', 'siblings', 'last_loaded'] + attributes = {} + for attr in node.knobs().keys(): + if attr in list_atts: + if enumerate: + try: + attributes[attr] = node.knob(attr).values() + except AttributeError: + attributes[attr] = node.knob(attr).getValue() + else: + attributes[attr] = node.knob(attr).getValue() + + return attributes + + +def delete_placeholder_attributes(node): + ''' + function to delete all extra placeholder attributes + ''' + extra_attributes = get_placeholder_attributes(node) + for attribute in extra_attributes.keys(): + try: + node.removeKnob(node.knob(attribute)) + except ValueError: + continue + + +def hide_placeholder_attributes(node): + ''' + function to hide all extra placeholder attributes + ''' + extra_attributes = get_placeholder_attributes(node) + for attribute in extra_attributes.keys(): + try: + node.knob(attribute).setVisible(False) + except ValueError: + continue + + +def create_placeholder(): + + args = placeholder_window() + + if not args: + return # operation canceled, no locator created + + placeholder = nuke.nodes.NoOp() + placeholder.setName('PLACEHOLDER') + placeholder.knob('tile_color').setValue(4278190335) + + # custom arg parse to force empty data query + # and still imprint them on placeholder + # and getting items when arg is of type Enumerator + options = OrderedDict() + for arg in args: + if not type(arg) == qargparse.Separator: + options[str(arg)] = arg._data.get("items") or arg.read() + imprint(placeholder, options) + imprint(placeholder, {'is_placeholder': True}) + placeholder.knob('is_placeholder').setVisible(False) + + +def update_placeholder(): + placeholder = nuke.selectedNodes() + if not placeholder: + raise ValueError("No node selected") + if len(placeholder) > 1: + raise ValueError("Too many selected nodes") + placeholder = placeholder[0] + + args = placeholder_window(get_placeholder_attributes(placeholder)) + if not args: + return # operation canceled + # delete placeholder attributes + delete_placeholder_attributes(placeholder) + + options = OrderedDict() + for arg in args: + if not type(arg) == qargparse.Separator: + options[str(arg)] = arg._data.get("items") or arg.read() + imprint(placeholder, options) + + +def imprint_enum(placeholder, args): + """ + Imprint method doesn't act properly with enums. + Replacing the functionnality with this for now + """ + enum_values = {str(arg): arg.read() + for arg in args if arg._data.get("items")} + string_to_value_enum_table = { + build: i for i, build + in enumerate(build_types)} + attrs = {} + for key, value in enum_values.items(): + attrs[key] = string_to_value_enum_table[value] + + +def placeholder_window(options=None): + from openpype.hosts.nuke.api.pipeline import get_main_window + options = options or dict() + dialog = OptionDialog(parent=get_main_window()) + dialog.setWindowTitle("Create Placeholder") + + args = [ + qargparse.Separator("Main attributes"), + qargparse.Enum( + "builder_type", + label="Asset Builder Type", + default=options.get("builder_type", 0), + items=build_types, + help="""Asset Builder Type +Builder type describe what template loader will look for. + +context_asset : Template loader will look for subsets of +current context asset (Asset bob will find asset) + +linked_asset : Template loader will look for assets linked +to current context asset. +Linked asset are looked in avalon database under field "inputLinks" +""" + ), + qargparse.String( + "family", + default=options.get("family", ""), + label="OpenPype Family", + placeholder="ex: model, look ..."), + qargparse.String( + "representation", + default=options.get("representation", ""), + label="OpenPype Representation", + placeholder="ex: ma, abc ..."), + qargparse.String( + "loader", + default=options.get("loader", ""), + label="Loader", + placeholder="ex: ReferenceLoader, LightLoader ...", + help="""Loader + +Defines what openpype loader will be used to load assets. +Useable loader depends on current host's loader list. +Field is case sensitive. +"""), + qargparse.String( + "loader_args", + default=options.get("loader_args", ""), + label="Loader Arguments", + placeholder='ex: {"camera":"persp", "lights":True}', + help="""Loader + +Defines a dictionnary of arguments used to load assets. +Useable arguments depend on current placeholder Loader. +Field should be a valid python dict. Anything else will be ignored. +"""), + qargparse.Integer( + "order", + default=options.get("order", 0), + min=0, + max=999, + label="Order", + placeholder="ex: 0, 100 ... (smallest order loaded first)", + help="""Order + +Order defines asset loading priority (0 to 999) +Priority rule is : "lowest is first to load"."""), + qargparse.Separator( + "Optional attributes "), + qargparse.String( + "asset", + default=options.get("asset", ""), + label="Asset filter", + placeholder="regex filtering by asset name", + help="Filtering assets by matching field regex to asset's name"), + qargparse.String( + "subset", + default=options.get("subset", ""), + label="Subset filter", + placeholder="regex filtering by subset name", + help="Filtering assets by matching field regex to subset's name"), + qargparse.String( + "hierarchy", + default=options.get("hierarchy", ""), + label="Hierarchy filter", + placeholder="regex filtering by asset's hierarchy", + help="Filtering assets by matching field asset's hierarchy") + ] + dialog.create(args) + if not dialog.exec_(): + return None + + return args diff --git a/openpype/hosts/nuke/api/template_loader.py b/openpype/hosts/nuke/api/template_loader.py new file mode 100644 index 0000000000..861498d2e5 --- /dev/null +++ b/openpype/hosts/nuke/api/template_loader.py @@ -0,0 +1,534 @@ +from openpype.hosts.nuke.api.lib_template_builder import ( + delete_placeholder_attributes, get_placeholder_attributes, + hide_placeholder_attributes) +from openpype.lib.abstract_template_loader import ( + AbstractPlaceholder, + AbstractTemplateLoader) +import nuke +from collections import defaultdict +from openpype.hosts.nuke.api.lib import ( + find_free_space_to_paste_nodes, get_extremes, get_io, imprint, + refresh_node, refresh_nodes, reset_selection, + get_names_from_nodes, get_nodes_from_names, select_nodes) +PLACEHOLDER_SET = 'PLACEHOLDERS_SET' + + +class NukeTemplateLoader(AbstractTemplateLoader): + """Concrete implementation of AbstractTemplateLoader for Nuke + + """ + + def import_template(self, path): + """Import template into current scene. + Block if a template is already loaded. + + Args: + path (str): A path to current template (usually given by + get_template_path implementation) + + Returns: + bool: Wether the template was succesfully imported or not + """ + + # TODO check if the template is already imported + + nuke.nodePaste(path) + reset_selection() + + return True + + def preload(self, placeholder, loaders_by_name, last_representation): + placeholder.data["nodes_init"] = nuke.allNodes() + placeholder.data["_id"] = last_representation['_id'] + + def populate_template(self, ignored_ids=None): + place_holders = self.get_template_nodes() + while len(place_holders) > 0: + super().populate_template(ignored_ids) + place_holders = self.get_template_nodes() + + @staticmethod + def get_template_nodes(): + placeholders = [] + allGroups = [nuke.thisGroup()] + while len(allGroups) > 0: + group = allGroups.pop(0) + for node in group.nodes(): + if "builder_type" in node.knobs().keys() and ( + 'is_placeholder' in node.knobs().keys() + and node.knob('is_placeholder').value()): + if 'empty' in node.knobs().keys()\ + and node.knob('empty').value(): + continue + placeholders += [node] + if isinstance(node, nuke.Group): + allGroups.append(node) + + return placeholders + + def update_missing_containers(self): + nodes_byId = {} + nodes_byId = defaultdict(lambda: [], nodes_byId) + + for n in nuke.allNodes(): + if 'id_rep' in n.knobs().keys(): + nodes_byId[n.knob('id_rep').getValue()] += [n.name()] + if 'empty' in n.knobs().keys(): + n.removeKnob(n.knob('empty')) + imprint(n, {"empty": False}) + for s in nodes_byId.values(): + n = None + for name in s: + n = nuke.toNode(name) + if 'builder_type' in n.knobs().keys(): + break + if n is not None and 'builder_type' in n.knobs().keys(): + + placeholder = nuke.nodes.NoOp() + placeholder.setName('PLACEHOLDER') + placeholder.knob('tile_color').setValue(4278190335) + attributes = get_placeholder_attributes(n, enumerate=True) + imprint(placeholder, attributes) + x = int(n.knob('x').getValue()) + y = int(n.knob('y').getValue()) + placeholder.setXYpos(x, y) + imprint(placeholder, {'nb_children': 1}) + refresh_node(placeholder) + + self.populate_template(self.get_loaded_containers_by_id()) + + def get_loaded_containers_by_id(self): + ids = [] + for n in nuke.allNodes(): + if 'id_rep' in n.knobs(): + ids.append(n.knob('id_rep').getValue()) + + # Removes duplicates in the list + ids = list(set(ids)) + return ids + + def get_placeholders(self): + placeholders = super().get_placeholders() + return placeholders + + def delete_placeholder(self, placeholder): + node = placeholder.data['node'] + lastLoaded = placeholder.data['last_loaded'] + if not placeholder.data['delete']: + if 'empty' in node.knobs().keys(): + node.removeKnob(node.knob('empty')) + imprint(node, {"empty": True}) + else: + if lastLoaded: + if 'last_loaded' in node.knobs().keys(): + for s in node.knob('last_loaded').values(): + n = nuke.toNode(s) + try: + delete_placeholder_attributes(n) + except Exception: + pass + + lastLoaded_names = [] + for loadedNode in lastLoaded: + lastLoaded_names.append(loadedNode.name()) + imprint(node, {'last_loaded': lastLoaded_names}) + + for n in lastLoaded: + refresh_node(n) + refresh_node(node) + if 'builder_type' not in n.knobs().keys(): + attributes = get_placeholder_attributes(node, True) + imprint(n, attributes) + imprint(n, {'is_placeholder': False}) + hide_placeholder_attributes(n) + n.knob('is_placeholder').setVisible(False) + imprint(n, {'x': node.xpos(), 'y': node.ypos()}) + n.knob('x').setVisible(False) + n.knob('y').setVisible(False) + nuke.delete(node) + + +class NukePlaceholder(AbstractPlaceholder): + """Concrete implementation of AbstractPlaceholder for Nuke + + """ + + optional_attributes = {'asset', 'subset', 'hierarchy'} + + def get_data(self, node): + user_data = dict() + dictKnobs = node.knobs() + for attr in self.attributes.union(self.optional_attributes): + if attr in dictKnobs.keys(): + user_data[attr] = dictKnobs[attr].getValue() + user_data['node'] = node + + if 'nb_children' in dictKnobs.keys(): + user_data['nb_children'] = int(dictKnobs['nb_children'].getValue()) + else: + user_data['nb_children'] = 0 + if 'siblings' in dictKnobs.keys(): + user_data['siblings'] = dictKnobs['siblings'].values() + else: + user_data['siblings'] = [] + + fullName = node.fullName() + user_data['group_name'] = fullName.rpartition('.')[0] + user_data['last_loaded'] = [] + user_data['delete'] = False + self.data = user_data + + def parent_in_hierarchy(self, containers): + return + + def create_sib_copies(self): + """ creating copies of the palce_holder siblings (the ones who were + loaded with it) for the new nodes added + + Returns : + copies (dict) : with copied nodes names and their copies + """ + + copies = {} + siblings = get_nodes_from_names(self.data['siblings']) + for n in siblings: + reset_selection() + n.setSelected(True) + nuke.nodeCopy("%clipboard%") + reset_selection() + nuke.nodePaste("%clipboard%") + new_node = nuke.selectedNodes()[0] + x_init = int(new_node.knob('x_init').getValue()) + y_init = int(new_node.knob('y_init').getValue()) + new_node.setXYpos(x_init, y_init) + if isinstance(new_node, nuke.BackdropNode): + w_init = new_node.knob('w_init').getValue() + h_init = new_node.knob('h_init').getValue() + new_node.knob('bdwidth').setValue(w_init) + new_node.knob('bdheight').setValue(h_init) + refresh_node(n) + + if 'id_rep' in n.knobs().keys(): + n.removeKnob(n.knob('id_rep')) + copies[n.name()] = new_node + return copies + + def fix_z_order(self): + """ + fix the problem of z_order when a backdrop is loaded + """ + orders_bd = [] + nodes_loaded = self.data['last_loaded'] + for n in nodes_loaded: + if isinstance(n, nuke.BackdropNode): + orders_bd.append(n.knob("z_order").getValue()) + + if orders_bd: + + min_order = min(orders_bd) + siblings = self.data["siblings"] + + orders_sib = [] + for s in siblings: + n = nuke.toNode(s) + if isinstance(n, nuke.BackdropNode): + orders_sib.append(n.knob("z_order").getValue()) + if orders_sib: + max_order = max(orders_sib) + for n in nodes_loaded: + if isinstance(n, nuke.BackdropNode): + z_order = n.knob("z_order").getValue() + n.knob("z_order").setValue( + z_order + max_order - min_order + 1) + + def update_nodes(self, nodes, considered_nodes, offset_y=None): + """ Adjust backdrop nodes dimensions and positions considering some nodes + sizes + + Arguments: + nodes (list): list of nodes to update + considered_nodes (list) : list of nodes to consider while updating + positions and dimensions + offset (int) : distance between copies + """ + node = self.data['node'] + + min_x, min_y, max_x, max_y = get_extremes(considered_nodes) + + diff_x = diff_y = 0 + contained_nodes = [] # for backdrops + + if offset_y is None: + width_ph = node.screenWidth() + height_ph = node.screenHeight() + diff_y = max_y - min_y - height_ph + diff_x = max_x - min_x - width_ph + contained_nodes = [node] + min_x = node.xpos() + min_y = node.ypos() + else: + siblings = get_nodes_from_names(self.data['siblings']) + minX, _, maxX, _ = get_extremes(siblings) + diff_y = max_y - min_y + 20 + diff_x = abs(max_x - min_x - maxX + minX) + contained_nodes = considered_nodes + + if diff_y > 0 or diff_x > 0: + for n in nodes: + refresh_node(n) + if n != node and n not in considered_nodes: + + if not isinstance(n, nuke.BackdropNode)\ + or isinstance(n, nuke.BackdropNode)\ + and not set(contained_nodes) <= set(n.getNodes()): + if offset_y is None and n.xpos() >= min_x: + n.setXpos(n.xpos() + diff_x) + + if n.ypos() >= min_y: + n.setYpos(n.ypos() + diff_y) + + else: + width = n.screenWidth() + height = n.screenHeight() + n.knob("bdwidth").setValue(width + diff_x) + n.knob("bdheight").setValue(height + diff_y) + + refresh_node(n) + + def imprint_inits(self): + """ + add initial positions and dimensions to the attributes + """ + for n in nuke.allNodes(): + refresh_node(n) + imprint(n, {'x_init': n.xpos(), 'y_init': n.ypos()}) + n.knob('x_init').setVisible(False) + n.knob('y_init').setVisible(False) + width = n.screenWidth() + height = n.screenHeight() + if 'bdwidth' in n.knobs().keys(): + imprint(n, {'w_init': width, 'h_init': height}) + n.knob('w_init').setVisible(False) + n.knob('h_init').setVisible(False) + refresh_node(n) + + def imprint_siblings(self): + """ + - add siblings names to placeholder attributes (nodes loaded with it) + - add Id to the attributes of all the other nodes + """ + + nodes_loaded = self.data['last_loaded'] + d = {"id_rep": str(self.data['_id'])} + + for n in nodes_loaded: + if "builder_type" in n.knobs().keys()\ + and ('is_placeholder' not in n.knobs().keys() + or 'is_placeholder' in n.knobs().keys() + and n.knob('is_placeholder').value()): + + siblings = list(set(nodes_loaded) - set([n])) + siblings_name = get_names_from_nodes(siblings) + siblings = {"siblings": siblings_name} + imprint(n, siblings) + + elif 'builder_type' not in n.knobs().keys(): + # save the id of representation for all imported nodes + imprint(n, d) + n.knob('id_rep').setVisible(False) + refresh_node(n) + + def set_loaded_connections(self): + """ + set inputs and outputs of loaded nodes""" + + node = self.data['node'] + input, output = get_io(self.data['last_loaded']) + for n in node.dependent(): + for i in range(n.inputs()): + if n.input(i) == node: + n.setInput(i, output) + + for n in node.dependencies(): + for i in range(node.inputs()): + if node.input(i) == n: + input.setInput(0, n) + + def set_copies_connections(self, copies): + """ + set inputs and outputs of the copies + + Arguments : + copies (dict) : with copied nodes names and their copies + """ + input, output = get_io(self.data['last_loaded']) + siblings = get_nodes_from_names(self.data['siblings']) + inp, out = get_io(siblings) + inp_copy, out_copy = (copies[inp.name()], copies[out.name()]) + + for node_init in siblings: + if node_init != out: + node_copy = copies[node_init.name()] + for n in node_init.dependent(): + for i in range(n.inputs()): + if n.input(i) == node_init: + if n in siblings: + copies[n.name()].setInput(i, node_copy) + else: + input.setInput(0, node_copy) + + for n in node_init.dependencies(): + for i in range(node_init.inputs()): + if node_init.input(i) == n: + if node_init == inp: + inp_copy.setInput(i, n) + elif n in siblings: + node_copy.setInput(i, copies[n.name()]) + else: + node_copy.setInput(i, output) + + inp.setInput(0, out_copy) + + def move_to_placeholder_group(self, nodes_loaded): + """ + opening the placeholder's group and copying loaded nodes in it. + + Returns : + nodes_loaded (list): the new list of pasted nodes + """ + + groups_name = self.data['group_name'] + reset_selection() + select_nodes(nodes_loaded) + if groups_name: + nuke.nodeCopy("%clipboard%") + for n in nuke.selectedNodes(): + nuke.delete(n) + group = nuke.toNode(groups_name) + group.begin() + nuke.nodePaste("%clipboard%") + nodes_loaded = nuke.selectedNodes() + return nodes_loaded + + def clean(self): + print("cleaaaaar") + # deselect all selected nodes + node = self.data['node'] + + # getting the latest nodes added + nodes_init = self.data["nodes_init"] + nodes_loaded = list(set(nuke.allNodes()) - set(nodes_init)) + print(nodes_loaded) + if nodes_loaded: + self.data['delete'] = True + else: + return + nodes_loaded = self.move_to_placeholder_group(nodes_loaded) + self.data['last_loaded'] = nodes_loaded + refresh_nodes(nodes_loaded) + + # positioning of the loaded nodes + min_x, min_y, _, _ = get_extremes(nodes_loaded) + for n in nodes_loaded: + xpos = (n.xpos() - min_x) + node.xpos() + ypos = (n.ypos() - min_y) + node.ypos() + n.setXYpos(xpos, ypos) + refresh_nodes(nodes_loaded) + + self.fix_z_order() # fix the problem of z_order for backdrops + self.imprint_siblings() + + if self.data['nb_children'] == 0: + # save initial nodes postions and dimensions, update them + # and set inputs and outputs of loaded nodes + + self.imprint_inits() + self.update_nodes(nuke.allNodes(), nodes_loaded) + self.set_loaded_connections() + + elif self.data['siblings']: + # create copies of placeholder siblings for the new loaded nodes, + # set their inputs and outpus and update all nodes positions and + # dimensions and siblings names + + siblings = get_nodes_from_names(self.data['siblings']) + refresh_nodes(siblings) + copies = self.create_sib_copies() + new_nodes = list(copies.values()) # copies nodes + self.update_nodes(new_nodes, nodes_loaded) + node.removeKnob(node.knob('siblings')) + new_nodes_name = get_names_from_nodes(new_nodes) + imprint(node, {'siblings': new_nodes_name}) + self.set_copies_connections(copies) + + self.update_nodes(nuke.allNodes(), + new_nodes + nodes_loaded, 20) + + new_siblings = get_names_from_nodes(new_nodes) + self.data['siblings'] = new_siblings + + else: + # if the placeholder doesn't have siblings, the loaded + # nodes will be placed in a free space + + xpointer, ypointer = find_free_space_to_paste_nodes( + nodes_loaded, direction="bottom", offset=200 + ) + n = nuke.createNode("NoOp") + reset_selection() + nuke.delete(n) + for n in nodes_loaded: + xpos = (n.xpos() - min_x) + xpointer + ypos = (n.ypos() - min_y) + ypointer + n.setXYpos(xpos, ypos) + + self.data['nb_children'] += 1 + reset_selection() + # go back to root group + nuke.root().begin() + + def convert_to_db_filters(self, current_asset, linked_asset): + if self.data['builder_type'] == "context_asset": + return [{ + "type": "representation", + "context.asset": { + "$eq": current_asset, "$regex": self.data['asset']}, + "context.subset": {"$regex": self.data['subset']}, + "context.hierarchy": {"$regex": self.data['hierarchy']}, + "context.representation": self.data['representation'], + "context.family": self.data['family'], + }] + + elif self.data['builder_type'] == "linked_asset": + return [{ + "type": "representation", + "context.asset": { + "$eq": asset_name, "$regex": self.data['asset']}, + "context.subset": {"$regex": self.data['subset']}, + "context.hierarchy": {"$regex": self.data['hierarchy']}, + "context.representation": self.data['representation'], + "context.family": self.data['family'], + } for asset_name in linked_asset] + + else: + return [{ + "type": "representation", + "context.asset": {"$regex": self.data['asset']}, + "context.subset": {"$regex": self.data['subset']}, + "context.hierarchy": {"$regex": self.data['hierarchy']}, + "context.representation": self.data['representation'], + "context.family": self.data['family'], + }] + + def err_message(self): + return ( + "Error while trying to load a representation.\n" + "Either the subset wasn't published or the template is malformed." + "\n\n" + "Builder was looking for:\n{attributes}".format( + attributes="\n".join([ + "{}: {}".format(key.title(), value) + for key, value in self.data.items()] + ) + ) + ) From cf2adc4e0172e1478cc64dda27a75ce31f225f2a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 11:40:41 +0200 Subject: [PATCH 177/274] small tweaks of template builder lib --- .../hosts/nuke/api/lib_template_builder.py | 62 ++++++++++++------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/openpype/hosts/nuke/api/lib_template_builder.py b/openpype/hosts/nuke/api/lib_template_builder.py index b95a6edf7b..555d942cea 100644 --- a/openpype/hosts/nuke/api/lib_template_builder.py +++ b/openpype/hosts/nuke/api/lib_template_builder.py @@ -1,19 +1,32 @@ from collections import OrderedDict -from openpype.vendor.python.common import qargparse -from openpype.tools.utils.widgets import OptionDialog -from openpype.hosts.nuke.api.lib import imprint +import qargparse + import nuke +from openpype.tools.utils.widgets import OptionDialog + +from .lib import imprint, get_main_window + # To change as enum build_types = ["context_asset", "linked_asset", "all_assets"] def get_placeholder_attributes(node, enumerate=False): - list_atts = ['builder_type', 'family', 'representation', 'loader', - 'loader_args', 'order', 'asset', 'subset', - 'hierarchy', 'siblings', 'last_loaded'] + list_atts = { + "builder_type", + "family", + "representation", + "loader", + "loader_args", + "order", + "asset", + "subset", + "hierarchy", + "siblings", + "last_loaded" + } attributes = {} for attr in node.knobs().keys(): if attr in list_atts: @@ -29,9 +42,8 @@ def get_placeholder_attributes(node, enumerate=False): def delete_placeholder_attributes(node): - ''' - function to delete all extra placeholder attributes - ''' + """Delete all extra placeholder attributes.""" + extra_attributes = get_placeholder_attributes(node) for attribute in extra_attributes.keys(): try: @@ -41,9 +53,8 @@ def delete_placeholder_attributes(node): def hide_placeholder_attributes(node): - ''' - function to hide all extra placeholder attributes - ''' + """Hide all extra placeholder attributes.""" + extra_attributes = get_placeholder_attributes(node) for attribute in extra_attributes.keys(): try: @@ -53,15 +64,14 @@ def hide_placeholder_attributes(node): def create_placeholder(): - args = placeholder_window() - if not args: - return # operation canceled, no locator created + # operation canceled, no locator created + return placeholder = nuke.nodes.NoOp() - placeholder.setName('PLACEHOLDER') - placeholder.knob('tile_color').setValue(4278190335) + placeholder.setName("PLACEHOLDER") + placeholder.knob("tile_color").setValue(4278190335) # custom arg parse to force empty data query # and still imprint them on placeholder @@ -71,8 +81,8 @@ def create_placeholder(): if not type(arg) == qargparse.Separator: options[str(arg)] = arg._data.get("items") or arg.read() imprint(placeholder, options) - imprint(placeholder, {'is_placeholder': True}) - placeholder.knob('is_placeholder').setVisible(False) + imprint(placeholder, {"is_placeholder": True}) + placeholder.knob("is_placeholder").setVisible(False) def update_placeholder(): @@ -101,18 +111,22 @@ def imprint_enum(placeholder, args): Imprint method doesn't act properly with enums. Replacing the functionnality with this for now """ - enum_values = {str(arg): arg.read() - for arg in args if arg._data.get("items")} + + enum_values = { + str(arg): arg.read() + for arg in args + if arg._data.get("items") + } string_to_value_enum_table = { - build: i for i, build - in enumerate(build_types)} + build: idx + for idx, build in enumerate(build_types) + } attrs = {} for key, value in enum_values.items(): attrs[key] = string_to_value_enum_table[value] def placeholder_window(options=None): - from openpype.hosts.nuke.api.pipeline import get_main_window options = options or dict() dialog = OptionDialog(parent=get_main_window()) dialog.setWindowTitle("Create Placeholder") From aae4854e12774c94a77ab6b6c6beb92dcee300bd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 11:42:23 +0200 Subject: [PATCH 178/274] safer template population --- openpype/hosts/nuke/api/template_loader.py | 31 +++++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/nuke/api/template_loader.py b/openpype/hosts/nuke/api/template_loader.py index 861498d2e5..dbdeb92134 100644 --- a/openpype/hosts/nuke/api/template_loader.py +++ b/openpype/hosts/nuke/api/template_loader.py @@ -42,10 +42,33 @@ class NukeTemplateLoader(AbstractTemplateLoader): placeholder.data["_id"] = last_representation['_id'] def populate_template(self, ignored_ids=None): - place_holders = self.get_template_nodes() - while len(place_holders) > 0: - super().populate_template(ignored_ids) - place_holders = self.get_template_nodes() + processed_key = "_node_processed" + + processed_nodes = [] + nodes = self.get_template_nodes() + while nodes: + # Mark nodes as processed so they're not re-executed + # - that can happen if processing of placeholder node fails + for node in nodes: + imprint(node, {processed_key: True}) + processed_nodes.append(node) + + super(NukeTemplateLoader, self).populate_template(ignored_ids) + + # Recollect nodes to repopulate + nodes = [] + for node in self.get_template_nodes(): + # Skip already processed nodes + if ( + processed_key in node.knobs() + and node.knob(processed_key).value() + ): + continue + nodes.append(node) + + for node in processed_nodes: + if processed_key in node.knobs(): + nuke.removeKnob(node, processed_key) @staticmethod def get_template_nodes(): From d912431833d3f6799583658014b70e621019dd3d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 11:44:18 +0200 Subject: [PATCH 179/274] tweak template loader and use new abstractions --- openpype/hosts/nuke/api/template_loader.py | 711 ++++++++++++--------- 1 file changed, 396 insertions(+), 315 deletions(-) diff --git a/openpype/hosts/nuke/api/template_loader.py b/openpype/hosts/nuke/api/template_loader.py index dbdeb92134..d4a8560c4a 100644 --- a/openpype/hosts/nuke/api/template_loader.py +++ b/openpype/hosts/nuke/api/template_loader.py @@ -1,16 +1,37 @@ -from openpype.hosts.nuke.api.lib_template_builder import ( - delete_placeholder_attributes, get_placeholder_attributes, - hide_placeholder_attributes) -from openpype.lib.abstract_template_loader import ( - AbstractPlaceholder, - AbstractTemplateLoader) +import re +import collections + import nuke -from collections import defaultdict -from openpype.hosts.nuke.api.lib import ( - find_free_space_to_paste_nodes, get_extremes, get_io, imprint, - refresh_node, refresh_nodes, reset_selection, - get_names_from_nodes, get_nodes_from_names, select_nodes) -PLACEHOLDER_SET = 'PLACEHOLDERS_SET' + +from openpype.client import get_representations +from openpype.pipeline import legacy_io +from openpype.pipeline.workfile.abstract_template_loader import ( + AbstractPlaceholder, + AbstractTemplateLoader, +) + +from .lib import ( + find_free_space_to_paste_nodes, + get_extreme_positions, + get_group_io_nodes, + imprint, + refresh_node, + refresh_nodes, + reset_selection, + get_names_from_nodes, + get_nodes_by_names, + select_nodes, + duplicate_node, + node_tempfile, +) + +from .lib_template_builder import ( + delete_placeholder_attributes, + get_placeholder_attributes, + hide_placeholder_attributes +) + +PLACEHOLDER_SET = "PLACEHOLDERS_SET" class NukeTemplateLoader(AbstractTemplateLoader): @@ -39,7 +60,7 @@ class NukeTemplateLoader(AbstractTemplateLoader): def preload(self, placeholder, loaders_by_name, last_representation): placeholder.data["nodes_init"] = nuke.allNodes() - placeholder.data["_id"] = last_representation['_id'] + placeholder.data["last_repre_id"] = str(last_representation["_id"]) def populate_template(self, ignored_ids=None): processed_key = "_node_processed" @@ -73,132 +94,150 @@ class NukeTemplateLoader(AbstractTemplateLoader): @staticmethod def get_template_nodes(): placeholders = [] - allGroups = [nuke.thisGroup()] - while len(allGroups) > 0: - group = allGroups.pop(0) + all_groups = collections.deque() + all_groups.append(nuke.thisGroup()) + while all_groups: + group = all_groups.popleft() for node in group.nodes(): - if "builder_type" in node.knobs().keys() and ( - 'is_placeholder' in node.knobs().keys() - and node.knob('is_placeholder').value()): - if 'empty' in node.knobs().keys()\ - and node.knob('empty').value(): - continue - placeholders += [node] if isinstance(node, nuke.Group): - allGroups.append(node) + all_groups.append(node) + + node_knobs = node.knobs() + if ( + "builder_type" not in node_knobs + or "is_placeholder" not in node_knobs + or not node.knob("is_placeholder").value() + ): + continue + + if "empty" in node_knobs and node.knob("empty").value(): + continue + + placeholders.append(node) return placeholders def update_missing_containers(self): - nodes_byId = {} - nodes_byId = defaultdict(lambda: [], nodes_byId) + nodes_by_id = collections.defaultdict(list) - for n in nuke.allNodes(): - if 'id_rep' in n.knobs().keys(): - nodes_byId[n.knob('id_rep').getValue()] += [n.name()] - if 'empty' in n.knobs().keys(): - n.removeKnob(n.knob('empty')) - imprint(n, {"empty": False}) - for s in nodes_byId.values(): - n = None - for name in s: - n = nuke.toNode(name) - if 'builder_type' in n.knobs().keys(): + for node in nuke.allNodes(): + node_knobs = node.knobs().keys() + if "repre_id" in node_knobs: + repre_id = node.knob("repre_id").getValue() + nodes_by_id[repre_id].append(node.name()) + + if "empty" in node_knobs: + node.removeKnob(node.knob("empty")) + imprint(node, {"empty": False}) + + for node_names in nodes_by_id.values(): + node = None + for node_name in node_names: + node_by_name = nuke.toNode(node_name) + if "builder_type" in node_by_name.knobs().keys(): + node = node_by_name break - if n is not None and 'builder_type' in n.knobs().keys(): - placeholder = nuke.nodes.NoOp() - placeholder.setName('PLACEHOLDER') - placeholder.knob('tile_color').setValue(4278190335) - attributes = get_placeholder_attributes(n, enumerate=True) - imprint(placeholder, attributes) - x = int(n.knob('x').getValue()) - y = int(n.knob('y').getValue()) - placeholder.setXYpos(x, y) - imprint(placeholder, {'nb_children': 1}) - refresh_node(placeholder) + if node is None: + continue + + placeholder = nuke.nodes.NoOp() + placeholder.setName("PLACEHOLDER") + placeholder.knob("tile_color").setValue(4278190335) + attributes = get_placeholder_attributes(node, enumerate=True) + imprint(placeholder, attributes) + pos_x = int(node.knob("x").getValue()) + pos_y = int(node.knob("y").getValue()) + placeholder.setXYpos(pos_x, pos_y) + imprint(placeholder, {"nb_children": 1}) + refresh_node(placeholder) self.populate_template(self.get_loaded_containers_by_id()) def get_loaded_containers_by_id(self): - ids = [] - for n in nuke.allNodes(): - if 'id_rep' in n.knobs(): - ids.append(n.knob('id_rep').getValue()) + repre_ids = set() + for node in nuke.allNodes(): + if "repre_id" in node.knobs(): + repre_ids.add(node.knob("repre_id").getValue()) # Removes duplicates in the list - ids = list(set(ids)) - return ids - - def get_placeholders(self): - placeholders = super().get_placeholders() - return placeholders + return list(repre_ids) def delete_placeholder(self, placeholder): - node = placeholder.data['node'] - lastLoaded = placeholder.data['last_loaded'] - if not placeholder.data['delete']: - if 'empty' in node.knobs().keys(): - node.removeKnob(node.knob('empty')) - imprint(node, {"empty": True}) - else: - if lastLoaded: - if 'last_loaded' in node.knobs().keys(): - for s in node.knob('last_loaded').values(): - n = nuke.toNode(s) - try: - delete_placeholder_attributes(n) - except Exception: - pass + placeholder_node = placeholder.data["node"] + last_loaded = placeholder.data["last_loaded"] + if not placeholder.data["delete"]: + if "empty" in placeholder_node.knobs().keys(): + placeholder_node.removeKnob(placeholder_node.knob("empty")) + imprint(placeholder_node, {"empty": True}) + return - lastLoaded_names = [] - for loadedNode in lastLoaded: - lastLoaded_names.append(loadedNode.name()) - imprint(node, {'last_loaded': lastLoaded_names}) + if not last_loaded: + nuke.delete(placeholder_node) + return - for n in lastLoaded: - refresh_node(n) - refresh_node(node) - if 'builder_type' not in n.knobs().keys(): - attributes = get_placeholder_attributes(node, True) - imprint(n, attributes) - imprint(n, {'is_placeholder': False}) - hide_placeholder_attributes(n) - n.knob('is_placeholder').setVisible(False) - imprint(n, {'x': node.xpos(), 'y': node.ypos()}) - n.knob('x').setVisible(False) - n.knob('y').setVisible(False) - nuke.delete(node) + if "last_loaded" in placeholder_node.knobs().keys(): + for node_name in placeholder_node.knob("last_loaded").values(): + node = nuke.toNode(node_name) + try: + delete_placeholder_attributes(node) + except Exception: + pass + + last_loaded_names = [ + loaded_node.name() + for loaded_node in last_loaded + ] + imprint(placeholder_node, {"last_loaded": last_loaded_names}) + + for node in last_loaded: + refresh_node(node) + refresh_node(placeholder_node) + if "builder_type" not in node.knobs().keys(): + attributes = get_placeholder_attributes(placeholder_node, True) + imprint(node, attributes) + imprint(node, {"is_placeholder": False}) + hide_placeholder_attributes(node) + node.knob("is_placeholder").setVisible(False) + imprint( + node, + { + "x": placeholder_node.xpos(), + "y": placeholder_node.ypos() + } + ) + node.knob("x").setVisible(False) + node.knob("y").setVisible(False) + nuke.delete(placeholder_node) class NukePlaceholder(AbstractPlaceholder): - """Concrete implementation of AbstractPlaceholder for Nuke + """Concrete implementation of AbstractPlaceholder for Nuke""" - """ - - optional_attributes = {'asset', 'subset', 'hierarchy'} + optional_keys = {"asset", "subset", "hierarchy"} def get_data(self, node): user_data = dict() - dictKnobs = node.knobs() - for attr in self.attributes.union(self.optional_attributes): - if attr in dictKnobs.keys(): - user_data[attr] = dictKnobs[attr].getValue() - user_data['node'] = node + node_knobs = node.knobs() + for attr in self.required_keys.union(self.optional_keys): + if attr in node_knobs: + user_data[attr] = node_knobs[attr].getValue() + user_data["node"] = node - if 'nb_children' in dictKnobs.keys(): - user_data['nb_children'] = int(dictKnobs['nb_children'].getValue()) - else: - user_data['nb_children'] = 0 - if 'siblings' in dictKnobs.keys(): - user_data['siblings'] = dictKnobs['siblings'].values() - else: - user_data['siblings'] = [] + nb_children = 0 + if "nb_children" in node_knobs: + nb_children = int(node_knobs["nb_children"].getValue()) + user_data["nb_children"] = nb_children - fullName = node.fullName() - user_data['group_name'] = fullName.rpartition('.')[0] - user_data['last_loaded'] = [] - user_data['delete'] = False + siblings = [] + if "siblings" in node_knobs: + siblings = node_knobs["siblings"].values() + user_data["siblings"] = siblings + + node_full_name = node.fullName() + user_data["group_name"] = node_full_name.rpartition(".")[0] + user_data["last_loaded"] = [] + user_data["delete"] = False self.data = user_data def parent_in_hierarchy(self, containers): @@ -213,127 +252,137 @@ class NukePlaceholder(AbstractPlaceholder): """ copies = {} - siblings = get_nodes_from_names(self.data['siblings']) - for n in siblings: - reset_selection() - n.setSelected(True) - nuke.nodeCopy("%clipboard%") - reset_selection() - nuke.nodePaste("%clipboard%") - new_node = nuke.selectedNodes()[0] - x_init = int(new_node.knob('x_init').getValue()) - y_init = int(new_node.knob('y_init').getValue()) + siblings = get_nodes_by_names(self.data["siblings"]) + for node in siblings: + new_node = duplicate_node(node) + + x_init = int(new_node.knob("x_init").getValue()) + y_init = int(new_node.knob("y_init").getValue()) new_node.setXYpos(x_init, y_init) if isinstance(new_node, nuke.BackdropNode): - w_init = new_node.knob('w_init').getValue() - h_init = new_node.knob('h_init').getValue() - new_node.knob('bdwidth').setValue(w_init) - new_node.knob('bdheight').setValue(h_init) - refresh_node(n) + w_init = new_node.knob("w_init").getValue() + h_init = new_node.knob("h_init").getValue() + new_node.knob("bdwidth").setValue(w_init) + new_node.knob("bdheight").setValue(h_init) + refresh_node(node) - if 'id_rep' in n.knobs().keys(): - n.removeKnob(n.knob('id_rep')) - copies[n.name()] = new_node + if "repre_id" in node.knobs().keys(): + node.removeKnob(node.knob("repre_id")) + copies[node.name()] = new_node return copies def fix_z_order(self): - """ - fix the problem of z_order when a backdrop is loaded - """ - orders_bd = [] - nodes_loaded = self.data['last_loaded'] - for n in nodes_loaded: - if isinstance(n, nuke.BackdropNode): - orders_bd.append(n.knob("z_order").getValue()) + """Fix the problem of z_order when a backdrop is loaded.""" - if orders_bd: + nodes_loaded = self.data["last_loaded"] + loaded_backdrops = [] + bd_orders = set() + for node in nodes_loaded: + if isinstance(node, nuke.BackdropNode): + loaded_backdrops.append(node) + bd_orders.add(node.knob("z_order").getValue()) - min_order = min(orders_bd) - siblings = self.data["siblings"] + if not bd_orders: + return - orders_sib = [] - for s in siblings: - n = nuke.toNode(s) - if isinstance(n, nuke.BackdropNode): - orders_sib.append(n.knob("z_order").getValue()) - if orders_sib: - max_order = max(orders_sib) - for n in nodes_loaded: - if isinstance(n, nuke.BackdropNode): - z_order = n.knob("z_order").getValue() - n.knob("z_order").setValue( - z_order + max_order - min_order + 1) + sib_orders = set() + for node_name in self.data["siblings"]: + node = nuke.toNode(node_name) + if isinstance(node, nuke.BackdropNode): + sib_orders.add(node.knob("z_order").getValue()) + + if not sib_orders: + return + + min_order = min(bd_orders) + max_order = max(sib_orders) + for backdrop_node in loaded_backdrops: + z_order = backdrop_node.knob("z_order").getValue() + backdrop_node.knob("z_order").setValue( + z_order + max_order - min_order + 1) def update_nodes(self, nodes, considered_nodes, offset_y=None): - """ Adjust backdrop nodes dimensions and positions considering some nodes - sizes + """Adjust backdrop nodes dimensions and positions. - Arguments: + Considering some nodes sizes. + + Args: nodes (list): list of nodes to update - considered_nodes (list) : list of nodes to consider while updating - positions and dimensions - offset (int) : distance between copies + considered_nodes (list): list of nodes to consider while updating + positions and dimensions + offset (int): distance between copies """ - node = self.data['node'] - min_x, min_y, max_x, max_y = get_extremes(considered_nodes) + placeholder_node = self.data["node"] + + min_x, min_y, max_x, max_y = get_extreme_positions(considered_nodes) diff_x = diff_y = 0 contained_nodes = [] # for backdrops if offset_y is None: - width_ph = node.screenWidth() - height_ph = node.screenHeight() + width_ph = placeholder_node.screenWidth() + height_ph = placeholder_node.screenHeight() diff_y = max_y - min_y - height_ph diff_x = max_x - min_x - width_ph - contained_nodes = [node] - min_x = node.xpos() - min_y = node.ypos() + contained_nodes = [placeholder_node] + min_x = placeholder_node.xpos() + min_y = placeholder_node.ypos() else: - siblings = get_nodes_from_names(self.data['siblings']) - minX, _, maxX, _ = get_extremes(siblings) + siblings = get_nodes_by_names(self.data["siblings"]) + minX, _, maxX, _ = get_extreme_positions(siblings) diff_y = max_y - min_y + 20 diff_x = abs(max_x - min_x - maxX + minX) contained_nodes = considered_nodes - if diff_y > 0 or diff_x > 0: - for n in nodes: - refresh_node(n) - if n != node and n not in considered_nodes: + if diff_y <= 0 and diff_x <= 0: + return - if not isinstance(n, nuke.BackdropNode)\ - or isinstance(n, nuke.BackdropNode)\ - and not set(contained_nodes) <= set(n.getNodes()): - if offset_y is None and n.xpos() >= min_x: - n.setXpos(n.xpos() + diff_x) + for node in nodes: + refresh_node(node) - if n.ypos() >= min_y: - n.setYpos(n.ypos() + diff_y) + if ( + node == placeholder_node + or node in considered_nodes + ): + continue - else: - width = n.screenWidth() - height = n.screenHeight() - n.knob("bdwidth").setValue(width + diff_x) - n.knob("bdheight").setValue(height + diff_y) + if ( + not isinstance(node, nuke.BackdropNode) + or ( + isinstance(node, nuke.BackdropNode) + and not set(contained_nodes) <= set(node.getNodes()) + ) + ): + if offset_y is None and node.xpos() >= min_x: + node.setXpos(node.xpos() + diff_x) - refresh_node(n) + if node.ypos() >= min_y: + node.setYpos(node.ypos() + diff_y) + + else: + width = node.screenWidth() + height = node.screenHeight() + node.knob("bdwidth").setValue(width + diff_x) + node.knob("bdheight").setValue(height + diff_y) + + refresh_node(node) def imprint_inits(self): - """ - add initial positions and dimensions to the attributes - """ - for n in nuke.allNodes(): - refresh_node(n) - imprint(n, {'x_init': n.xpos(), 'y_init': n.ypos()}) - n.knob('x_init').setVisible(False) - n.knob('y_init').setVisible(False) - width = n.screenWidth() - height = n.screenHeight() - if 'bdwidth' in n.knobs().keys(): - imprint(n, {'w_init': width, 'h_init': height}) - n.knob('w_init').setVisible(False) - n.knob('h_init').setVisible(False) - refresh_node(n) + """Add initial positions and dimensions to the attributes""" + + for node in nuke.allNodes(): + refresh_node(node) + imprint(node, {"x_init": node.xpos(), "y_init": node.ypos()}) + node.knob("x_init").setVisible(False) + node.knob("y_init").setVisible(False) + width = node.screenWidth() + height = node.screenHeight() + if "bdwidth" in node.knobs(): + imprint(node, {"w_init": width, "h_init": height}) + node.knob("w_init").setVisible(False) + node.knob("h_init").setVisible(False) + refresh_node(node) def imprint_siblings(self): """ @@ -341,76 +390,88 @@ class NukePlaceholder(AbstractPlaceholder): - add Id to the attributes of all the other nodes """ - nodes_loaded = self.data['last_loaded'] - d = {"id_rep": str(self.data['_id'])} + loaded_nodes = self.data["last_loaded"] + loaded_nodes_set = set(loaded_nodes) + data = {"repre_id": str(self.data["last_repre_id"])} - for n in nodes_loaded: - if "builder_type" in n.knobs().keys()\ - and ('is_placeholder' not in n.knobs().keys() - or 'is_placeholder' in n.knobs().keys() - and n.knob('is_placeholder').value()): + for node in loaded_nodes: + node_knobs = node.knobs() + if "builder_type" not in node_knobs: + # save the id of representation for all imported nodes + imprint(node, data) + node.knob("repre_id").setVisible(False) + refresh_node(node) + continue - siblings = list(set(nodes_loaded) - set([n])) + if ( + "is_placeholder" not in node_knobs + or ( + "is_placeholder" in node_knobs + and node.knob("is_placeholder").value() + ) + ): + siblings = list(loaded_nodes_set - {node}) siblings_name = get_names_from_nodes(siblings) siblings = {"siblings": siblings_name} - imprint(n, siblings) - - elif 'builder_type' not in n.knobs().keys(): - # save the id of representation for all imported nodes - imprint(n, d) - n.knob('id_rep').setVisible(False) - refresh_node(n) + imprint(node, siblings) def set_loaded_connections(self): """ set inputs and outputs of loaded nodes""" - node = self.data['node'] - input, output = get_io(self.data['last_loaded']) - for n in node.dependent(): - for i in range(n.inputs()): - if n.input(i) == node: - n.setInput(i, output) + placeholder_node = self.data["node"] + input_node, output_node = get_group_io_nodes(self.data["last_loaded"]) + for node in placeholder_node.dependent(): + for idx in range(node.inputs()): + if node.input(idx) == placeholder_node: + node.setInput(idx, output_node) - for n in node.dependencies(): - for i in range(node.inputs()): - if node.input(i) == n: - input.setInput(0, n) + for node in placeholder_node.dependencies(): + for idx in range(placeholder_node.inputs()): + if placeholder_node.input(idx) == node: + input_node.setInput(0, node) def set_copies_connections(self, copies): - """ - set inputs and outputs of the copies + """Set inputs and outputs of the copies. - Arguments : - copies (dict) : with copied nodes names and their copies + Args: + copies (dict): Copied nodes by their names. """ - input, output = get_io(self.data['last_loaded']) - siblings = get_nodes_from_names(self.data['siblings']) - inp, out = get_io(siblings) - inp_copy, out_copy = (copies[inp.name()], copies[out.name()]) + + last_input, last_output = get_group_io_nodes(self.data["last_loaded"]) + siblings = get_nodes_by_names(self.data["siblings"]) + siblings_input, siblings_output = get_group_io_nodes(siblings) + copy_input = copies[siblings_input.name()] + copy_output = copies[siblings_output.name()] for node_init in siblings: - if node_init != out: - node_copy = copies[node_init.name()] - for n in node_init.dependent(): - for i in range(n.inputs()): - if n.input(i) == node_init: - if n in siblings: - copies[n.name()].setInput(i, node_copy) - else: - input.setInput(0, node_copy) + if node_init == siblings_output: + continue - for n in node_init.dependencies(): - for i in range(node_init.inputs()): - if node_init.input(i) == n: - if node_init == inp: - inp_copy.setInput(i, n) - elif n in siblings: - node_copy.setInput(i, copies[n.name()]) - else: - node_copy.setInput(i, output) + node_copy = copies[node_init.name()] + for node in node_init.dependent(): + for idx in range(node.inputs()): + if node.input(idx) != node_init: + continue - inp.setInput(0, out_copy) + if node in siblings: + copies[node.name()].setInput(idx, node_copy) + else: + last_input.setInput(0, node_copy) + + for node in node_init.dependencies(): + for idx in range(node_init.inputs()): + if node_init.input(idx) != node: + continue + + if node_init == siblings_input: + copy_input.setInput(idx, node) + elif node in siblings: + node_copy.setInput(idx, copies[node.name()]) + else: + node_copy.setInput(idx, last_output) + + siblings_input.setInput(0, copy_output) def move_to_placeholder_group(self, nodes_loaded): """ @@ -420,48 +481,49 @@ class NukePlaceholder(AbstractPlaceholder): nodes_loaded (list): the new list of pasted nodes """ - groups_name = self.data['group_name'] + groups_name = self.data["group_name"] reset_selection() select_nodes(nodes_loaded) if groups_name: - nuke.nodeCopy("%clipboard%") - for n in nuke.selectedNodes(): - nuke.delete(n) - group = nuke.toNode(groups_name) - group.begin() - nuke.nodePaste("%clipboard%") - nodes_loaded = nuke.selectedNodes() + with node_tempfile() as filepath: + nuke.nodeCopy(filepath) + for node in nuke.selectedNodes(): + nuke.delete(node) + group = nuke.toNode(groups_name) + group.begin() + nuke.nodePaste(filepath) + nodes_loaded = nuke.selectedNodes() return nodes_loaded def clean(self): - print("cleaaaaar") # deselect all selected nodes - node = self.data['node'] + placeholder_node = self.data["node"] # getting the latest nodes added nodes_init = self.data["nodes_init"] nodes_loaded = list(set(nuke.allNodes()) - set(nodes_init)) - print(nodes_loaded) - if nodes_loaded: - self.data['delete'] = True - else: + self.log.debug("Loaded nodes: {}".format(nodes_loaded)) + if not nodes_loaded: return + + self.data["delete"] = True + nodes_loaded = self.move_to_placeholder_group(nodes_loaded) - self.data['last_loaded'] = nodes_loaded + self.data["last_loaded"] = nodes_loaded refresh_nodes(nodes_loaded) # positioning of the loaded nodes - min_x, min_y, _, _ = get_extremes(nodes_loaded) - for n in nodes_loaded: - xpos = (n.xpos() - min_x) + node.xpos() - ypos = (n.ypos() - min_y) + node.ypos() - n.setXYpos(xpos, ypos) + min_x, min_y, _, _ = get_extreme_positions(nodes_loaded) + for node in nodes_loaded: + xpos = (node.xpos() - min_x) + placeholder_node.xpos() + ypos = (node.ypos() - min_y) + placeholder_node.ypos() + node.setXYpos(xpos, ypos) refresh_nodes(nodes_loaded) self.fix_z_order() # fix the problem of z_order for backdrops self.imprint_siblings() - if self.data['nb_children'] == 0: + if self.data["nb_children"] == 0: # save initial nodes postions and dimensions, update them # and set inputs and outputs of loaded nodes @@ -469,26 +531,29 @@ class NukePlaceholder(AbstractPlaceholder): self.update_nodes(nuke.allNodes(), nodes_loaded) self.set_loaded_connections() - elif self.data['siblings']: + elif self.data["siblings"]: # create copies of placeholder siblings for the new loaded nodes, # set their inputs and outpus and update all nodes positions and # dimensions and siblings names - siblings = get_nodes_from_names(self.data['siblings']) + siblings = get_nodes_by_names(self.data["siblings"]) refresh_nodes(siblings) copies = self.create_sib_copies() new_nodes = list(copies.values()) # copies nodes self.update_nodes(new_nodes, nodes_loaded) - node.removeKnob(node.knob('siblings')) + placeholder_node.removeKnob(placeholder_node.knob("siblings")) new_nodes_name = get_names_from_nodes(new_nodes) - imprint(node, {'siblings': new_nodes_name}) + imprint(placeholder_node, {"siblings": new_nodes_name}) self.set_copies_connections(copies) - self.update_nodes(nuke.allNodes(), - new_nodes + nodes_loaded, 20) + self.update_nodes( + nuke.allNodes(), + new_nodes + nodes_loaded, + 20 + ) new_siblings = get_names_from_nodes(new_nodes) - self.data['siblings'] = new_siblings + self.data["siblings"] = new_siblings else: # if the placeholder doesn't have siblings, the loaded @@ -497,51 +562,67 @@ class NukePlaceholder(AbstractPlaceholder): xpointer, ypointer = find_free_space_to_paste_nodes( nodes_loaded, direction="bottom", offset=200 ) - n = nuke.createNode("NoOp") + node = nuke.createNode("NoOp") reset_selection() - nuke.delete(n) - for n in nodes_loaded: - xpos = (n.xpos() - min_x) + xpointer - ypos = (n.ypos() - min_y) + ypointer - n.setXYpos(xpos, ypos) + nuke.delete(node) + for node in nodes_loaded: + xpos = (node.xpos() - min_x) + xpointer + ypos = (node.ypos() - min_y) + ypointer + node.setXYpos(xpos, ypos) - self.data['nb_children'] += 1 + self.data["nb_children"] += 1 reset_selection() # go back to root group nuke.root().begin() - def convert_to_db_filters(self, current_asset, linked_asset): - if self.data['builder_type'] == "context_asset": - return [{ - "type": "representation", - "context.asset": { - "$eq": current_asset, "$regex": self.data['asset']}, - "context.subset": {"$regex": self.data['subset']}, - "context.hierarchy": {"$regex": self.data['hierarchy']}, - "context.representation": self.data['representation'], - "context.family": self.data['family'], - }] + def get_representations(self, current_asset_doc, linked_asset_docs): + project_name = legacy_io.active_project() - elif self.data['builder_type'] == "linked_asset": - return [{ - "type": "representation", - "context.asset": { - "$eq": asset_name, "$regex": self.data['asset']}, - "context.subset": {"$regex": self.data['subset']}, - "context.hierarchy": {"$regex": self.data['hierarchy']}, - "context.representation": self.data['representation'], - "context.family": self.data['family'], - } for asset_name in linked_asset] + builder_type = self.data["builder_type"] + if builder_type == "context_asset": + context_filters = { + "asset": [re.compile(self.data["asset"])], + "subset": [re.compile(self.data["subset"])], + "hierarchy": [re.compile(self.data["hierarchy"])], + "representations": [self.data["representation"]], + "family": [self.data["family"]] + } + + elif builder_type != "linked_asset": + context_filters = { + "asset": [ + current_asset_doc["name"], + re.compile(self.data["asset"]) + ], + "subset": [re.compile(self.data["subset"])], + "hierarchy": [re.compile(self.data["hierarchy"])], + "representation": [self.data["representation"]], + "family": [self.data["family"]] + } else: - return [{ - "type": "representation", - "context.asset": {"$regex": self.data['asset']}, - "context.subset": {"$regex": self.data['subset']}, - "context.hierarchy": {"$regex": self.data['hierarchy']}, - "context.representation": self.data['representation'], - "context.family": self.data['family'], - }] + asset_regex = re.compile(self.data["asset"]) + linked_asset_names = [] + for asset_doc in linked_asset_docs: + asset_name = asset_doc["name"] + if asset_regex.match(asset_name): + linked_asset_names.append(asset_name) + + if not linked_asset_names: + return [] + + context_filters = { + "asset": linked_asset_names, + "subset": [re.compile(self.data["subset"])], + "hierarchy": [re.compile(self.data["hierarchy"])], + "representation": [self.data["representation"]], + "family": [self.data["family"]], + } + + return list(get_representations( + project_name, + context_filters=context_filters + )) def err_message(self): return ( From ebcd34a29b84986d660c99aae0ad463b27cf7a54 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 11:44:26 +0200 Subject: [PATCH 180/274] add menu options --- openpype/hosts/nuke/api/pipeline.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py index c680cd9119..bac42128cc 100644 --- a/openpype/hosts/nuke/api/pipeline.py +++ b/openpype/hosts/nuke/api/pipeline.py @@ -22,6 +22,10 @@ from openpype.pipeline import ( AVALON_CONTAINER_ID, ) from openpype.pipeline.workfile import BuildWorkfile +from openpype.pipeline.workfile.build_template import ( + build_workfile_template, + update_workfile_template +) from openpype.tools.utils import host_tools from .command import viewer_update_and_undo_stop @@ -36,6 +40,9 @@ from .lib import ( set_avalon_knob_data, read_avalon_data, ) +from .lib_template_builder import ( + create_placeholder, update_placeholder +) log = Logger.get_logger(__name__) @@ -203,6 +210,24 @@ def _install_menu(): lambda: BuildWorkfile().process() ) + menu_template = menu.addMenu("Template Builder") # creating template menu + menu_template.addCommand( + "Build Workfile from template", + lambda: build_workfile_template() + ) + menu_template.addCommand( + "Update Workfile", + lambda: update_workfile_template() + ) + menu_template.addSeparator() + menu_template.addCommand( + "Create Place Holder", + lambda: create_placeholder() + ) + menu_template.addCommand( + "Update Place Holder", + lambda: update_placeholder() + ) menu.addSeparator() menu.addCommand( "Experimental tools...", From d5f5fedfda0c40e27c4d43591a31d7b94a5b4c79 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 11:44:53 +0200 Subject: [PATCH 181/274] fix host name getter --- openpype/pipeline/workfile/build_template.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/workfile/build_template.py b/openpype/pipeline/workfile/build_template.py index e6396578c5..3328dfbc9e 100644 --- a/openpype/pipeline/workfile/build_template.py +++ b/openpype/pipeline/workfile/build_template.py @@ -1,3 +1,4 @@ +import os from importlib import import_module from openpype.lib import classes_from_module from openpype.host import HostBase @@ -30,7 +31,7 @@ def build_workfile_template(*args): template_loader.populate_template() -def update_workfile_template(args): +def update_workfile_template(*args): template_loader = build_template_loader() template_loader.update_missing_containers() @@ -42,7 +43,10 @@ def build_template_loader(): if isinstance(host, HostBase): host_name = host.name else: - host_name = host.__name__.partition('.')[2] + host_name = os.environ.get("AVALON_APP") + if not host_name: + host_name = host.__name__.split(".")[-2] + module_path = _module_path_format.format(host=host_name) module = import_module(module_path) if not module: From 4ac4c672bfec28c3f886afb84dd10b55001a02dd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 11:45:09 +0200 Subject: [PATCH 182/274] use 'task_names' instead of 'tasks' --- openpype/pipeline/workfile/abstract_template_loader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/workfile/abstract_template_loader.py b/openpype/pipeline/workfile/abstract_template_loader.py index 05a98a1ddc..370dcef20a 100644 --- a/openpype/pipeline/workfile/abstract_template_loader.py +++ b/openpype/pipeline/workfile/abstract_template_loader.py @@ -177,7 +177,7 @@ class AbstractTemplateLoader: build_info["profiles"], { "task_types": task_type, - "tasks": task_name + "task_names": task_name } ) From 400ff1d3aedf656818fb0e92e110d560e5c17333 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 11:53:26 +0200 Subject: [PATCH 183/274] added build documentation from https://github.com/pypeclub/OpenPype/pull/3544 --- website/docs/artist_hosts_hiero.md | 64 ++++++++++++++++++ website/docs/assets/nuke_addProfile.png | Bin 0 -> 24604 bytes .../assets/nuke_buildWorfileFromTemplate.png | Bin 0 -> 29798 bytes website/docs/assets/nuke_buildworkfile.png | Bin 0 -> 36149 bytes .../docs/assets/nuke_createPlaceHolder.png | Bin 0 -> 30703 bytes .../assets/nuke_fillingExtraAttributes.png | Bin 0 -> 30953 bytes website/docs/assets/nuke_placeHolderNode.png | Bin 0 -> 4010 bytes website/docs/assets/nuke_placeholder.png | Bin 0 -> 12169 bytes .../docs/assets/nuke_publishedinstance.png | Bin 0 -> 20541 bytes .../docs/assets/nuke_updatePlaceHolder.png | Bin 0 -> 30608 bytes website/docs/assets/nuke_updateWorkfile.png | Bin 0 -> 30968 bytes 11 files changed, 64 insertions(+) create mode 100644 website/docs/assets/nuke_addProfile.png create mode 100644 website/docs/assets/nuke_buildWorfileFromTemplate.png create mode 100644 website/docs/assets/nuke_buildworkfile.png create mode 100644 website/docs/assets/nuke_createPlaceHolder.png create mode 100644 website/docs/assets/nuke_fillingExtraAttributes.png create mode 100644 website/docs/assets/nuke_placeHolderNode.png create mode 100644 website/docs/assets/nuke_placeholder.png create mode 100644 website/docs/assets/nuke_publishedinstance.png create mode 100644 website/docs/assets/nuke_updatePlaceHolder.png create mode 100644 website/docs/assets/nuke_updateWorkfile.png diff --git a/website/docs/artist_hosts_hiero.md b/website/docs/artist_hosts_hiero.md index dc6f1696e7..d14dcd1c01 100644 --- a/website/docs/artist_hosts_hiero.md +++ b/website/docs/artist_hosts_hiero.md @@ -202,3 +202,67 @@ This video shows a way to publish shot look as effect from Hiero to Nuke. ### Assembling edit from published shot versions + + +# Nuke Build Workfile +This is a tool of Node Graph initialisation using a pre-created template. + +### Add a profile +The path to the template that will be used in the initialisation must be added as a profile on Project Settings. + +![Create menu](assets/nuke_addProfile.png) + +### Create Place Holder + +![Create menu](assets/nuke_createPlaceHolder.png) + +This tool creates a Place Holder, which is a node that will be replaced by published instances. + +![Create menu](assets/nuke_placeHolderNode.png) +#### Result +- Create a red node called `PLACEHOLDER` which can be manipulated as wanted by using it in Node Graph. + +![Create menu](assets/nuke_placeholder.png) + +:::note +All published instances that will replace the place holder must contain unique input and output nodes in case they will not be imported as a single node. +::: + +![Create menu](assets/nuke_publishedinstance.png) + + +The informations about these objects are given by the user by filling the extra attributes of the Place Holder + +![Create menu](assets/nuke_fillingExtraAttributes.png) + + + +### Update Place Holder +This tool alows the user to change the information provided in the extra attributes of the selected Place Holder. + +![Create menu](assets/nuke_updatePlaceHolder.png) + + + +### Build Workfile from template +This tool imports the template used and replaces the existed PlaceHolders with the corresponding published objects (which can contain Place Holders too). In case there is no published items with the description given, the place holder will remain in the node graph. + +![Create menu](assets/nuke_buildWorfileFromTemplate.png) + +#### Result +- Replace `PLACEHOLDER` node in the template with the published instance corresponding to the informations provided in extra attributes of the Place Holder + +![Create menu](assets/nuke_buildworkfile.png) + +:::note +In case the instance that will replace the Place holder **A** contains another Place Holder **B** that points to many published elements, all the nodes that were imported with **A** except **B** will be duplicated for each element that will replace **B** +::: + +### Update Workfile +This tool can be used to check if some instances were published after the last build, so they will be imported. + +![Create menu](assets/nuke_updateWorkfile.png) + +:::note +Imported instances must not be deleted because they contain extra attributes that will be used to update the workfile since the place holder is been deleted. +::: \ No newline at end of file diff --git a/website/docs/assets/nuke_addProfile.png b/website/docs/assets/nuke_addProfile.png new file mode 100644 index 0000000000000000000000000000000000000000..37578df7f52ee4b61e3ba0725d999fc4940288ea GIT binary patch literal 24604 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 literal 0 HcmV?d00001 diff --git a/website/docs/assets/nuke_buildWorfileFromTemplate.png b/website/docs/assets/nuke_buildWorfileFromTemplate.png new file mode 100644 index 0000000000000000000000000000000000000000..77d2de2ff85841cf7910dc714c60e531002b2344 GIT binary patch literal 29798 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 literal 0 HcmV?d00001 diff --git a/website/docs/assets/nuke_buildworkfile.png b/website/docs/assets/nuke_buildworkfile.png new file mode 100644 index 0000000000000000000000000000000000000000..e3d8d07f7c959324c1224f88079084813ad7c101 GIT binary patch literal 36149 zcmZsCbzIaz_b#ZQG$J7#QqtWaAR?VhNGUB{N(m?u(jwhmOS5!JOM~RnA>AEI+(GaA zd++_+%RjLDjhQ)f=A7qw&LL1mNd^m@1RV(p2}}0%OEn}U8d$&U+s6!ak9;6r`(G*dM^bM_!NuhBb64}k{TI)a5jT~ znHt^7cgb;rX!97K@aopn9j2o-W_+Ro>sjdc?`ca*8TMB-J*|F2_0_Gr`b=&Y=!0Sf zT`RROc>djw#jiM>0ti#r1&5uH z+(Gta8(*qN`r+&A7MWMn>A_=2OQrnF+v0dg^rpK>| zPW5N{f4JR0IIZvPG$uvEIySLYdsKi^NblfG8`~Rg(Nx!idF6eLO>@&DiN# z-U_PiTy|d^$uTEWy)yBPAJuQRmm3#4hNpH zN!z_QA+s!TkEIn9?o(14NGW;}2UAk~+`M1#6nA&9?>(|!Q*`G5hHyN$SVD=i&7#qb zbPOw*q>2T~R(P@b%F;0Ygx7h&3-@;&<;pdC)@QwA#0LGGWNDEQyNZ2Nj#)m!SL2$YQ(W#YOR0-;dX7S6Ghcq<4MPSD@zeqnX z?inuv9@uyf(;EGeAyk($GGjF)_{0%KCYAfu5eugg9+Ee(&yGw`%N{#~4j4i(a7F zHu^Gp(n7#VbMg3nwD{FKM&r&9Y8IBhzh?49Jdh~u`HX!3_3PIQvx<95JL#q^+f6eg ze@X>+m^Ilbq&F+^PYo`^=wSXFmRY0^*|v?kufDim%$hC-4nD%#f4h79R+C)2zhXE- zAz`7v_?SGY=A#cvphKZ(17SyYOqbbNre^JzL>FRiJde#b&ovirJ2|o^cr)<$rmwkc zT}wn3m}j-=bCG?qy9qn&K&q0aTEE5N;xU|Mx$Mn-I$sRH z+jXMhio|=+!o+Pp@TW`shI4HoRYpP@LLl|1|2*Oy<{9yzj)AdT;Rzx%UDWDq+KZKN zB9HK$_|YlmyO$8omkf#1mJQzL`|lLuI3&!}lwOW!0m}tBDE5quME=H89xpkg5}tm7 zqpUn$bd*uob6;6mx#^-alD%pF#YkH7>hb$)UX*H4?dkyxWp-tyl%dp-4UvQWeUm{K z16Oxf6`lNMdgk7*1zMsy%`K5Zj-K@ov0ck;lNp4PZ>%h#4=E@N@Cn?$r|t#j=04om zxO5^3eW=@XxG-%ey1##%Q8oVcWb0(&&+2B$bJnAuBW=M^_&9pAA`VVRZ?A=v!^jTW zM|!`0*b=FAlbY}92&O2UUwCe%rjLYnhUW7&`q!`5ouQ;%9){OMb183D2eH>u=o3qR zHyKGETKsmzKAP>=!-RVlZ}(32Kk2lecaZuxe~hb1sV%(}_nJPO-xpD?T>chHU9X(K zSO$MQ%EWJf#zyzv5^2qL9fnv{4x^)PiA>&@c60Y4^ZJtg$=&%r@=fuAW=|Dt`t`TH z-l#w?vYNcX-Qcj{($)Pg|LrC)NMHL4ws|FPUql8xrYbtPnoIk164%Fv6^5uvfrIT; z!DG01jEnnobF6@FX6>a3{atuhfP4D)iFDwLbNl|njCaV{BOo}kNZ5D-YZp?%oFG+7 zl$pm|Q@AD_EV!&9jXdZp=E6YZ)34?TXL)0GNvxc3GM|OwIjK^PsJmDG_k}r%iJi

=-Qwg~sFK#=Fyw)a9rjcCcJZMh> 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 DVlFeL literal 0 HcmV?d00001 diff --git a/website/docs/assets/nuke_createPlaceHolder.png b/website/docs/assets/nuke_createPlaceHolder.png new file mode 100644 index 0000000000000000000000000000000000000000..93fb4de9d01adc65bff170cdaaeaa576074a956f GIT binary patch literal 30703 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 literal 0 HcmV?d00001 diff --git a/website/docs/assets/nuke_fillingExtraAttributes.png b/website/docs/assets/nuke_fillingExtraAttributes.png new file mode 100644 index 0000000000000000000000000000000000000000..146c4d9db61284233c486a83708a8473969f7e44 GIT binary patch literal 30953 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 VDq4MW`0iqf*?J3R#?G+V@jnmA5kmj~ literal 0 HcmV?d00001 diff --git a/website/docs/assets/nuke_placeHolderNode.png b/website/docs/assets/nuke_placeHolderNode.png new file mode 100644 index 0000000000000000000000000000000000000000..ac9e83b9d67c5c976c99bb9016a47a9836eadb39 GIT binary patch literal 4010 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 literal 0 HcmV?d00001 diff --git a/website/docs/assets/nuke_placeholder.png b/website/docs/assets/nuke_placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..d899ff742ce32a76317645bcf60fdf256f98b0c8 GIT binary patch literal 12169 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≪6 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 XvZ_}FZZN?sQ_soEs7Mz|8a@3#;PNPY literal 0 HcmV?d00001 diff --git a/website/docs/assets/nuke_updateWorkfile.png b/website/docs/assets/nuke_updateWorkfile.png new file mode 100644 index 0000000000000000000000000000000000000000..aeeebb123f0971938f79f7b086eb291eb99158fa GIT binary patch literal 30968 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%0A68dafrch8 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 CMAer7 literal 0 HcmV?d00001 From eebfd22e275bea13b7aa81e5fa0bf0ad89881326 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 11:56:24 +0200 Subject: [PATCH 184/274] added nuke specific placeholders --- openpype/hosts/nuke/api/lib_template_builder.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/nuke/api/lib_template_builder.py b/openpype/hosts/nuke/api/lib_template_builder.py index 555d942cea..61baa23928 100644 --- a/openpype/hosts/nuke/api/lib_template_builder.py +++ b/openpype/hosts/nuke/api/lib_template_builder.py @@ -146,24 +146,24 @@ current context asset (Asset bob will find asset) linked_asset : Template loader will look for assets linked to current context asset. -Linked asset are looked in avalon database under field "inputLinks" +Linked asset are looked in OpenPype database under field "inputLinks" """ ), qargparse.String( "family", default=options.get("family", ""), label="OpenPype Family", - placeholder="ex: model, look ..."), + placeholder="ex: image, plate ..."), qargparse.String( "representation", default=options.get("representation", ""), label="OpenPype Representation", - placeholder="ex: ma, abc ..."), + placeholder="ex: mov, png ..."), qargparse.String( "loader", default=options.get("loader", ""), label="Loader", - placeholder="ex: ReferenceLoader, LightLoader ...", + placeholder="ex: LoadClip, LoadImage ...", help="""Loader Defines what openpype loader will be used to load assets. From 5d6b672c9b6ec4a0c9bc0f7069041b7d7c06bfa5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 12:08:09 +0200 Subject: [PATCH 185/274] fix knob removement --- openpype/hosts/nuke/api/template_loader.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/api/template_loader.py b/openpype/hosts/nuke/api/template_loader.py index d4a8560c4a..5ff4b8fc41 100644 --- a/openpype/hosts/nuke/api/template_loader.py +++ b/openpype/hosts/nuke/api/template_loader.py @@ -88,8 +88,9 @@ class NukeTemplateLoader(AbstractTemplateLoader): nodes.append(node) for node in processed_nodes: - if processed_key in node.knobs(): - nuke.removeKnob(node, processed_key) + knob = node.knob(processed_key) + if knob is not None: + node.removeKnob(knob) @staticmethod def get_template_nodes(): From 1ff3b31d4087e92b705cf80ad939332e00a35f5b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 14:43:10 +0200 Subject: [PATCH 186/274] added cli commands for sync server --- .../modules/sync_server/sync_server_module.py | 50 ++++++++++++++++++- openpype/pype_commands.py | 9 +++- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 634b68c55f..a478faa9ef 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -1,11 +1,16 @@ import os -from bson.objectid import ObjectId +import sys +import time from datetime import datetime import threading import platform import copy +import signal from collections import deque, defaultdict +import click +from bson.objectid import ObjectId + from openpype.client import get_projects from openpype.modules import OpenPypeModule from openpype_interfaces import ITrayModule @@ -2080,3 +2085,46 @@ class SyncServerModule(OpenPypeModule, ITrayModule): settings ('presets') """ return presets[project_name]['sites'][site_name]['root'] + + def cli(self, click_group): + click_group.add_command(cli_main) + + +@click.group(SyncServerModule.name, help="SyncServer module related commands.") +def cli_main(): + pass + + +@cli_main.command() +@click.option( + "-a", + "--active_site", + required=True, + help="Name of active stie") +def syncservice(active_site): + """Launch sync server under entered site. + + This should be ideally used by system service (such us systemd or upstart + on linux and window service). + """ + + from openpype.modules import ModulesManager + + os.environ["OPENPYPE_LOCAL_ID"] = active_site + + def signal_handler(sig, frame): + print("You pressed Ctrl+C. Process ended.") + sync_server_module.server_exit() + sys.exit(0) + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + manager = ModulesManager() + sync_server_module = manager.modules_by_name["sync_server"] + + sync_server_module.server_init() + sync_server_module.server_start() + + while True: + time.sleep(1.0) diff --git a/openpype/pype_commands.py b/openpype/pype_commands.py index fe46a4bc54..85561495fd 100644 --- a/openpype/pype_commands.py +++ b/openpype/pype_commands.py @@ -4,6 +4,7 @@ import os import sys import json import time +import signal class PypeCommands: @@ -315,8 +316,12 @@ class PypeCommands: pytest.main(args) def syncserver(self, active_site): - """Start running sync_server in background.""" - import signal + """Start running sync_server in background. + + This functionality is available in directly in module cli commands. + `~/openpype_console module sync_server syncservice` + """ + os.environ["OPENPYPE_LOCAL_ID"] = active_site def signal_handler(sig, frame): From 519f8088674d6e48ffa6d03df4e5e38c2866f6d6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 15:02:55 +0200 Subject: [PATCH 187/274] moved 'create_project' to client operations --- openpype/client/__init__.py | 7 +++ openpype/client/operations.py | 87 +++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index 64a82334d9..4b2d56168f 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -45,6 +45,11 @@ from .entities import ( get_workfile_info, ) +from .operations import ( + create_project, +) + + __all__ = ( "OpenPypeMongoConnection", @@ -88,4 +93,6 @@ __all__ = ( "get_thumbnail_id_from_source", "get_workfile_info", + + "create_project", ) diff --git a/openpype/client/operations.py b/openpype/client/operations.py index 9daaa3e116..48e8645726 100644 --- a/openpype/client/operations.py +++ b/openpype/client/operations.py @@ -9,6 +9,7 @@ from bson.objectid import ObjectId from pymongo import DeleteOne, InsertOne, UpdateOne from .mongo import get_project_connection +from .entities import get_project REMOVED_VALUE = object() @@ -662,3 +663,89 @@ class OperationsSession(object): operation = DeleteOperation(project_name, entity_type, entity_id) self.add(operation) return operation + + +def create_project(project_name, project_code, library_project=False): + """Create project using OpenPype settings. + + This project creation function is not validating project document on + creation. It is because project document is created blindly with only + minimum required information about project which is it's name, code, type + and schema. + + Entered project name must be unique and project must not exist yet. + + Note: + This function is here to be OP v4 ready but in v3 has more logic + to do. That's why inner imports are in the body. + + Args: + project_name(str): New project name. Should be unique. + project_code(str): Project's code should be unique too. + library_project(bool): Project is library project. + + Raises: + ValueError: When project name already exists in MongoDB. + + Returns: + dict: Created project document. + """ + + from openpype.settings import ProjectSettings, SaveWarningExc + from openpype.pipeline.schema import validate + + if get_project(project_name, fields=["name"]): + raise ValueError("Project with name \"{}\" already exists".format( + project_name + )) + + if not PROJECT_NAME_REGEX.match(project_name): + raise ValueError(( + "Project name \"{}\" contain invalid characters" + ).format(project_name)) + + project_doc = { + "type": "project", + "name": project_name, + "data": { + "code": project_code, + "library_project": library_project + }, + "schema": CURRENT_PROJECT_SCHEMA + } + + op_session = OperationsSession() + # Insert document with basic data + create_op = op_session.create_entity( + project_name, project_doc["type"], project_doc + ) + op_session.commit() + + # Load ProjectSettings for the project and save it to store all attributes + # and Anatomy + try: + project_settings_entity = ProjectSettings(project_name) + project_settings_entity.save() + except SaveWarningExc as exc: + print(str(exc)) + except Exception: + op_session.delete_entity( + project_name, project_doc["type"], create_op.entity_id + ) + op_session.commit() + raise + + project_doc = get_project(project_name) + + try: + # Validate created project document + validate(project_doc) + except Exception: + # Remove project if is not valid + op_session.delete_entity( + project_name, project_doc["type"], create_op.entity_id + ) + op_session.commit() + raise + + return project_doc From c62e12a11bf69f98d3c7d6d0c9c062c1086393c0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 15:03:19 +0200 Subject: [PATCH 188/274] marked 'create_project' in lib as deprecated --- openpype/lib/avalon_context.py | 53 ++-------------------------------- 1 file changed, 3 insertions(+), 50 deletions(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 7d56d039d4..d2a0665bf5 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -84,6 +84,7 @@ def deprecated(new_destination): return _decorator(func) +@deprecated("openpype.client.operations.create_project") def create_project( project_name, project_code, library_project=False, dbcon=None ): @@ -109,57 +110,9 @@ def create_project( dict: Created project document. """ - from openpype.settings import ProjectSettings, SaveWarningExc - from openpype.pipeline import AvalonMongoDB - from openpype.pipeline.schema import validate + from openpype.client.operations import create_project - if get_project(project_name, fields=["name"]): - raise ValueError("Project with name \"{}\" already exists".format( - project_name - )) - - if dbcon is None: - dbcon = AvalonMongoDB() - - if not PROJECT_NAME_REGEX.match(project_name): - raise ValueError(( - "Project name \"{}\" contain invalid characters" - ).format(project_name)) - - database = dbcon.database - project_doc = { - "type": "project", - "name": project_name, - "data": { - "code": project_code, - "library_project": library_project - }, - "schema": CURRENT_DOC_SCHEMAS["project"] - } - # Insert document with basic data - database[project_name].insert_one(project_doc) - # Load ProjectSettings for the project and save it to store all attributes - # and Anatomy - try: - project_settings_entity = ProjectSettings(project_name) - project_settings_entity.save() - except SaveWarningExc as exc: - print(str(exc)) - except Exception: - database[project_name].delete_one({"type": "project"}) - raise - - project_doc = get_project(project_name) - - try: - # Validate created project document - validate(project_doc) - except Exception: - # Remove project if is not valid - database[project_name].delete_one({"type": "project"}) - raise - - return project_doc + return create_project(project_name, project_code, library_project) def with_pipeline_io(func): From a1b20c22683b8c26e5158e5cdd601b6c3c01d40e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 15:03:49 +0200 Subject: [PATCH 189/274] use new location of 'create_project' function --- .../ftrack/event_handlers_server/action_prepare_project.py | 6 ++---- .../ftrack/event_handlers_user/action_prepare_project.py | 6 ++---- openpype/modules/kitsu/utils/update_op_with_zou.py | 6 +++--- openpype/tools/project_manager/project_manager/widgets.py | 7 +++---- 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py b/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py index 713a4d9aba..332648cd02 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py +++ b/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py @@ -1,10 +1,8 @@ import json import copy -from openpype.client import get_project -from openpype.api import ProjectSettings -from openpype.lib import create_project -from openpype.settings import SaveWarningExc +from openpype.client import get_project, create_project +from openpype.settings import ProjectSettings, SaveWarningExc from openpype_modules.ftrack.lib import ( ServerAction, diff --git a/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py b/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py index e89595109e..e825198180 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py +++ b/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py @@ -1,10 +1,8 @@ import json import copy -from openpype.client import get_project -from openpype.api import ProjectSettings -from openpype.lib import create_project -from openpype.settings import SaveWarningExc +from openpype.client import get_project, create_project +from openpype.settings import ProjectSettings, SaveWarningExc from openpype_modules.ftrack.lib import ( BaseAction, diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index e03cf2b30e..8d65591c0b 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -15,10 +15,10 @@ from openpype.client import ( get_assets, get_asset_by_id, get_asset_by_name, + create_project, ) from openpype.pipeline import AvalonMongoDB -from openpype.api import get_project_settings -from openpype.lib import create_project +from openpype.settings import get_project_settings from openpype.modules.kitsu.utils.credentials import validate_credentials @@ -278,7 +278,7 @@ def write_project_to_op(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: project_doc = get_project(project_name) if not project_doc: print(f"Creating project '{project_name}'") - project_doc = create_project(project_name, project_name, dbcon=dbcon) + project_doc = create_project(project_name, project_name) # Project data and tasks project_data = project_doc["data"] or {} diff --git a/openpype/tools/project_manager/project_manager/widgets.py b/openpype/tools/project_manager/project_manager/widgets.py index d0715f204d..4bc968347a 100644 --- a/openpype/tools/project_manager/project_manager/widgets.py +++ b/openpype/tools/project_manager/project_manager/widgets.py @@ -1,14 +1,13 @@ import re -from openpype.client import get_projects +from openpype.client import get_projects, create_project from .constants import ( NAME_ALLOWED_SYMBOLS, NAME_REGEX ) -from openpype.lib import create_project from openpype.client.operations import ( PROJECT_NAME_ALLOWED_SYMBOLS, - PROJECT_NAME_REGEX + PROJECT_NAME_REGEX, ) from openpype.style import load_stylesheet from openpype.pipeline import AvalonMongoDB @@ -266,7 +265,7 @@ class CreateProjectDialog(QtWidgets.QDialog): project_name = self.project_name_input.text() project_code = self.project_code_input.text() library_project = self.library_project_input.isChecked() - create_project(project_name, project_code, library_project, self.dbcon) + create_project(project_name, project_code, library_project) self.done(1) From ac273094ba6286df53ff5b07cc3697575865aac1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 15:10:58 +0200 Subject: [PATCH 190/274] added removement version --- openpype/lib/avalon_context.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index d2a0665bf5..08c221cb81 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -108,6 +108,9 @@ def create_project( Returns: dict: Created project document. + + Deprecated: + Function will be removed after release version 3.16.* """ from openpype.client.operations import create_project From acf3d67f242625f8a91317b602ffa6467ff7cc6b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 15:59:20 +0200 Subject: [PATCH 191/274] moved 'create_workdir_extra_folders' to 'openpype.pipeline.workfile' --- .../hooks/pre_create_extra_workdir_folders.py | 6 +- openpype/lib/path_tools.py | 89 ++++++++++++------- openpype/lib/plugin_tools.py | 1 - openpype/pipeline/workfile/__init__.py | 4 + openpype/pipeline/workfile/path_resolving.py | 57 ++++++++++++ openpype/tools/workfiles/files_widget.py | 10 +-- 6 files changed, 126 insertions(+), 41 deletions(-) diff --git a/openpype/hooks/pre_create_extra_workdir_folders.py b/openpype/hooks/pre_create_extra_workdir_folders.py index d79c5831ee..c5af620c87 100644 --- a/openpype/hooks/pre_create_extra_workdir_folders.py +++ b/openpype/hooks/pre_create_extra_workdir_folders.py @@ -1,8 +1,6 @@ import os -from openpype.lib import ( - PreLaunchHook, - create_workdir_extra_folders -) +from openpype.lib import PreLaunchHook +from openpype.pipeline.workfile import create_workdir_extra_folders class AddLastWorkfileToLaunchArgs(PreLaunchHook): diff --git a/openpype/lib/path_tools.py b/openpype/lib/path_tools.py index f60b2fa722..671591bca4 100644 --- a/openpype/lib/path_tools.py +++ b/openpype/lib/path_tools.py @@ -16,6 +16,51 @@ from .profiles_filtering import filter_profiles log = logging.getLogger(__name__) +class PathToolsDeprecatedWarning(DeprecationWarning): + pass + + +def deprecated(new_destination): + """Mark functions as deprecated. + + It will result in a warning being emitted when the function is used. + """ + + func = None + if callable(new_destination): + func = new_destination + new_destination = None + + def _decorator(decorated_func): + if new_destination is None: + warning_message = ( + " Please check content of deprecated function to figure out" + " possible replacement." + ) + else: + warning_message = " Please replace your usage with '{}'.".format( + new_destination + ) + + @functools.wraps(decorated_func) + def wrapper(*args, **kwargs): + warnings.simplefilter("always", PathToolsDeprecatedWarning) + warnings.warn( + ( + "Call to deprecated function '{}'" + "\nFunction was moved or removed.{}" + ).format(decorated_func.__name__, warning_message), + category=PathToolsDeprecatedWarning, + stacklevel=4 + ) + return decorated_func(*args, **kwargs) + return wrapper + + if func is None: + return _decorator + return _decorator(func) + + def format_file_size(file_size, suffix=None): """Returns formatted string with size in appropriate unit. @@ -333,6 +378,7 @@ def get_project_basic_paths(project_name): return _list_path_items(folder_structure) +@deprecated("openpype.pipeline.workfile.create_workdir_extra_folders") def create_workdir_extra_folders( workdir, host_name, task_type, task_name, project_name, project_settings=None @@ -349,37 +395,18 @@ def create_workdir_extra_folders( project_name (str): Name of project on which task is. project_settings (dict): Prepared project settings. Are loaded if not passed. + + Deprecated: + Function will be removed after release version 3.16.* """ - # Load project settings if not set - if not project_settings: - project_settings = get_project_settings(project_name) - # Load extra folders profiles - extra_folders_profiles = ( - project_settings["global"]["tools"]["Workfiles"]["extra_folders"] + from openpype.pipeline.project_folders import create_workdir_extra_folders + + return create_workdir_extra_folders( + workdir, + host_name, + task_type, + task_name, + project_name, + project_settings ) - # Skip if are empty - if not extra_folders_profiles: - return - - # Prepare profiles filters - filter_data = { - "task_types": task_type, - "task_names": task_name, - "hosts": host_name - } - profile = filter_profiles(extra_folders_profiles, filter_data) - if profile is None: - return - - for subfolder in profile["folders"]: - # Make sure backslashes are converted to forwards slashes - # and does not start with slash - subfolder = subfolder.replace("\\", "/").lstrip("/") - # Skip empty strings - if not subfolder: - continue - - fullpath = os.path.join(workdir, subfolder) - if not os.path.exists(fullpath): - os.makedirs(fullpath) diff --git a/openpype/lib/plugin_tools.py b/openpype/lib/plugin_tools.py index 81d268ea1c..1e157dfbfd 100644 --- a/openpype/lib/plugin_tools.py +++ b/openpype/lib/plugin_tools.py @@ -3,7 +3,6 @@ import os import logging import re -import json import warnings import functools diff --git a/openpype/pipeline/workfile/__init__.py b/openpype/pipeline/workfile/__init__.py index 0aad29b6f9..94ecc81bd6 100644 --- a/openpype/pipeline/workfile/__init__.py +++ b/openpype/pipeline/workfile/__init__.py @@ -9,6 +9,8 @@ from .path_resolving import ( get_custom_workfile_template, get_custom_workfile_template_by_string_context, + + create_workdir_extra_folders, ) from .build_workfile import BuildWorkfile @@ -26,5 +28,7 @@ __all__ = ( "get_custom_workfile_template", "get_custom_workfile_template_by_string_context", + "create_workdir_extra_folders", + "BuildWorkfile", ) diff --git a/openpype/pipeline/workfile/path_resolving.py b/openpype/pipeline/workfile/path_resolving.py index 6d9e72dbd2..1243e84148 100644 --- a/openpype/pipeline/workfile/path_resolving.py +++ b/openpype/pipeline/workfile/path_resolving.py @@ -467,3 +467,60 @@ def get_custom_workfile_template_by_string_context( return get_custom_workfile_template( project_doc, asset_doc, task_name, host_name, anatomy, project_settings ) + + +def create_workdir_extra_folders( + workdir, + host_name, + task_type, + task_name, + project_name, + project_settings=None +): + """Create extra folders in work directory based on context. + + Args: + workdir (str): Path to workdir where workfiles is stored. + host_name (str): Name of host implementation. + task_type (str): Type of task for which extra folders should be + created. + task_name (str): Name of task for which extra folders should be + created. + project_name (str): Name of project on which task is. + project_settings (dict): Prepared project settings. Are loaded if not + passed. + """ + + # Load project settings if not set + if not project_settings: + project_settings = get_project_settings(project_name) + + # Load extra folders profiles + extra_folders_profiles = ( + project_settings["global"]["tools"]["Workfiles"]["extra_folders"] + ) + # Skip if are empty + if not extra_folders_profiles: + return + + # Prepare profiles filters + filter_data = { + "task_types": task_type, + "task_names": task_name, + "hosts": host_name + } + profile = filter_profiles(extra_folders_profiles, filter_data) + if profile is None: + return + + for subfolder in profile["folders"]: + # Make sure backslashes are converted to forwards slashes + # and does not start with slash + subfolder = subfolder.replace("\\", "/").lstrip("/") + # Skip empty strings + if not subfolder: + continue + + fullpath = os.path.join(workdir, subfolder) + if not os.path.exists(fullpath): + os.makedirs(fullpath) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index a5d5b14bb6..b4f5e422bc 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -10,10 +10,7 @@ from openpype.host import IWorkfileHost from openpype.client import get_asset_by_id from openpype.tools.utils import PlaceholderLineEdit from openpype.tools.utils.delegates import PrettyTimeDelegate -from openpype.lib import ( - emit_event, - create_workdir_extra_folders, -) +from openpype.lib import emit_event from openpype.pipeline import ( registered_host, legacy_io, @@ -23,7 +20,10 @@ from openpype.pipeline.context_tools import ( compute_session_changes, change_current_context ) -from openpype.pipeline.workfile import get_workfile_template_key +from openpype.pipeline.workfile import ( + get_workfile_template_key, + create_workdir_extra_folders, +) from .model import ( WorkAreaFilesModel, From d3a9610c5a92f17bd319843eab4a99f6d68acce4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 16:09:15 +0200 Subject: [PATCH 192/274] moved helper function to pipeline.project_folders --- openpype/lib/path_tools.py | 64 +++++++++++----------------- openpype/pipeline/project_folders.py | 49 +++++++++++++++++++++ 2 files changed, 73 insertions(+), 40 deletions(-) create mode 100644 openpype/pipeline/project_folders.py diff --git a/openpype/lib/path_tools.py b/openpype/lib/path_tools.py index 671591bca4..736eb0effc 100644 --- a/openpype/lib/path_tools.py +++ b/openpype/lib/path_tools.py @@ -277,58 +277,42 @@ def get_last_version_from_path(path_dir, filter): return None +@deprecated("openpype.pipeline.project_folders.concatenate_splitted_paths") def concatenate_splitted_paths(split_paths, anatomy): - pattern_array = re.compile(r"\[.*\]") - output = [] - for path_items in split_paths: - clean_items = [] - if isinstance(path_items, str): - path_items = [path_items] + """ + Deprecated: + Function will be removed after release version 3.16.* + """ - for path_item in path_items: - if not re.match(r"{.+}", path_item): - path_item = re.sub(pattern_array, "", path_item) - clean_items.append(path_item) + from openpype.pipeline.project_folders import concatenate_splitted_paths - # backward compatibility - if "__project_root__" in path_items: - for root, root_path in anatomy.roots.items(): - if not os.path.exists(str(root_path)): - log.debug("Root {} path path {} not exist on \ - computer!".format(root, root_path)) - continue - clean_items = ["{{root[{}]}}".format(root), - r"{project[name]}"] + clean_items[1:] - output.append(os.path.normpath(os.path.sep.join(clean_items))) - continue - - output.append(os.path.normpath(os.path.sep.join(clean_items))) - - return output + return concatenate_splitted_paths(split_paths, anatomy) +@deprecated def get_format_data(anatomy): - project_doc = get_project(anatomy.project_name, fields=["data.code"]) - project_code = project_doc["data"]["code"] + """ + Deprecated: + Function will be removed after release version 3.16.* + """ - return { - "root": anatomy.roots, - "project": { - "name": anatomy.project_name, - "code": project_code - }, - } + from openpype.pipeline.template_data import get_project_template_data + + data = get_project_template_data(project_name=anatomy.project_name) + data["root"] = anatomy.roots + return data +@deprecated("openpype.pipeline.project_folders.fill_paths") def fill_paths(path_list, anatomy): - format_data = get_format_data(anatomy) - filled_paths = [] + """ + Deprecated: + Function will be removed after release version 3.16.* + """ - for path in path_list: - new_path = path.format(**format_data) - filled_paths.append(new_path) + from openpype.pipeline.project_folders import fill_paths - return filled_paths + return fill_paths(path_list, anatomy) def create_project_folders(basic_paths, project_name): diff --git a/openpype/pipeline/project_folders.py b/openpype/pipeline/project_folders.py new file mode 100644 index 0000000000..256c4e73d8 --- /dev/null +++ b/openpype/pipeline/project_folders.py @@ -0,0 +1,49 @@ +import os +import re + +from openpype.lib import Logger + +from .template_data import get_project_template_data + + +def concatenate_splitted_paths(split_paths, anatomy): + log = Logger.get_logger("concatenate_splitted_paths") + pattern_array = re.compile(r"\[.*\]") + output = [] + for path_items in split_paths: + clean_items = [] + if isinstance(path_items, str): + path_items = [path_items] + + for path_item in path_items: + if not re.match(r"{.+}", path_item): + path_item = re.sub(pattern_array, "", path_item) + clean_items.append(path_item) + + # backward compatibility + if "__project_root__" in path_items: + for root, root_path in anatomy.roots.items(): + if not os.path.exists(str(root_path)): + log.debug("Root {} path path {} not exist on \ + computer!".format(root, root_path)) + continue + clean_items = ["{{root[{}]}}".format(root), + r"{project[name]}"] + clean_items[1:] + output.append(os.path.normpath(os.path.sep.join(clean_items))) + continue + + output.append(os.path.normpath(os.path.sep.join(clean_items))) + + return output + + +def fill_paths(path_list, anatomy): + format_data = get_project_template_data(project_name=anatomy.project_name) + format_data["root"] = anatomy.roots + filled_paths = [] + + for path in path_list: + new_path = path.format(**format_data) + filled_paths.append(new_path) + + return filled_paths From c7ffda124e36813d00757d5096475bcb902c60f3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 16:10:54 +0200 Subject: [PATCH 193/274] moved 'create_project_folders' and 'get_project_basic_paths' --- openpype/lib/path_tools.py | 65 ++++++------------- .../action_create_project_structure.py | 7 +- openpype/pipeline/project_folders.py | 56 ++++++++++++++++ .../project_manager/project_manager/window.py | 24 +++---- 4 files changed, 88 insertions(+), 64 deletions(-) diff --git a/openpype/lib/path_tools.py b/openpype/lib/path_tools.py index 736eb0effc..5800498b07 100644 --- a/openpype/lib/path_tools.py +++ b/openpype/lib/path_tools.py @@ -1,21 +1,17 @@ import os import re -import abc -import json import logging -import six import platform +import functools +import warnings import clique -from openpype.client import get_project -from openpype.settings import get_project_settings - -from .profiles_filtering import filter_profiles - log = logging.getLogger(__name__) + + class PathToolsDeprecatedWarning(DeprecationWarning): pass @@ -315,51 +311,28 @@ def fill_paths(path_list, anatomy): return fill_paths(path_list, anatomy) +@deprecated("openpype.pipeline.project_folders.create_project_folders") def create_project_folders(basic_paths, project_name): - from openpype.pipeline import Anatomy - anatomy = Anatomy(project_name) + """ + Deprecated: + Function will be removed after release version 3.16.* + """ - concat_paths = concatenate_splitted_paths(basic_paths, anatomy) - filled_paths = fill_paths(concat_paths, anatomy) + from openpype.pipeline.project_folders import create_project_folders - # Create folders - for path in filled_paths: - if os.path.exists(path): - log.debug("Folder already exists: {}".format(path)) - else: - log.debug("Creating folder: {}".format(path)) - os.makedirs(path) - - -def _list_path_items(folder_structure): - output = [] - for key, value in folder_structure.items(): - if not value: - output.append(key) - else: - paths = _list_path_items(value) - for path in paths: - if not isinstance(path, (list, tuple)): - path = [path] - - item = [key] - item.extend(path) - output.append(item) - - return output + return create_project_folders(project_name, basic_paths) +@deprecated("openpype.pipeline.project_folders.get_project_basic_paths") def get_project_basic_paths(project_name): - project_settings = get_project_settings(project_name) - folder_structure = ( - project_settings["global"]["project_folder_structure"] - ) - if not folder_structure: - return [] + """ + Deprecated: + Function will be removed after release version 3.16.* + """ - if isinstance(folder_structure, str): - folder_structure = json.loads(folder_structure) - return _list_path_items(folder_structure) + from openpype.pipeline.project_folders import get_project_basic_paths + + return get_project_basic_paths(project_name) @deprecated("openpype.pipeline.workfile.create_workdir_extra_folders") diff --git a/openpype/modules/ftrack/event_handlers_user/action_create_project_structure.py b/openpype/modules/ftrack/event_handlers_user/action_create_project_structure.py index df914de854..7c896570b1 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_create_project_structure.py +++ b/openpype/modules/ftrack/event_handlers_user/action_create_project_structure.py @@ -1,7 +1,10 @@ import re +from openpype.pipeline.project_folders import ( + get_project_basic_paths, + create_project_folders, +) from openpype_modules.ftrack.lib import BaseAction, statics_icon -from openpype.api import get_project_basic_paths, create_project_folders class CreateProjectFolders(BaseAction): @@ -81,7 +84,7 @@ class CreateProjectFolders(BaseAction): } # Invoking OpenPype API to create the project folders - create_project_folders(basic_paths, project_name) + create_project_folders(project_name, basic_paths) self.create_ftrack_entities(basic_paths, project_entity) self.trigger_event( diff --git a/openpype/pipeline/project_folders.py b/openpype/pipeline/project_folders.py index 256c4e73d8..811b9aa648 100644 --- a/openpype/pipeline/project_folders.py +++ b/openpype/pipeline/project_folders.py @@ -1,8 +1,13 @@ import os import re +import json +import six + +from openpype.settings import get_project_settings from openpype.lib import Logger +from .anatomy import Anatomy from .template_data import get_project_template_data @@ -47,3 +52,54 @@ def fill_paths(path_list, anatomy): filled_paths.append(new_path) return filled_paths + + +def create_project_folders(project_name, basic_paths=None): + log = Logger.get_logger("create_project_folders") + anatomy = Anatomy(project_name) + if basic_paths is None: + basic_paths = get_project_basic_paths(project_name) + + concat_paths = concatenate_splitted_paths(basic_paths, anatomy) + filled_paths = fill_paths(concat_paths, anatomy) + + # Create folders + for path in filled_paths: + if os.path.exists(path): + log.debug("Folder already exists: {}".format(path)) + else: + log.debug("Creating folder: {}".format(path)) + os.makedirs(path) + return filled_paths + + +def _list_path_items(folder_structure): + output = [] + for key, value in folder_structure.items(): + if not value: + output.append(key) + continue + + paths = _list_path_items(value) + for path in paths: + if not isinstance(path, (list, tuple)): + path = [path] + + item = [key] + item.extend(path) + output.append(item) + + return output + + +def get_project_basic_paths(project_name): + project_settings = get_project_settings(project_name) + folder_structure = ( + project_settings["global"]["project_folder_structure"] + ) + if not folder_structure: + return [] + + if isinstance(folder_structure, six.string_types): + folder_structure = json.loads(folder_structure) + return _list_path_items(folder_structure) diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index c6ae0ff352..3b2dea8ca3 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -1,5 +1,12 @@ from Qt import QtWidgets, QtCore, QtGui +from openpype import resources +from openpype.style import load_stylesheet +from openpype.widgets import PasswordDialog +from openpype.lib import is_admin_password_required, Logger +from openpype.pipeline import AvalonMongoDB +from openpype.pipeline.project_folders import create_project_folders + from . import ( ProjectModel, ProjectProxyFilter, @@ -13,17 +20,6 @@ from . import ( ) from .widgets import ConfirmProjectDeletion from .style import ResourceCache -from openpype.style import load_stylesheet -from openpype.lib import is_admin_password_required -from openpype.widgets import PasswordDialog -from openpype.pipeline import AvalonMongoDB - -from openpype import resources -from openpype.api import ( - get_project_basic_paths, - create_project_folders, - Logger -) class ProjectManagerWindow(QtWidgets.QWidget): @@ -259,12 +255,8 @@ class ProjectManagerWindow(QtWidgets.QWidget): qm.Yes | qm.No) if ans == qm.Yes: try: - # Get paths based on presets - basic_paths = get_project_basic_paths(project_name) - if not basic_paths: - pass # Invoking OpenPype API to create the project folders - create_project_folders(basic_paths, project_name) + create_project_folders(project_name) except Exception as exc: self.log.warning( "Cannot create starting folders: {}".format(exc), From 27e74ee0095d33c22ee2059f12efb508662d066f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 16:15:28 +0200 Subject: [PATCH 194/274] skip folders creation if are not set --- openpype/pipeline/project_folders.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/project_folders.py b/openpype/pipeline/project_folders.py index 811b9aa648..1bcba5c320 100644 --- a/openpype/pipeline/project_folders.py +++ b/openpype/pipeline/project_folders.py @@ -60,6 +60,9 @@ def create_project_folders(project_name, basic_paths=None): if basic_paths is None: basic_paths = get_project_basic_paths(project_name) + if not basic_paths: + return + concat_paths = concatenate_splitted_paths(basic_paths, anatomy) filled_paths = fill_paths(concat_paths, anatomy) @@ -70,7 +73,6 @@ def create_project_folders(project_name, basic_paths=None): else: log.debug("Creating folder: {}".format(path)) os.makedirs(path) - return filled_paths def _list_path_items(folder_structure): From 111a0be1a858f7568a169196c0be32e5d9da4a88 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 16:21:23 +0200 Subject: [PATCH 195/274] removed not needed lines --- openpype/lib/path_tools.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/lib/path_tools.py b/openpype/lib/path_tools.py index 5800498b07..0b6d0a3391 100644 --- a/openpype/lib/path_tools.py +++ b/openpype/lib/path_tools.py @@ -10,8 +10,6 @@ import clique log = logging.getLogger(__name__) - - class PathToolsDeprecatedWarning(DeprecationWarning): pass From 137e4098897942a0baa15e01ab6d1950996900ce Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 18:08:16 +0200 Subject: [PATCH 196/274] convert functions to be usable in other files of client --- openpype/client/entities.py | 82 +++++++++++++++++++++++-------------- 1 file changed, 51 insertions(+), 31 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 3d2730a17c..a4bd07838a 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -32,17 +32,37 @@ def _prepare_fields(fields, required_fields=None): return output -def _convert_id(in_id): +def convert_id(in_id): + """Helper function for conversion of id from string to ObjectId. + + Args: + in_id (Union[str, ObjectId, Any]): Entity id that should be converted + to right type for queries. + + Returns: + Union[ObjectId, Any]: Converted ids to ObjectId or in type. + """ + if isinstance(in_id, six.string_types): return ObjectId(in_id) return in_id -def _convert_ids(in_ids): +def convert_ids(in_ids): + """Helper function for conversion of ids from string to ObjectId. + + Args: + in_ids (Iterable[Union[str, ObjectId, Any]]): List of entity ids that + should be converted to right type for queries. + + Returns: + List[ObjectId]: Converted ids to ObjectId. + """ + _output = set() for in_id in in_ids: if in_id is not None: - _output.add(_convert_id(in_id)) + _output.add(convert_id(in_id)) return list(_output) @@ -115,7 +135,7 @@ def get_asset_by_id(project_name, asset_id, fields=None): None: Asset was not found by id. """ - asset_id = _convert_id(asset_id) + asset_id = convert_id(asset_id) if not asset_id: return None @@ -196,7 +216,7 @@ def _get_assets( query_filter = {"type": {"$in": asset_types}} if asset_ids is not None: - asset_ids = _convert_ids(asset_ids) + asset_ids = convert_ids(asset_ids) if not asset_ids: return [] query_filter["_id"] = {"$in": asset_ids} @@ -207,7 +227,7 @@ def _get_assets( query_filter["name"] = {"$in": list(asset_names)} if parent_ids is not None: - parent_ids = _convert_ids(parent_ids) + parent_ids = convert_ids(parent_ids) if not parent_ids: return [] query_filter["data.visualParent"] = {"$in": parent_ids} @@ -307,7 +327,7 @@ def get_asset_ids_with_subsets(project_name, asset_ids=None): "type": "subset" } if asset_ids is not None: - asset_ids = _convert_ids(asset_ids) + asset_ids = convert_ids(asset_ids) if not asset_ids: return [] subset_query["parent"] = {"$in": asset_ids} @@ -347,7 +367,7 @@ def get_subset_by_id(project_name, subset_id, fields=None): Dict: Subset document which can be reduced to specified 'fields'. """ - subset_id = _convert_id(subset_id) + subset_id = convert_id(subset_id) if not subset_id: return None @@ -374,7 +394,7 @@ def get_subset_by_name(project_name, subset_name, asset_id, fields=None): if not subset_name: return None - asset_id = _convert_id(asset_id) + asset_id = convert_id(asset_id) if not asset_id: return None @@ -428,13 +448,13 @@ def get_subsets( query_filter = {"type": {"$in": subset_types}} if asset_ids is not None: - asset_ids = _convert_ids(asset_ids) + asset_ids = convert_ids(asset_ids) if not asset_ids: return [] query_filter["parent"] = {"$in": asset_ids} if subset_ids is not None: - subset_ids = _convert_ids(subset_ids) + subset_ids = convert_ids(subset_ids) if not subset_ids: return [] query_filter["_id"] = {"$in": subset_ids} @@ -449,7 +469,7 @@ def get_subsets( for asset_id, names in names_by_asset_ids.items(): if asset_id and names: or_query.append({ - "parent": _convert_id(asset_id), + "parent": convert_id(asset_id), "name": {"$in": list(names)} }) if not or_query: @@ -510,7 +530,7 @@ def get_version_by_id(project_name, version_id, fields=None): Dict: Version document which can be reduced to specified 'fields'. """ - version_id = _convert_id(version_id) + version_id = convert_id(version_id) if not version_id: return None @@ -537,7 +557,7 @@ def get_version_by_name(project_name, version, subset_id, fields=None): Dict: Version document which can be reduced to specified 'fields'. """ - subset_id = _convert_id(subset_id) + subset_id = convert_id(subset_id) if not subset_id: return None @@ -567,7 +587,7 @@ def version_is_latest(project_name, version_id): bool: True if is latest version from subset else False. """ - version_id = _convert_id(version_id) + version_id = convert_id(version_id) if not version_id: return False version_doc = get_version_by_id( @@ -610,13 +630,13 @@ def _get_versions( query_filter = {"type": {"$in": version_types}} if subset_ids is not None: - subset_ids = _convert_ids(subset_ids) + subset_ids = convert_ids(subset_ids) if not subset_ids: return [] query_filter["parent"] = {"$in": subset_ids} if version_ids is not None: - version_ids = _convert_ids(version_ids) + version_ids = convert_ids(version_ids) if not version_ids: return [] query_filter["_id"] = {"$in": version_ids} @@ -690,7 +710,7 @@ def get_hero_version_by_subset_id(project_name, subset_id, fields=None): Dict: Hero version entity data. """ - subset_id = _convert_id(subset_id) + subset_id = convert_id(subset_id) if not subset_id: return None @@ -720,7 +740,7 @@ def get_hero_version_by_id(project_name, version_id, fields=None): Dict: Hero version entity data. """ - version_id = _convert_id(version_id) + version_id = convert_id(version_id) if not version_id: return None @@ -786,7 +806,7 @@ def get_output_link_versions(project_name, version_id, fields=None): links for passed version. """ - version_id = _convert_id(version_id) + version_id = convert_id(version_id) if not version_id: return [] @@ -812,7 +832,7 @@ def get_last_versions(project_name, subset_ids, fields=None): dict[ObjectId, int]: Key is subset id and value is last version name. """ - subset_ids = _convert_ids(subset_ids) + subset_ids = convert_ids(subset_ids) if not subset_ids: return {} @@ -898,7 +918,7 @@ def get_last_version_by_subset_id(project_name, subset_id, fields=None): Dict: Version document which can be reduced to specified 'fields'. """ - subset_id = _convert_id(subset_id) + subset_id = convert_id(subset_id) if not subset_id: return None @@ -971,7 +991,7 @@ def get_representation_by_id(project_name, representation_id, fields=None): "type": {"$in": repre_types} } if representation_id is not None: - query_filter["_id"] = _convert_id(representation_id) + query_filter["_id"] = convert_id(representation_id) conn = get_project_connection(project_name) @@ -996,7 +1016,7 @@ def get_representation_by_name( to specified 'fields'. """ - version_id = _convert_id(version_id) + version_id = convert_id(version_id) if not version_id or not representation_name: return None repre_types = ["representation", "archived_representations"] @@ -1089,7 +1109,7 @@ def _get_representations( query_filter = {"type": {"$in": repre_types}} if representation_ids is not None: - representation_ids = _convert_ids(representation_ids) + representation_ids = convert_ids(representation_ids) if not representation_ids: return default_output query_filter["_id"] = {"$in": representation_ids} @@ -1100,7 +1120,7 @@ def _get_representations( query_filter["name"] = {"$in": list(representation_names)} if version_ids is not None: - version_ids = _convert_ids(version_ids) + version_ids = convert_ids(version_ids) if not version_ids: return default_output query_filter["parent"] = {"$in": version_ids} @@ -1111,7 +1131,7 @@ def _get_representations( for version_id, names in names_by_version_ids.items(): if version_id and names: or_query.append({ - "parent": _convert_id(version_id), + "parent": convert_id(version_id), "name": {"$in": list(names)} }) if not or_query: @@ -1361,7 +1381,7 @@ def get_thumbnail_id_from_source(project_name, src_type, src_id): if not src_type or not src_id: return None - query_filter = {"_id": _convert_id(src_id)} + query_filter = {"_id": convert_id(src_id)} conn = get_project_connection(project_name) src_doc = conn.find_one(query_filter, {"data.thumbnail_id"}) @@ -1388,7 +1408,7 @@ def get_thumbnails(project_name, thumbnail_ids, fields=None): """ if thumbnail_ids: - thumbnail_ids = _convert_ids(thumbnail_ids) + thumbnail_ids = convert_ids(thumbnail_ids) if not thumbnail_ids: return [] @@ -1416,7 +1436,7 @@ def get_thumbnail(project_name, thumbnail_id, fields=None): if not thumbnail_id: return None - query_filter = {"type": "thumbnail", "_id": _convert_id(thumbnail_id)} + query_filter = {"type": "thumbnail", "_id": convert_id(thumbnail_id)} conn = get_project_connection(project_name) return conn.find_one(query_filter, _prepare_fields(fields)) @@ -1444,7 +1464,7 @@ def get_workfile_info( query_filter = { "type": "workfile", - "parent": _convert_id(asset_id), + "parent": convert_id(asset_id), "task_name": task_name, "filename": filename } From a0b33deda33c1777f9cd2dd62c6dac6a535167fa Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 18:18:33 +0200 Subject: [PATCH 197/274] added functions to get linked ids --- openpype/client/__init__.py | 11 ++ openpype/client/entity_links.py | 232 ++++++++++++++++++++++++++++++++ 2 files changed, 243 insertions(+) create mode 100644 openpype/client/entity_links.py diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index 64a82334d9..d080425e3c 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -45,6 +45,13 @@ from .entities import ( get_workfile_info, ) +from .entity_links import ( + get_linked_asset_ids, + get_linked_assets, + get_linked_representation_ids, +) + + __all__ = ( "OpenPypeMongoConnection", @@ -88,4 +95,8 @@ __all__ = ( "get_thumbnail_id_from_source", "get_workfile_info", + + "get_linked_asset_ids", + "get_linked_assets", + "get_linked_representation_ids", ) diff --git a/openpype/client/entity_links.py b/openpype/client/entity_links.py new file mode 100644 index 0000000000..66214f469c --- /dev/null +++ b/openpype/client/entity_links.py @@ -0,0 +1,232 @@ +from .mongo import get_project_connection +from .entities import ( + get_assets, + get_asset_by_id, + get_representation_by_id, + convert_id, +) + + +def get_linked_asset_ids(project_name, asset_doc=None, asset_id=None): + """Extract linked asset ids from asset document. + + One of asset document or asset id must be passed. + + Note: + Asset links now works only from asset to assets. + + Args: + asset_doc (dict): Asset document from DB. + + Returns: + List[Union[ObjectId, str]]: Asset ids of input links. + """ + + output = [] + if not asset_doc and not asset_id: + return output + + if not asset_doc: + asset_doc = get_asset_by_id( + project_name, asset_id, fields=["data.inputLinks"] + ) + + input_links = asset_doc["data"].get("inputLinks") + if not input_links: + return output + + for item in input_links: + # Backwards compatibility for "_id" key which was replaced with + # "id" + if "_id" in item: + link_id = item["_id"] + else: + link_id = item["id"] + output.append(link_id) + return output + + +def get_linked_assets( + project_name, asset_doc=None, asset_id=None, fields=None +): + """Return linked assets based on passed asset document. + + One of asset document or asset id must be passed. + + Args: + project_name (str): Name of project where to look for queried entities. + asset_doc (Dict[str, Any]): Asset document from database. + asset_id (Union[ObjectId, str]): Asset id. Can be used instead of + asset document. + fields (Iterable[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + List[Dict[str, Any]]: Asset documents of input links for passed + asset doc. + """ + + if not asset_doc: + if not asset_id: + return [] + asset_doc = get_asset_by_id( + project_name, + asset_id, + fields=["data.inputLinks"] + ) + if not asset_doc: + return [] + + link_ids = get_linked_asset_ids(project_name, asset_doc=asset_doc) + if not link_ids: + return [] + + return list(get_assets(project_name, asset_ids=link_ids, fields=fields)) + + +def get_linked_representation_id( + project_name, repre_doc=None, repre_id=None, link_type=None, max_depth=None +): + """Returns list of linked ids of particular type (if provided). + + One of representation document or representation id must be passed. + Note: + Representation links now works only from representation through version + back to representations. + + Args: + project_name (str): Name of project where look for links. + repre_doc (Dict[str, Any]): Representation document. + repre_id (Union[ObjectId, str]): Representation id. + link_type (str): Type of link (e.g. 'reference', ...). + max_depth (int): Limit recursion level. Default: 0 + + Returns: + List[ObjectId] Linked representation ids. + """ + + if repre_doc: + repre_id = repre_doc["_id"] + + if repre_id: + repre_id = convert_id(repre_id) + + if not repre_id and not repre_doc: + return [] + + version_id = None + if repre_doc: + version_id = repre_doc.get("parent") + + if not version_id: + repre_doc = get_representation_by_id( + project_name, repre_id, fields=["parent"] + ) + version_id = repre_doc["parent"] + + if not version_id: + return [] + + if max_depth is None: + max_depth = 0 + + match = { + "_id": version_id, + "type": {"$in": ["version", "hero_version"]} + } + + graph_lookup = { + "from": project_name, + "startWith": "$data.inputLinks.id", + "connectFromField": "data.inputLinks.id", + "connectToField": "_id", + "as": "outputs_recursive", + "depthField": "depth" + } + if max_depth != 0: + # We offset by -1 since 0 basically means no recursion + # but the recursion only happens after the initial lookup + # for outputs. + graph_lookup["maxDepth"] = max_depth - 1 + + query_pipeline = [ + # Match + {"$match": match}, + # Recursive graph lookup for inputs + {"$graphLookup": graph_lookup} + ] + + conn = get_project_connection(project_name) + result = conn.aggregate(query_pipeline) + referenced_version_ids = _process_referenced_pipeline_result( + result, link_type + ) + if not referenced_version_ids: + return [] + + ref_ids = conn.distinct( + "_id", + filter={ + "parent": {"$in": list(referenced_version_ids)}, + "type": "representation" + } + ) + + return list(ref_ids) + + +def _process_referenced_pipeline_result(result, link_type): + """Filters result from pipeline for particular link_type. + + Pipeline cannot use link_type directly in a query. + + Returns: + (list) + """ + + referenced_version_ids = set() + correctly_linked_ids = set() + for item in result: + input_links = item["data"].get("inputLinks") + if not input_links: + continue + + _filter_input_links( + input_links, + link_type, + correctly_linked_ids + ) + + # outputs_recursive in random order, sort by depth + outputs_recursive = item.get("outputs_recursive") + if not outputs_recursive: + continue + + for output in sorted(outputs_recursive, key=lambda o: o["depth"]): + output_links = output["data"].get("inputLinks") + if not output_links: + continue + + # Leaf + if output["_id"] not in correctly_linked_ids: + continue + + _filter_input_links( + output_links, + link_type, + correctly_linked_ids + ) + + referenced_version_ids.add(output["_id"]) + + return referenced_version_ids + + +def _filter_input_links(input_links, link_type, correctly_linked_ids): + for input_link in input_links: + if link_type and input_link["type"] != link_type: + continue + + link_id = input_link.get("id") or input_link.get("_id") + if link_id is not None: + correctly_linked_ids.add(link_id) From 17cfd18d1aa16720292b8f953db77039776ec47d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 18:19:51 +0200 Subject: [PATCH 198/274] use new location of 'get_linked_assets' --- openpype/lib/avalon_context.py | 36 +++++++++---------- .../workfile/abstract_template_loader.py | 6 ++-- openpype/pipeline/workfile/build_workfile.py | 2 +- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 7d56d039d4..140ea887ff 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -236,6 +236,7 @@ def get_system_general_anatomy_data(system_settings=None): return get_general_template_data(system_settings) +@deprecated("openpype.client.get_linked_asset_ids") def get_linked_asset_ids(asset_doc): """Return linked asset ids for `asset_doc` from DB @@ -244,26 +245,20 @@ def get_linked_asset_ids(asset_doc): Returns: (list): MongoDB ids of input links. + + Deprecated: + Function will be removed after release version 3.16.* """ - output = [] - if not asset_doc: - return output - input_links = asset_doc["data"].get("inputLinks") or [] - if input_links: - for item in input_links: - # Backwards compatibility for "_id" key which was replaced with - # "id" - if "_id" in item: - link_id = item["_id"] - else: - link_id = item["id"] - output.append(link_id) + from openpype.client import get_linked_asset_ids + from openpype.pipeline import legacy_io - return output + project_name = legacy_io.active_project() + + return get_linked_asset_ids(project_name, asset_doc=asset_doc) -@with_pipeline_io +@deprecated("openpype.client.get_linked_assets") def get_linked_assets(asset_doc): """Return linked assets for `asset_doc` from DB @@ -272,14 +267,17 @@ def get_linked_assets(asset_doc): Returns: (list) Asset documents of input links for passed asset doc. + + Deprecated: + Function will be removed after release version 3.15.* """ - link_ids = get_linked_asset_ids(asset_doc) - if not link_ids: - return [] + from openpype.pipeline import legacy_io + from openpype.client import get_linked_assets project_name = legacy_io.active_project() - return list(get_assets(project_name, link_ids)) + + return get_linked_assets(project_name, asset_doc=asset_doc) @deprecated("openpype.client.get_last_version_by_subset_name") diff --git a/openpype/pipeline/workfile/abstract_template_loader.py b/openpype/pipeline/workfile/abstract_template_loader.py index 05a98a1ddc..82a0fd33e9 100644 --- a/openpype/pipeline/workfile/abstract_template_loader.py +++ b/openpype/pipeline/workfile/abstract_template_loader.py @@ -5,13 +5,15 @@ import six import logging from functools import reduce -from openpype.client import get_asset_by_name +from openpype.client import ( + get_asset_by_name, + get_linked_assets, +) from openpype.settings import get_project_settings from openpype.lib import ( StringTemplate, Logger, filter_profiles, - get_linked_assets, ) from openpype.pipeline import legacy_io, Anatomy from openpype.pipeline.load import ( diff --git a/openpype/pipeline/workfile/build_workfile.py b/openpype/pipeline/workfile/build_workfile.py index bb6fcb4189..0b8a444436 100644 --- a/openpype/pipeline/workfile/build_workfile.py +++ b/openpype/pipeline/workfile/build_workfile.py @@ -8,10 +8,10 @@ from openpype.client import ( get_subsets, get_last_versions, get_representations, + get_linked_assets, ) from openpype.settings import get_project_settings from openpype.lib import ( - get_linked_assets, filter_profiles, Logger, ) From c8ef54e9ce840c7ad09c3ca2f52d28a8c9f1c622 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 18:22:04 +0200 Subject: [PATCH 199/274] use new source of 'get_linked_ids_for_representations' --- openpype/lib/avalon_context.py | 116 +++++------------------------- openpype/plugins/load/add_site.py | 10 +-- 2 files changed, 25 insertions(+), 101 deletions(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 140ea887ff..1fff45f262 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -1083,9 +1083,10 @@ def get_last_workfile( ) -@with_pipeline_io -def get_linked_ids_for_representations(project_name, repre_ids, dbcon=None, - link_type=None, max_depth=0): +@deprecated("openpype.client.get_linked_ids_for_representations") +def get_linked_ids_for_representations( + project_name, repre_ids, dbcon=None, link_type=None, max_depth=0 +): """Returns list of linked ids of particular type (if provided). Goes from representations to version, back to representations @@ -1096,104 +1097,25 @@ def get_linked_ids_for_representations(project_name, repre_ids, dbcon=None, with Session. link_type (str): ['reference', '..] max_depth (int): limit how many levels of recursion + Returns: (list) of ObjectId - linked representations + + Deprecated: + Function will be removed after release version 3.16.* """ - # Create new dbcon if not passed and use passed project name - if not dbcon: - from openpype.pipeline import AvalonMongoDB - dbcon = AvalonMongoDB() - dbcon.Session["AVALON_PROJECT"] = project_name - # Validate that passed dbcon has same project - elif dbcon.Session["AVALON_PROJECT"] != project_name: - raise ValueError("Passed connection does not have right project") + + from openpype.client import get_linked_representation_ids if not isinstance(repre_ids, list): repre_ids = [repre_ids] - version_ids = dbcon.distinct("parent", { - "_id": {"$in": repre_ids}, - "type": "representation" - }) - - match = { - "_id": {"$in": version_ids}, - "type": "version" - } - - graph_lookup = { - "from": project_name, - "startWith": "$data.inputLinks.id", - "connectFromField": "data.inputLinks.id", - "connectToField": "_id", - "as": "outputs_recursive", - "depthField": "depth" - } - if max_depth != 0: - # We offset by -1 since 0 basically means no recursion - # but the recursion only happens after the initial lookup - # for outputs. - graph_lookup["maxDepth"] = max_depth - 1 - - pipeline_ = [ - # Match - {"$match": match}, - # Recursive graph lookup for inputs - {"$graphLookup": graph_lookup} - ] - - result = dbcon.aggregate(pipeline_) - referenced_version_ids = _process_referenced_pipeline_result(result, - link_type) - - ref_ids = dbcon.distinct( - "_id", - filter={ - "parent": {"$in": list(referenced_version_ids)}, - "type": "representation" - } - ) - - return list(ref_ids) - - -def _process_referenced_pipeline_result(result, link_type): - """Filters result from pipeline for particular link_type. - - Pipeline cannot use link_type directly in a query. - Returns: - (list) - """ - referenced_version_ids = set() - correctly_linked_ids = set() - for item in result: - input_links = item["data"].get("inputLinks", []) - correctly_linked_ids = _filter_input_links(input_links, - link_type, - correctly_linked_ids) - - # outputs_recursive in random order, sort by depth - outputs_recursive = sorted(item.get("outputs_recursive", []), - key=lambda d: d["depth"]) - - for output in outputs_recursive: - if output["_id"] not in correctly_linked_ids: # leaf - continue - - correctly_linked_ids = _filter_input_links( - output["data"].get("inputLinks", []), - link_type, - correctly_linked_ids) - - referenced_version_ids.add(output["_id"]) - - return referenced_version_ids - - -def _filter_input_links(input_links, link_type, correctly_linked_ids): - for input_link in input_links: - if not link_type or input_link["type"] == link_type: - correctly_linked_ids.add(input_link.get("id") or - input_link.get("_id")) # legacy - - return correctly_linked_ids + output = [] + for repre_id in repre_ids: + output.extend(get_linked_representation_ids( + project_name, + repre_id=repre_id, + link_type=link_type, + max_depth=max_depth + )) + return output diff --git a/openpype/plugins/load/add_site.py b/openpype/plugins/load/add_site.py index 55fda55d17..388a871e9d 100644 --- a/openpype/plugins/load/add_site.py +++ b/openpype/plugins/load/add_site.py @@ -1,6 +1,6 @@ +from openpype.client import get_linked_ids_for_representations from openpype.modules import ModulesManager from openpype.pipeline import load -from openpype.lib.avalon_context import get_linked_ids_for_representations from openpype.modules.sync_server.utils import SiteAlreadyPresentError @@ -45,9 +45,11 @@ class AddSyncSite(load.LoaderPlugin): force=True) if family == "workfile": - links = get_linked_ids_for_representations(project_name, - [repre_id], - link_type="reference") + links = get_linked_ids_for_representations( + project_name, + repre_id=repre_id, + link_type="reference" + ) for link_repre_id in links: try: self.sync_server.add_site(project_name, link_repre_id, From 0399c311db079c74c74fb652cbadca48c83ffcbe Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Sep 2022 10:42:17 +0200 Subject: [PATCH 200/274] copied 'Extractor' to publish pipeline --- openpype/pipeline/publish/__init__.py | 4 ++++ openpype/pipeline/publish/publish_plugins.py | 25 +++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/publish/__init__.py b/openpype/pipeline/publish/__init__.py index 8ba17b2516..8ccfc32a6b 100644 --- a/openpype/pipeline/publish/__init__.py +++ b/openpype/pipeline/publish/__init__.py @@ -17,6 +17,8 @@ from .publish_plugins import ( RepairAction, RepairContextAction, + + Extractor, ) from .lib import ( @@ -58,6 +60,8 @@ __all__ = ( "RepairAction", "RepairContextAction", + "Extractor", + "DiscoverResult", "publish_plugins_discover", "load_help_content_from_plugin", diff --git a/openpype/pipeline/publish/publish_plugins.py b/openpype/pipeline/publish/publish_plugins.py index 5bb6b5aaff..6e2be1ce2c 100644 --- a/openpype/pipeline/publish/publish_plugins.py +++ b/openpype/pipeline/publish/publish_plugins.py @@ -8,7 +8,8 @@ from openpype.lib import BoolDef from .lib import ( load_help_content_from_plugin, get_errored_instances_from_context, - get_errored_plugins_from_context + get_errored_plugins_from_context, + get_instance_staging_dir, ) @@ -241,3 +242,25 @@ class RepairContextAction(pyblish.api.Action): if plugin in errored_plugins: self.log.info("Attempting fix ...") plugin.repair(context) + + +class Extractor(pyblish.api.InstancePlugin): + """Extractor base class. + + The extractor base class implements a "staging_dir" function used to + generate a temporary directory for an instance to extract to. + + This temporary directory is generated through `tempfile.mkdtemp()` + + """ + + order = 2.0 + + def staging_dir(self, instance): + """Provide a temporary directory in which to store extracted files + + Upon calling this method the staging directory is stored inside + the instance.data['stagingDir'] + """ + + return get_instance_staging_dir(instance) From 20d9345c48a2bfe7f229dbfd83a18ccb2c613be5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Sep 2022 11:38:29 +0200 Subject: [PATCH 201/274] removed comment --- openpype/plugin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/plugin.py b/openpype/plugin.py index d3605fcb1e..7e906b4451 100644 --- a/openpype/plugin.py +++ b/openpype/plugin.py @@ -85,7 +85,6 @@ class InstancePlugin(pyblish.api.InstancePlugin): super(InstancePlugin, self).__init__(*args, **kwargs) -# NOTE: This class is used on so many places I gave up moving it class Extractor(pyblish.api.InstancePlugin): """Extractor base class. From ec782caa00935af6841110c5b153b6e71ff46171 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Sep 2022 11:40:33 +0200 Subject: [PATCH 202/274] use new Extractor in global plugins --- openpype/plugins/publish/extract_burnin.py | 12 ++++++------ openpype/plugins/publish/extract_otio_file.py | 5 +++-- .../plugins/publish/extract_otio_review.py | 13 +++++++++---- .../publish/extract_otio_trimming_video.py | 18 ++++++++++++------ .../plugins/publish/extract_review_slate.py | 17 ++++++++++------- .../publish/extract_trim_video_audio.py | 10 ++++++---- 6 files changed, 46 insertions(+), 29 deletions(-) diff --git a/openpype/plugins/publish/extract_burnin.py b/openpype/plugins/publish/extract_burnin.py index 88093fb92f..8ddee162e3 100644 --- a/openpype/plugins/publish/extract_burnin.py +++ b/openpype/plugins/publish/extract_burnin.py @@ -8,10 +8,10 @@ import shutil import clique import six -import pyblish +import pyblish.api -import openpype -import openpype.api +from openpype import resources, PACKAGE_DIR +from openpype.pipeline import publish from openpype.lib import ( run_openpype_process, @@ -23,7 +23,7 @@ from openpype.lib import ( ) -class ExtractBurnin(openpype.api.Extractor): +class ExtractBurnin(publish.Extractor): """ Extractor to create video with pre-defined burnins from existing extracted video representation. @@ -400,7 +400,7 @@ class ExtractBurnin(openpype.api.Extractor): # Use OpenPype default font if not font_filepath: - font_filepath = openpype.api.resources.get_liberation_font_path() + font_filepath = resources.get_liberation_font_path() burnin_options["font"] = font_filepath @@ -981,7 +981,7 @@ class ExtractBurnin(openpype.api.Extractor): """Return path to python script for burnin processing.""" scriptpath = os.path.normpath( os.path.join( - openpype.PACKAGE_DIR, + PACKAGE_DIR, "scripts", "otio_burnin.py" ) diff --git a/openpype/plugins/publish/extract_otio_file.py b/openpype/plugins/publish/extract_otio_file.py index 4d310ce109..c692205d81 100644 --- a/openpype/plugins/publish/extract_otio_file.py +++ b/openpype/plugins/publish/extract_otio_file.py @@ -1,10 +1,11 @@ import os import pyblish.api -import openpype.api import opentimelineio as otio +from openpype.pipeline import publish -class ExtractOTIOFile(openpype.api.Extractor): + +class ExtractOTIOFile(publish.Extractor): """ Extractor export OTIO file """ diff --git a/openpype/plugins/publish/extract_otio_review.py b/openpype/plugins/publish/extract_otio_review.py index 2ce5323468..169ff9e136 100644 --- a/openpype/plugins/publish/extract_otio_review.py +++ b/openpype/plugins/publish/extract_otio_review.py @@ -18,7 +18,12 @@ import os import clique import opentimelineio as otio from pyblish import api -import openpype + +from openpype.lib import ( + get_ffmpeg_tool_path, + run_subprocess, +) +from openpype.pipeline import publish from openpype.pipeline.editorial import ( otio_range_to_frame_range, trim_media_range, @@ -28,7 +33,7 @@ from openpype.pipeline.editorial import ( ) -class ExtractOTIOReview(openpype.api.Extractor): +class ExtractOTIOReview(publish.Extractor): """ Extract OTIO timeline into one concuted image sequence file. @@ -334,7 +339,7 @@ class ExtractOTIOReview(openpype.api.Extractor): otio.time.TimeRange: trimmed available range """ # get rendering app path - ffmpeg_path = openpype.lib.get_ffmpeg_tool_path("ffmpeg") + ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") # create path and frame start to destination output_path, out_frame_start = self._get_ffmpeg_output() @@ -397,7 +402,7 @@ class ExtractOTIOReview(openpype.api.Extractor): ]) # execute self.log.debug("Executing: {}".format(" ".join(command))) - output = openpype.api.run_subprocess( + output = run_subprocess( command, logger=self.log ) self.log.debug("Output: {}".format(output)) diff --git a/openpype/plugins/publish/extract_otio_trimming_video.py b/openpype/plugins/publish/extract_otio_trimming_video.py index 19625fa568..70726338aa 100644 --- a/openpype/plugins/publish/extract_otio_trimming_video.py +++ b/openpype/plugins/publish/extract_otio_trimming_video.py @@ -6,18 +6,24 @@ Requires: """ import os -from pyblish import api -import openpype from copy import deepcopy + +import pyblish.api + +from openpype.lib import ( + get_ffmpeg_tool_path, + run_subprocess, +) +from openpype.pipeline import publish from openpype.pipeline.editorial import frames_to_seconds -class ExtractOTIOTrimmingVideo(openpype.api.Extractor): +class ExtractOTIOTrimmingVideo(publish.Extractor): """ Trimming video file longer then required lenght """ - order = api.ExtractorOrder + order = pyblish.api.ExtractorOrder label = "Extract OTIO trim longer video" families = ["trim"] hosts = ["resolve", "hiero", "flame"] @@ -70,7 +76,7 @@ class ExtractOTIOTrimmingVideo(openpype.api.Extractor): """ # get rendering app path - ffmpeg_path = openpype.lib.get_ffmpeg_tool_path("ffmpeg") + ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") # create path to destination output_path = self._get_ffmpeg_output(input_file_path) @@ -96,7 +102,7 @@ class ExtractOTIOTrimmingVideo(openpype.api.Extractor): # execute self.log.debug("Executing: {}".format(" ".join(command))) - output = openpype.api.run_subprocess( + output = run_subprocess( command, logger=self.log ) self.log.debug("Output: {}".format(output)) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 69043ee261..239d89538c 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -1,19 +1,22 @@ import os -from pprint import pformat import re -import openpype.api -import pyblish +from pprint import pformat + +import pyblish.api + from openpype.lib import ( path_to_subprocess_arg, + run_subprocess, get_ffmpeg_tool_path, get_ffprobe_data, get_ffprobe_streams, get_ffmpeg_codec_args, get_ffmpeg_format_args, ) +from openpype.pipeline import publish -class ExtractReviewSlate(openpype.api.Extractor): +class ExtractReviewSlate(publish.Extractor): """ Will add slate frame at the start of the video files """ @@ -267,7 +270,7 @@ class ExtractReviewSlate(openpype.api.Extractor): self.log.debug( "Slate Executing: {}".format(slate_subprocess_cmd) ) - openpype.api.run_subprocess( + run_subprocess( slate_subprocess_cmd, shell=True, logger=self.log ) @@ -348,7 +351,7 @@ class ExtractReviewSlate(openpype.api.Extractor): "Executing concat filter: {}".format (" ".join(concat_args)) ) - openpype.api.run_subprocess( + run_subprocess( concat_args, logger=self.log ) @@ -533,7 +536,7 @@ class ExtractReviewSlate(openpype.api.Extractor): self.log.debug("Silent Slate Executing: {}".format( " ".join(slate_silent_args) )) - openpype.api.run_subprocess( + run_subprocess( slate_silent_args, logger=self.log ) diff --git a/openpype/plugins/publish/extract_trim_video_audio.py b/openpype/plugins/publish/extract_trim_video_audio.py index 06817c4b5a..b951136391 100644 --- a/openpype/plugins/publish/extract_trim_video_audio.py +++ b/openpype/plugins/publish/extract_trim_video_audio.py @@ -1,14 +1,16 @@ import os +from pprint import pformat + import pyblish.api -import openpype.api from openpype.lib import ( get_ffmpeg_tool_path, + run_subprocess, ) -from pprint import pformat +from openpype.pipeline import publish -class ExtractTrimVideoAudio(openpype.api.Extractor): +class ExtractTrimVideoAudio(publish.Extractor): """Trim with ffmpeg "mov" and "wav" files.""" # must be before `ExtractThumbnailSP` @@ -98,7 +100,7 @@ class ExtractTrimVideoAudio(openpype.api.Extractor): joined_args = " ".join(ffmpeg_args) self.log.info(f"Processing: {joined_args}") - openpype.api.run_subprocess( + run_subprocess( ffmpeg_args, logger=self.log ) From e4085f768805e66c1ca58766101891922c0e963f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Sep 2022 11:42:20 +0200 Subject: [PATCH 203/274] fix 'path_to_subprocess_arg' usage --- openpype/plugins/publish/extract_review_slate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 239d89538c..fca3d96ca6 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -161,7 +161,7 @@ class ExtractReviewSlate(publish.Extractor): input_args.extend([ "-loop", "1", - "-i", openpype.lib.path_to_subprocess_arg(slate_path), + "-i", path_to_subprocess_arg(slate_path), "-r", str(input_frame_rate), "-frames:v", "1", ]) From e5c662d941aa1e09716de2b1ce8bdaf56f8d0ea5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Sep 2022 11:59:48 +0200 Subject: [PATCH 204/274] use Extractor from openpype.pipeline --- .../plugins/publish/extract_local_render.py | 14 +++++++++----- .../plugins/publish/extract_save_scene.py | 4 ++-- .../plugins/publish/remove_publish_highlight.py | 6 +++--- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py b/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py index 7323a0b125..dc65cee61d 100644 --- a/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py +++ b/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py @@ -2,14 +2,18 @@ import os import sys import six -import openpype.api +from openpype.lib import ( + get_ffmpeg_tool_path, + run_subprocess, +) +from openpype.pipeline import publish from openpype.hosts.aftereffects.api import get_stub -class ExtractLocalRender(openpype.api.Extractor): +class ExtractLocalRender(publish.Extractor): """Render RenderQueue locally.""" - order = openpype.api.Extractor.order - 0.47 + order = publish.Extractor.order - 0.47 label = "Extract Local Render" hosts = ["aftereffects"] families = ["renderLocal", "render.local"] @@ -53,7 +57,7 @@ class ExtractLocalRender(openpype.api.Extractor): instance.data["representations"] = [repre_data] - ffmpeg_path = openpype.lib.get_ffmpeg_tool_path("ffmpeg") + ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") # Generate thumbnail. thumbnail_path = os.path.join(staging_dir, "thumbnail.jpg") @@ -66,7 +70,7 @@ class ExtractLocalRender(openpype.api.Extractor): ] self.log.debug("Thumbnail args:: {}".format(args)) try: - output = openpype.lib.run_subprocess(args) + output = run_subprocess(args) except TypeError: self.log.warning("Error in creating thumbnail") six.reraise(*sys.exc_info()) diff --git a/openpype/hosts/aftereffects/plugins/publish/extract_save_scene.py b/openpype/hosts/aftereffects/plugins/publish/extract_save_scene.py index eb2977309f..343838eb49 100644 --- a/openpype/hosts/aftereffects/plugins/publish/extract_save_scene.py +++ b/openpype/hosts/aftereffects/plugins/publish/extract_save_scene.py @@ -1,13 +1,13 @@ import pyblish.api -import openpype.api +from openpype.pipeline import publish from openpype.hosts.aftereffects.api import get_stub class ExtractSaveScene(pyblish.api.ContextPlugin): """Save scene before extraction.""" - order = openpype.api.Extractor.order - 0.48 + order = publish.Extractor.order - 0.48 label = "Extract Save Scene" hosts = ["aftereffects"] diff --git a/openpype/hosts/aftereffects/plugins/publish/remove_publish_highlight.py b/openpype/hosts/aftereffects/plugins/publish/remove_publish_highlight.py index 5f3fcc3089..370f916f04 100644 --- a/openpype/hosts/aftereffects/plugins/publish/remove_publish_highlight.py +++ b/openpype/hosts/aftereffects/plugins/publish/remove_publish_highlight.py @@ -1,8 +1,8 @@ -import openpype.api +from openpype.pipeline import publish from openpype.hosts.aftereffects.api import get_stub -class RemovePublishHighlight(openpype.api.Extractor): +class RemovePublishHighlight(publish.Extractor): """Clean utf characters which are not working in DL Published compositions are marked with unicode icon which causes @@ -10,7 +10,7 @@ class RemovePublishHighlight(openpype.api.Extractor): rendering, add it later back to avoid confusion. """ - order = openpype.api.Extractor.order - 0.49 # just before save + order = publish.Extractor.order - 0.49 # just before save label = "Clean render comp" hosts = ["aftereffects"] families = ["render.farm"] From cb8cccc9e8fdeba54d2d32b1bbf947ac40a4b92a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Sep 2022 12:01:36 +0200 Subject: [PATCH 205/274] Use new import source of Extractor --- openpype/hosts/blender/plugins/publish/extract_abc.py | 4 ++-- openpype/hosts/blender/plugins/publish/extract_blend.py | 4 ++-- .../hosts/blender/plugins/publish/extract_blend_animation.py | 4 ++-- openpype/hosts/blender/plugins/publish/extract_camera.py | 4 ++-- openpype/hosts/blender/plugins/publish/extract_fbx.py | 4 ++-- .../hosts/blender/plugins/publish/extract_fbx_animation.py | 4 ++-- openpype/hosts/blender/plugins/publish/extract_layout.py | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/extract_abc.py b/openpype/hosts/blender/plugins/publish/extract_abc.py index a26a92f7e4..1cab9d225b 100644 --- a/openpype/hosts/blender/plugins/publish/extract_abc.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc.py @@ -2,12 +2,12 @@ import os import bpy -from openpype import api +from openpype.pipeline import publish from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY -class ExtractABC(api.Extractor): +class ExtractABC(publish.Extractor): """Extract as ABC.""" label = "Extract ABC" diff --git a/openpype/hosts/blender/plugins/publish/extract_blend.py b/openpype/hosts/blender/plugins/publish/extract_blend.py index 9add633f05..6a001b6f65 100644 --- a/openpype/hosts/blender/plugins/publish/extract_blend.py +++ b/openpype/hosts/blender/plugins/publish/extract_blend.py @@ -2,10 +2,10 @@ import os import bpy -import openpype.api +from openpype.pipeline import publish -class ExtractBlend(openpype.api.Extractor): +class ExtractBlend(publish.Extractor): """Extract a blend file.""" label = "Extract Blend" diff --git a/openpype/hosts/blender/plugins/publish/extract_blend_animation.py b/openpype/hosts/blender/plugins/publish/extract_blend_animation.py index 4917223331..477411b73d 100644 --- a/openpype/hosts/blender/plugins/publish/extract_blend_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_blend_animation.py @@ -2,10 +2,10 @@ import os import bpy -import openpype.api +from openpype.pipeline import publish -class ExtractBlendAnimation(openpype.api.Extractor): +class ExtractBlendAnimation(publish.Extractor): """Extract a blend file.""" label = "Extract Blend" diff --git a/openpype/hosts/blender/plugins/publish/extract_camera.py b/openpype/hosts/blender/plugins/publish/extract_camera.py index b2c7611b58..9fd181825c 100644 --- a/openpype/hosts/blender/plugins/publish/extract_camera.py +++ b/openpype/hosts/blender/plugins/publish/extract_camera.py @@ -2,11 +2,11 @@ import os import bpy -from openpype import api +from openpype.pipeline import publish from openpype.hosts.blender.api import plugin -class ExtractCamera(api.Extractor): +class ExtractCamera(publish.Extractor): """Extract as the camera as FBX.""" label = "Extract Camera" diff --git a/openpype/hosts/blender/plugins/publish/extract_fbx.py b/openpype/hosts/blender/plugins/publish/extract_fbx.py index 3ac66f33a4..0ad797c226 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx.py @@ -2,12 +2,12 @@ import os import bpy -from openpype import api +from openpype.pipeline import publish from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY -class ExtractFBX(api.Extractor): +class ExtractFBX(publish.Extractor): """Extract as FBX.""" label = "Extract FBX" diff --git a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py index 4b4a92932a..062b42e99d 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py @@ -5,12 +5,12 @@ import bpy import bpy_extras import bpy_extras.anim_utils -from openpype import api +from openpype.pipeline import publish from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY -class ExtractAnimationFBX(api.Extractor): +class ExtractAnimationFBX(publish.Extractor): """Extract as animation.""" label = "Extract FBX" diff --git a/openpype/hosts/blender/plugins/publish/extract_layout.py b/openpype/hosts/blender/plugins/publish/extract_layout.py index 8502c6fbd4..f2d04f1178 100644 --- a/openpype/hosts/blender/plugins/publish/extract_layout.py +++ b/openpype/hosts/blender/plugins/publish/extract_layout.py @@ -6,12 +6,12 @@ import bpy_extras import bpy_extras.anim_utils from openpype.client import get_representation_by_name +from openpype.pipeline import publish from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY -import openpype.api -class ExtractLayout(openpype.api.Extractor): +class ExtractLayout(publish.Extractor): """Extract a layout.""" label = "Extract Layout" From 72f254dd036c9bc1484a9d6d5092278e464d22a8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Sep 2022 12:54:01 +0200 Subject: [PATCH 206/274] Use new import source of Extractor --- openpype/hosts/photoshop/api/README.md | 6 +++--- .../photoshop/plugins/publish/extract_image.py | 4 ++-- .../photoshop/plugins/publish/extract_review.py | 15 +++++++++------ .../plugins/publish/extract_save_scene.py | 6 +++--- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/photoshop/api/README.md b/openpype/hosts/photoshop/api/README.md index 80792a4da0..4a36746cb2 100644 --- a/openpype/hosts/photoshop/api/README.md +++ b/openpype/hosts/photoshop/api/README.md @@ -127,11 +127,11 @@ class CollectInstances(pyblish.api.ContextPlugin): ```python import os -import openpype.api -from avalon import photoshop +from openpype.pipeline import publish +from openpype.hosts.photoshop import api as photoshop -class ExtractImage(openpype.api.Extractor): +class ExtractImage(publish.Extractor): """Produce a flattened image file from instance This plug-in takes into account only the layers in the group. diff --git a/openpype/hosts/photoshop/plugins/publish/extract_image.py b/openpype/hosts/photoshop/plugins/publish/extract_image.py index a133e33409..c84a958960 100644 --- a/openpype/hosts/photoshop/plugins/publish/extract_image.py +++ b/openpype/hosts/photoshop/plugins/publish/extract_image.py @@ -1,10 +1,10 @@ import os -import openpype.api +from openpype.pipeline import publish from openpype.hosts.photoshop import api as photoshop -class ExtractImage(openpype.api.Extractor): +class ExtractImage(publish.Extractor): """Produce a flattened image file from instance This plug-in takes into account only the layers in the group. diff --git a/openpype/hosts/photoshop/plugins/publish/extract_review.py b/openpype/hosts/photoshop/plugins/publish/extract_review.py index 5d37c86ed8..e5fee311f8 100644 --- a/openpype/hosts/photoshop/plugins/publish/extract_review.py +++ b/openpype/hosts/photoshop/plugins/publish/extract_review.py @@ -2,12 +2,15 @@ import os import shutil from PIL import Image -import openpype.api -import openpype.lib +from openpype.lib import ( + run_subprocess, + get_ffmpeg_tool_path, +) +from openpype.pipeline import publish from openpype.hosts.photoshop import api as photoshop -class ExtractReview(openpype.api.Extractor): +class ExtractReview(publish.Extractor): """ Produce a flattened or sequence image files from all 'image' instances. @@ -72,7 +75,7 @@ class ExtractReview(openpype.api.Extractor): }) processed_img_names = [img_list] - ffmpeg_path = openpype.lib.get_ffmpeg_tool_path("ffmpeg") + ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") instance.data["stagingDir"] = staging_dir @@ -93,7 +96,7 @@ class ExtractReview(openpype.api.Extractor): thumbnail_path ] self.log.debug("thumbnail args:: {}".format(args)) - output = openpype.lib.run_subprocess(args) + output = run_subprocess(args) instance.data["representations"].append({ "name": "thumbnail", @@ -116,7 +119,7 @@ class ExtractReview(openpype.api.Extractor): mov_path ] self.log.debug("mov args:: {}".format(args)) - output = openpype.lib.run_subprocess(args) + output = run_subprocess(args) self.log.debug(output) instance.data["representations"].append({ "name": "mov", diff --git a/openpype/hosts/photoshop/plugins/publish/extract_save_scene.py b/openpype/hosts/photoshop/plugins/publish/extract_save_scene.py index 03086f389f..aa900fec9f 100644 --- a/openpype/hosts/photoshop/plugins/publish/extract_save_scene.py +++ b/openpype/hosts/photoshop/plugins/publish/extract_save_scene.py @@ -1,11 +1,11 @@ -import openpype.api +from openpype.pipeline import publish from openpype.hosts.photoshop import api as photoshop -class ExtractSaveScene(openpype.api.Extractor): +class ExtractSaveScene(publish.Extractor): """Save scene before extraction.""" - order = openpype.api.Extractor.order - 0.49 + order = publish.Extractor.order - 0.49 label = "Extract Save Scene" hosts = ["photoshop"] families = ["workfile"] From aaff1525a06177127a23d6773c0ac84a1970369b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Sep 2022 12:59:14 +0200 Subject: [PATCH 207/274] remove unused teshost --- openpype/hosts/testhost/README.md | 16 -- openpype/hosts/testhost/__init__.py | 0 openpype/hosts/testhost/api/__init__.py | 43 ----- openpype/hosts/testhost/api/context.json | 1 - openpype/hosts/testhost/api/instances.json | 108 ------------ openpype/hosts/testhost/api/pipeline.py | 155 ------------------ .../testhost/plugins/create/auto_creator.py | 75 --------- .../testhost/plugins/create/test_creator_1.py | 94 ----------- .../testhost/plugins/create/test_creator_2.py | 74 --------- .../plugins/publish/collect_context.py | 34 ---- .../plugins/publish/collect_instance_1.py | 52 ------ .../publish/validate_context_with_error.py | 57 ------- .../plugins/publish/validate_with_error.py | 57 ------- openpype/hosts/testhost/run_publish.py | 68 -------- 14 files changed, 834 deletions(-) delete mode 100644 openpype/hosts/testhost/README.md delete mode 100644 openpype/hosts/testhost/__init__.py delete mode 100644 openpype/hosts/testhost/api/__init__.py delete mode 100644 openpype/hosts/testhost/api/context.json delete mode 100644 openpype/hosts/testhost/api/instances.json delete mode 100644 openpype/hosts/testhost/api/pipeline.py delete mode 100644 openpype/hosts/testhost/plugins/create/auto_creator.py delete mode 100644 openpype/hosts/testhost/plugins/create/test_creator_1.py delete mode 100644 openpype/hosts/testhost/plugins/create/test_creator_2.py delete mode 100644 openpype/hosts/testhost/plugins/publish/collect_context.py delete mode 100644 openpype/hosts/testhost/plugins/publish/collect_instance_1.py delete mode 100644 openpype/hosts/testhost/plugins/publish/validate_context_with_error.py delete mode 100644 openpype/hosts/testhost/plugins/publish/validate_with_error.py delete mode 100644 openpype/hosts/testhost/run_publish.py diff --git a/openpype/hosts/testhost/README.md b/openpype/hosts/testhost/README.md deleted file mode 100644 index f69e02a3b3..0000000000 --- a/openpype/hosts/testhost/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# What is `testhost` -Host `testhost` was created to fake running host for testing of publisher. - -Does not have any proper launch mechanism at the moment. There is python script `./run_publish.py` which will show publisher window. The script requires to set few variables to run. Execution will register host `testhost`, register global publish plugins and register creator and publish plugins from `./plugins`. - -## Data -Created instances and context data are stored into json files inside `./api` folder. Can be easily modified to save them to a different place. - -## Plugins -Test host has few plugins to be able test publishing. - -### Creators -They are just example plugins using functions from `api` to create/remove/update data. One of them is auto creator which means that is triggered on each reset of create context. Others are manual creators both creating the same family. - -### Publishers -Collectors are example plugin to use `get_attribute_defs` to define attributes for specific families or for context. Validators are to test `PublishValidationError`. diff --git a/openpype/hosts/testhost/__init__.py b/openpype/hosts/testhost/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/openpype/hosts/testhost/api/__init__.py b/openpype/hosts/testhost/api/__init__.py deleted file mode 100644 index a929a891aa..0000000000 --- a/openpype/hosts/testhost/api/__init__.py +++ /dev/null @@ -1,43 +0,0 @@ -import os -import logging -import pyblish.api - -from openpype.pipeline import register_creator_plugin_path - -from .pipeline import ( - ls, - list_instances, - update_instances, - remove_instances, - get_context_data, - update_context_data, - get_context_title -) - - -HOST_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") -PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") -CREATE_PATH = os.path.join(PLUGINS_DIR, "create") - -log = logging.getLogger(__name__) - - -def install(): - log.info("OpenPype - Installing TestHost integration") - pyblish.api.register_host("testhost") - pyblish.api.register_plugin_path(PUBLISH_PATH) - register_creator_plugin_path(CREATE_PATH) - - -__all__ = ( - "ls", - "list_instances", - "update_instances", - "remove_instances", - "get_context_data", - "update_context_data", - "get_context_title", - - "install" -) diff --git a/openpype/hosts/testhost/api/context.json b/openpype/hosts/testhost/api/context.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/openpype/hosts/testhost/api/context.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/openpype/hosts/testhost/api/instances.json b/openpype/hosts/testhost/api/instances.json deleted file mode 100644 index d955012514..0000000000 --- a/openpype/hosts/testhost/api/instances.json +++ /dev/null @@ -1,108 +0,0 @@ -[ - { - "id": "pyblish.avalon.instance", - "active": true, - "family": "test", - "subset": "testMyVariant", - "version": 1, - "asset": "sq01_sh0010", - "task": "Compositing", - "variant": "myVariant", - "instance_id": "a485f148-9121-46a5-8157-aa64df0fb449", - "creator_attributes": { - "number_key": 10, - "ha": 10 - }, - "publish_attributes": { - "CollectFtrackApi": { - "add_ftrack_family": false - } - }, - "creator_identifier": "test_one" - }, - { - "id": "pyblish.avalon.instance", - "active": true, - "family": "test", - "subset": "testMyVariant2", - "version": 1, - "asset": "sq01_sh0010", - "task": "Compositing", - "variant": "myVariant2", - "creator_attributes": {}, - "instance_id": "a485f148-9121-46a5-8157-aa64df0fb444", - "publish_attributes": { - "CollectFtrackApi": { - "add_ftrack_family": true - } - }, - "creator_identifier": "test_one" - }, - { - "id": "pyblish.avalon.instance", - "active": true, - "family": "test", - "subset": "testMain", - "version": 1, - "asset": "sq01_sh0010", - "task": "Compositing", - "variant": "Main", - "creator_attributes": {}, - "instance_id": "3607bc95-75f6-4648-a58d-e699f413d09f", - "publish_attributes": { - "CollectFtrackApi": { - "add_ftrack_family": true - } - }, - "creator_identifier": "test_two" - }, - { - "id": "pyblish.avalon.instance", - "active": true, - "family": "test", - "subset": "testMain2", - "version": 1, - "asset": "sq01_sh0020", - "task": "Compositing", - "variant": "Main2", - "instance_id": "4ccf56f6-9982-4837-967c-a49695dbe8eb", - "creator_attributes": {}, - "publish_attributes": { - "CollectFtrackApi": { - "add_ftrack_family": true - } - }, - "creator_identifier": "test_two" - }, - { - "id": "pyblish.avalon.instance", - "family": "test_three", - "subset": "test_threeMain2", - "active": true, - "version": 1, - "asset": "sq01_sh0020", - "task": "Compositing", - "variant": "Main2", - "instance_id": "4ccf56f6-9982-4837-967c-a49695dbe8ec", - "creator_attributes": {}, - "publish_attributes": { - "CollectFtrackApi": { - "add_ftrack_family": true - } - } - }, - { - "id": "pyblish.avalon.instance", - "family": "workfile", - "subset": "workfileMain", - "active": true, - "creator_identifier": "workfile", - "version": 1, - "asset": "Alpaca_01", - "task": "modeling", - "variant": "Main", - "instance_id": "7c9ddfc7-9f9c-4c1c-b233-38c966735fb6", - "creator_attributes": {}, - "publish_attributes": {} - } -] \ No newline at end of file diff --git a/openpype/hosts/testhost/api/pipeline.py b/openpype/hosts/testhost/api/pipeline.py deleted file mode 100644 index 1e05f336fb..0000000000 --- a/openpype/hosts/testhost/api/pipeline.py +++ /dev/null @@ -1,155 +0,0 @@ -import os -import json -from openpype.client import get_asset_by_name - - -class HostContext: - instances_json_path = None - context_json_path = None - - @classmethod - def get_context_title(cls): - project_name = os.environ.get("AVALON_PROJECT") - if not project_name: - return "TestHost" - - asset_name = os.environ.get("AVALON_ASSET") - if not asset_name: - return project_name - - asset_doc = get_asset_by_name( - project_name, asset_name, fields=["data.parents"] - ) - - parents = asset_doc.get("data", {}).get("parents") or [] - - hierarchy = [project_name] - hierarchy.extend(parents) - hierarchy.append("{}".format(asset_name)) - task_name = os.environ.get("AVALON_TASK") - if task_name: - hierarchy.append(task_name) - - return "/".join(hierarchy) - - @classmethod - def get_current_dir_filepath(cls, filename): - return os.path.join( - os.path.dirname(os.path.abspath(__file__)), - filename - ) - - @classmethod - def get_instances_json_path(cls): - if cls.instances_json_path is None: - cls.instances_json_path = cls.get_current_dir_filepath( - "instances.json" - ) - return cls.instances_json_path - - @classmethod - def get_context_json_path(cls): - if cls.context_json_path is None: - cls.context_json_path = cls.get_current_dir_filepath( - "context.json" - ) - return cls.context_json_path - - @classmethod - def add_instance(cls, instance): - instances = cls.get_instances() - instances.append(instance) - cls.save_instances(instances) - - @classmethod - def save_instances(cls, instances): - json_path = cls.get_instances_json_path() - with open(json_path, "w") as json_stream: - json.dump(instances, json_stream, indent=4) - - @classmethod - def get_instances(cls): - json_path = cls.get_instances_json_path() - if not os.path.exists(json_path): - instances = [] - with open(json_path, "w") as json_stream: - json.dump(json_stream, instances) - else: - with open(json_path, "r") as json_stream: - instances = json.load(json_stream) - return instances - - @classmethod - def get_context_data(cls): - json_path = cls.get_context_json_path() - if not os.path.exists(json_path): - data = {} - with open(json_path, "w") as json_stream: - json.dump(data, json_stream) - else: - with open(json_path, "r") as json_stream: - data = json.load(json_stream) - return data - - @classmethod - def save_context_data(cls, data): - json_path = cls.get_context_json_path() - with open(json_path, "w") as json_stream: - json.dump(data, json_stream, indent=4) - - -def ls(): - return [] - - -def list_instances(): - return HostContext.get_instances() - - -def update_instances(update_list): - updated_instances = {} - for instance, _changes in update_list: - updated_instances[instance.id] = instance.data_to_store() - - instances = HostContext.get_instances() - for instance_data in instances: - instance_id = instance_data["instance_id"] - if instance_id in updated_instances: - new_instance_data = updated_instances[instance_id] - old_keys = set(instance_data.keys()) - new_keys = set(new_instance_data.keys()) - instance_data.update(new_instance_data) - for key in (old_keys - new_keys): - instance_data.pop(key) - - HostContext.save_instances(instances) - - -def remove_instances(instances): - if not isinstance(instances, (tuple, list)): - instances = [instances] - - current_instances = HostContext.get_instances() - for instance in instances: - instance_id = instance.data["instance_id"] - found_idx = None - for idx, _instance in enumerate(current_instances): - if instance_id == _instance["instance_id"]: - found_idx = idx - break - - if found_idx is not None: - current_instances.pop(found_idx) - HostContext.save_instances(current_instances) - - -def get_context_data(): - return HostContext.get_context_data() - - -def update_context_data(data, changes): - HostContext.save_context_data(data) - - -def get_context_title(): - return HostContext.get_context_title() diff --git a/openpype/hosts/testhost/plugins/create/auto_creator.py b/openpype/hosts/testhost/plugins/create/auto_creator.py deleted file mode 100644 index 8d59fc3242..0000000000 --- a/openpype/hosts/testhost/plugins/create/auto_creator.py +++ /dev/null @@ -1,75 +0,0 @@ -from openpype.lib import NumberDef -from openpype.client import get_asset_by_name -from openpype.pipeline import ( - legacy_io, - AutoCreator, - CreatedInstance, -) -from openpype.hosts.testhost.api import pipeline - - -class MyAutoCreator(AutoCreator): - identifier = "workfile" - family = "workfile" - - def get_instance_attr_defs(self): - output = [ - NumberDef("number_key", label="Number") - ] - return output - - def collect_instances(self): - for instance_data in pipeline.list_instances(): - creator_id = instance_data.get("creator_identifier") - if creator_id == self.identifier: - subset_name = instance_data["subset"] - instance = CreatedInstance( - self.family, subset_name, instance_data, self - ) - self._add_instance_to_context(instance) - - def update_instances(self, update_list): - pipeline.update_instances(update_list) - - def create(self): - existing_instance = None - for instance in self.create_context.instances: - if instance.family == self.family: - existing_instance = instance - break - - variant = "Main" - project_name = legacy_io.Session["AVALON_PROJECT"] - asset_name = legacy_io.Session["AVALON_ASSET"] - task_name = legacy_io.Session["AVALON_TASK"] - host_name = legacy_io.Session["AVALON_APP"] - - if existing_instance is None: - asset_doc = get_asset_by_name(project_name, asset_name) - subset_name = self.get_subset_name( - variant, task_name, asset_doc, project_name, host_name - ) - data = { - "asset": asset_name, - "task": task_name, - "variant": variant - } - data.update(self.get_dynamic_data( - variant, task_name, asset_doc, project_name, host_name - )) - - new_instance = CreatedInstance( - self.family, subset_name, data, self - ) - self._add_instance_to_context(new_instance) - - elif ( - existing_instance["asset"] != asset_name - or existing_instance["task"] != task_name - ): - asset_doc = get_asset_by_name(project_name, asset_name) - subset_name = self.get_subset_name( - variant, task_name, asset_doc, project_name, host_name - ) - existing_instance["asset"] = asset_name - existing_instance["task"] = task_name diff --git a/openpype/hosts/testhost/plugins/create/test_creator_1.py b/openpype/hosts/testhost/plugins/create/test_creator_1.py deleted file mode 100644 index 7664276fa2..0000000000 --- a/openpype/hosts/testhost/plugins/create/test_creator_1.py +++ /dev/null @@ -1,94 +0,0 @@ -import json -from openpype import resources -from openpype.hosts.testhost.api import pipeline -from openpype.lib import ( - UISeparatorDef, - UILabelDef, - BoolDef, - NumberDef, - FileDef, -) -from openpype.pipeline import ( - Creator, - CreatedInstance, -) - - -class TestCreatorOne(Creator): - identifier = "test_one" - label = "test" - family = "test" - description = "Testing creator of testhost" - - create_allow_context_change = False - - def get_icon(self): - return resources.get_openpype_splash_filepath() - - def collect_instances(self): - for instance_data in pipeline.list_instances(): - creator_id = instance_data.get("creator_identifier") - if creator_id == self.identifier: - instance = CreatedInstance.from_existing( - instance_data, self - ) - self._add_instance_to_context(instance) - - def update_instances(self, update_list): - pipeline.update_instances(update_list) - - def remove_instances(self, instances): - pipeline.remove_instances(instances) - for instance in instances: - self._remove_instance_from_context(instance) - - def create(self, subset_name, data, pre_create_data): - print("Data that can be used in create:\n{}".format( - json.dumps(pre_create_data, indent=4) - )) - new_instance = CreatedInstance(self.family, subset_name, data, self) - pipeline.HostContext.add_instance(new_instance.data_to_store()) - self.log.info(new_instance.data) - self._add_instance_to_context(new_instance) - - def get_default_variants(self): - return [ - "myVariant", - "variantTwo", - "different_variant" - ] - - def get_instance_attr_defs(self): - output = [ - NumberDef("number_key", label="Number"), - ] - return output - - def get_pre_create_attr_defs(self): - output = [ - BoolDef("use_selection", label="Use selection"), - UISeparatorDef(), - UILabelDef("Testing label"), - FileDef("filepath", folders=True, label="Filepath"), - FileDef( - "filepath_2", multipath=True, folders=True, label="Filepath 2" - ) - ] - return output - - def get_detail_description(self): - return """# Relictus funes est Nyseides currusque nunc oblita - -## Causa sed - -Lorem markdownum posito consumptis, *plebe Amorque*, abstitimus rogatus fictaque -gladium Circe, nos? Bos aeternum quae. Utque me, si aliquem cladis, et vestigia -arbor, sic mea ferre lacrimae agantur prospiciens hactenus. Amanti dentes pete, -vos quid laudemque rastrorumque terras in gratantibus **radix** erat cedemus? - -Pudor tu ponderibus verbaque illa; ire ergo iam Venus patris certe longae -cruentum lecta, et quaeque. Sit doce nox. Anteit ad tempora magni plenaque et -videres mersit sibique auctor in tendunt mittit cunctos ventisque gravitate -volucris quemquam Aeneaden. Pectore Mensis somnus; pectora -[ferunt](http://www.mox.org/oculosbracchia)? Fertilitatis bella dulce et suum? - """ diff --git a/openpype/hosts/testhost/plugins/create/test_creator_2.py b/openpype/hosts/testhost/plugins/create/test_creator_2.py deleted file mode 100644 index f54adee8a2..0000000000 --- a/openpype/hosts/testhost/plugins/create/test_creator_2.py +++ /dev/null @@ -1,74 +0,0 @@ -from openpype.lib import NumberDef, TextDef -from openpype.hosts.testhost.api import pipeline -from openpype.pipeline import ( - Creator, - CreatedInstance, -) - - -class TestCreatorTwo(Creator): - identifier = "test_two" - label = "test" - family = "test" - description = "A second testing creator" - - def get_icon(self): - return "cube" - - def create(self, subset_name, data, pre_create_data): - new_instance = CreatedInstance(self.family, subset_name, data, self) - pipeline.HostContext.add_instance(new_instance.data_to_store()) - self.log.info(new_instance.data) - self._add_instance_to_context(new_instance) - - def collect_instances(self): - for instance_data in pipeline.list_instances(): - creator_id = instance_data.get("creator_identifier") - if creator_id == self.identifier: - instance = CreatedInstance.from_existing( - instance_data, self - ) - self._add_instance_to_context(instance) - - def update_instances(self, update_list): - pipeline.update_instances(update_list) - - def remove_instances(self, instances): - pipeline.remove_instances(instances) - for instance in instances: - self._remove_instance_from_context(instance) - - def get_instance_attr_defs(self): - output = [ - NumberDef("number_key"), - TextDef("text_key") - ] - return output - - def get_detail_description(self): - return """# Lorem ipsum, dolor sit amet. [![Awesome](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/sindresorhus/awesome) - -> A curated list of awesome lorem ipsum generators. - -Inspired by the [awesome](https://github.com/sindresorhus/awesome) list thing. - - -## Table of Contents - -- [Legend](#legend) -- [Practical](#briefcase-practical) -- [Whimsical](#roller_coaster-whimsical) - - [Animals](#rabbit-animals) - - [Eras](#tophat-eras) - - [Famous Individuals](#sunglasses-famous-individuals) - - [Music](#microphone-music) - - [Food and Drink](#pizza-food-and-drink) - - [Geographic and Dialects](#earth_africa-geographic-and-dialects) - - [Literature](#books-literature) - - [Miscellaneous](#cyclone-miscellaneous) - - [Sports and Fitness](#bicyclist-sports-and-fitness) - - [TV and Film](#movie_camera-tv-and-film) -- [Tools, Apps, and Extensions](#wrench-tools-apps-and-extensions) -- [Contribute](#contribute) -- [TODO](#todo) -""" diff --git a/openpype/hosts/testhost/plugins/publish/collect_context.py b/openpype/hosts/testhost/plugins/publish/collect_context.py deleted file mode 100644 index 0ab98fb84b..0000000000 --- a/openpype/hosts/testhost/plugins/publish/collect_context.py +++ /dev/null @@ -1,34 +0,0 @@ -import pyblish.api - -from openpype.pipeline import ( - OpenPypePyblishPluginMixin, - attribute_definitions -) - - -class CollectContextDataTestHost( - pyblish.api.ContextPlugin, OpenPypePyblishPluginMixin -): - """ - Collecting temp json data sent from a host context - and path for returning json data back to hostself. - """ - - label = "Collect Source - Test Host" - order = pyblish.api.CollectorOrder - 0.4 - hosts = ["testhost"] - - @classmethod - def get_attribute_defs(cls): - return [ - attribute_definitions.BoolDef( - "test_bool", - True, - label="Bool input" - ) - ] - - def process(self, context): - # get json paths from os and load them - for instance in context: - instance.data["source"] = "testhost" diff --git a/openpype/hosts/testhost/plugins/publish/collect_instance_1.py b/openpype/hosts/testhost/plugins/publish/collect_instance_1.py deleted file mode 100644 index c7241a15a8..0000000000 --- a/openpype/hosts/testhost/plugins/publish/collect_instance_1.py +++ /dev/null @@ -1,52 +0,0 @@ -import json -import pyblish.api - -from openpype.lib import attribute_definitions -from openpype.pipeline import OpenPypePyblishPluginMixin - - -class CollectInstanceOneTestHost( - pyblish.api.InstancePlugin, OpenPypePyblishPluginMixin -): - """ - Collecting temp json data sent from a host context - and path for returning json data back to hostself. - """ - - label = "Collect Instance 1 - Test Host" - order = pyblish.api.CollectorOrder - 0.3 - hosts = ["testhost"] - - @classmethod - def get_attribute_defs(cls): - return [ - attribute_definitions.NumberDef( - "version", - default=1, - minimum=1, - maximum=999, - decimals=0, - label="Version" - ) - ] - - def process(self, instance): - self._debug_log(instance) - - publish_attributes = instance.data.get("publish_attributes") - if not publish_attributes: - return - - values = publish_attributes.get(self.__class__.__name__) - if not values: - return - - instance.data["version"] = values["version"] - - def _debug_log(self, instance): - def _default_json(value): - return str(value) - - self.log.info( - json.dumps(instance.data, indent=4, default=_default_json) - ) diff --git a/openpype/hosts/testhost/plugins/publish/validate_context_with_error.py b/openpype/hosts/testhost/plugins/publish/validate_context_with_error.py deleted file mode 100644 index 46e996a569..0000000000 --- a/openpype/hosts/testhost/plugins/publish/validate_context_with_error.py +++ /dev/null @@ -1,57 +0,0 @@ -import pyblish.api -from openpype.pipeline import PublishValidationError - - -class ValidateInstanceAssetRepair(pyblish.api.Action): - """Repair the instance asset.""" - - label = "Repair" - icon = "wrench" - on = "failed" - - def process(self, context, plugin): - pass - - -description = """ -## Publish plugins - -### Validate Scene Settings - -#### Skip Resolution Check for Tasks - -Set regex pattern(s) to look for in a Task name to skip resolution check against values from DB. - -#### Skip Timeline Check for Tasks - -Set regex pattern(s) to look for in a Task name to skip `frameStart`, `frameEnd` check against values from DB. - -### AfterEffects Submit to Deadline - -* `Use Published scene` - Set to True (green) when Deadline should take published scene as a source instead of uploaded local one. -* `Priority` - priority of job on farm -* `Primary Pool` - here is list of pool fetched from server you can select from. -* `Secondary Pool` -* `Frames Per Task` - number of sequence division between individual tasks (chunks) -making one job on farm. -""" - - -class ValidateContextWithError(pyblish.api.ContextPlugin): - """Validate the instance asset is the current selected context asset. - - As it might happen that multiple worfiles are opened, switching - between them would mess with selected context. - In that case outputs might be output under wrong asset! - - Repair action will use Context asset value (from Workfiles or Launcher) - Closing and reopening with Workfiles will refresh Context value. - """ - - label = "Validate Context With Error" - hosts = ["testhost"] - actions = [ValidateInstanceAssetRepair] - order = pyblish.api.ValidatorOrder - - def process(self, context): - raise PublishValidationError("Crashing", "Context error", description) diff --git a/openpype/hosts/testhost/plugins/publish/validate_with_error.py b/openpype/hosts/testhost/plugins/publish/validate_with_error.py deleted file mode 100644 index 5a2888a8b0..0000000000 --- a/openpype/hosts/testhost/plugins/publish/validate_with_error.py +++ /dev/null @@ -1,57 +0,0 @@ -import pyblish.api -from openpype.pipeline import PublishValidationError - - -class ValidateInstanceAssetRepair(pyblish.api.Action): - """Repair the instance asset.""" - - label = "Repair" - icon = "wrench" - on = "failed" - - def process(self, context, plugin): - pass - - -description = """ -## Publish plugins - -### Validate Scene Settings - -#### Skip Resolution Check for Tasks - -Set regex pattern(s) to look for in a Task name to skip resolution check against values from DB. - -#### Skip Timeline Check for Tasks - -Set regex pattern(s) to look for in a Task name to skip `frameStart`, `frameEnd` check against values from DB. - -### AfterEffects Submit to Deadline - -* `Use Published scene` - Set to True (green) when Deadline should take published scene as a source instead of uploaded local one. -* `Priority` - priority of job on farm -* `Primary Pool` - here is list of pool fetched from server you can select from. -* `Secondary Pool` -* `Frames Per Task` - number of sequence division between individual tasks (chunks) -making one job on farm. -""" - - -class ValidateWithError(pyblish.api.InstancePlugin): - """Validate the instance asset is the current selected context asset. - - As it might happen that multiple worfiles are opened, switching - between them would mess with selected context. - In that case outputs might be output under wrong asset! - - Repair action will use Context asset value (from Workfiles or Launcher) - Closing and reopening with Workfiles will refresh Context value. - """ - - label = "Validate With Error" - hosts = ["testhost"] - actions = [ValidateInstanceAssetRepair] - order = pyblish.api.ValidatorOrder - - def process(self, instance): - raise PublishValidationError("Crashing", "Instance error", description) diff --git a/openpype/hosts/testhost/run_publish.py b/openpype/hosts/testhost/run_publish.py deleted file mode 100644 index c7ad63aafd..0000000000 --- a/openpype/hosts/testhost/run_publish.py +++ /dev/null @@ -1,68 +0,0 @@ -import os -import sys - -mongo_url = "" -project_name = "" -asset_name = "" -task_name = "" -ftrack_url = "" -ftrack_username = "" -ftrack_api_key = "" - - -def multi_dirname(path, times=1): - for _ in range(times): - path = os.path.dirname(path) - return path - - -host_name = "testhost" -current_file = os.path.abspath(__file__) -openpype_dir = multi_dirname(current_file, 4) - -os.environ["OPENPYPE_MONGO"] = mongo_url -os.environ["OPENPYPE_ROOT"] = openpype_dir -os.environ["AVALON_PROJECT"] = project_name -os.environ["AVALON_ASSET"] = asset_name -os.environ["AVALON_TASK"] = task_name -os.environ["AVALON_APP"] = host_name -os.environ["OPENPYPE_DATABASE_NAME"] = "openpype" -os.environ["AVALON_TIMEOUT"] = "1000" -os.environ["AVALON_DB"] = "avalon" -os.environ["FTRACK_SERVER"] = ftrack_url -os.environ["FTRACK_API_USER"] = ftrack_username -os.environ["FTRACK_API_KEY"] = ftrack_api_key -for path in [ - openpype_dir, - r"{}\repos\avalon-core".format(openpype_dir), - r"{}\.venv\Lib\site-packages".format(openpype_dir) -]: - sys.path.append(path) - -from Qt import QtWidgets, QtCore - -from openpype.tools.publisher.window import PublisherWindow - - -def main(): - """Main function for testing purposes.""" - import pyblish.api - from openpype.pipeline import install_host - from openpype.modules import ModulesManager - from openpype.hosts.testhost import api as testhost - - manager = ModulesManager() - for plugin_path in manager.collect_plugin_paths()["publish"]: - pyblish.api.register_plugin_path(plugin_path) - - install_host(testhost) - - QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling) - app = QtWidgets.QApplication([]) - window = PublisherWindow() - window.show() - app.exec_() - - -if __name__ == "__main__": - main() From 1f06830a03fa58ccfa2ccf76f940ec0281343c9c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Sep 2022 13:24:20 +0200 Subject: [PATCH 208/274] change both 'user' and 'username' keys --- openpype/plugins/publish/extract_burnin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/plugins/publish/extract_burnin.py b/openpype/plugins/publish/extract_burnin.py index 88093fb92f..36a28beb5d 100644 --- a/openpype/plugins/publish/extract_burnin.py +++ b/openpype/plugins/publish/extract_burnin.py @@ -492,6 +492,7 @@ class ExtractBurnin(openpype.api.Extractor): # OPENPYPE_USERNAME might have side effects webpublish_user_name = os.environ.get("WEBPUBLISH_OPENPYPE_USERNAME") if webpublish_user_name: + burnin_data["user"] = webpublish_user_name burnin_data["username"] = webpublish_user_name self.log.debug( From 398325684961bf9b408da0e56011cfbd4eb8bed1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Sep 2022 13:24:33 +0200 Subject: [PATCH 209/274] add 'user' to representation context data --- openpype/plugins/publish/integrate.py | 2 +- openpype/plugins/publish/integrate_hero_version.py | 2 +- openpype/plugins/publish/integrate_legacy.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index f99c718f8a..788966878f 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -135,7 +135,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # the database even if not used by the destination template db_representation_context_keys = [ "project", "asset", "task", "subset", "version", "representation", - "family", "hierarchy", "username", "output" + "family", "hierarchy", "username", "user", "output" ] skip_host_families = [] diff --git a/openpype/plugins/publish/integrate_hero_version.py b/openpype/plugins/publish/integrate_hero_version.py index 7d698ff98d..5b8b141e88 100644 --- a/openpype/plugins/publish/integrate_hero_version.py +++ b/openpype/plugins/publish/integrate_hero_version.py @@ -46,7 +46,7 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): ignored_representation_names = [] db_representation_context_keys = [ "project", "asset", "task", "subset", "representation", - "family", "hierarchy", "task", "username" + "family", "hierarchy", "task", "username", "user" ] # QUESTION/TODO this process should happen on server if crashed due to # permissions error on files (files were used or user didn't have perms) diff --git a/openpype/plugins/publish/integrate_legacy.py b/openpype/plugins/publish/integrate_legacy.py index b90b61f587..5a6190f38e 100644 --- a/openpype/plugins/publish/integrate_legacy.py +++ b/openpype/plugins/publish/integrate_legacy.py @@ -127,7 +127,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): exclude_families = ["render.farm"] db_representation_context_keys = [ "project", "asset", "task", "subset", "version", "representation", - "family", "hierarchy", "task", "username" + "family", "hierarchy", "task", "username", "user" ] default_template_name = "publish" From 198ad3ac901dc962ab840a0b09a5bd25d4a286e2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Sep 2022 13:25:02 +0200 Subject: [PATCH 210/274] add both 'user' and 'username' in integrate slack --- .../modules/slack/plugins/publish/integrate_slack_api.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index c3b288f0cd..4a8e9f773f 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -95,13 +95,15 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): Reviews might be large, so allow only adding link to message instead of uploading only. """ + fill_data = copy.deepcopy(instance.context.data["anatomyData"]) + username = fill_data.get("user") fill_pairs = [ ("asset", instance.data.get("asset", fill_data.get("asset"))), ("subset", instance.data.get("subset", fill_data.get("subset"))), - ("username", instance.data.get("username", - fill_data.get("username"))), + ("user", username), + ("username", username), ("app", instance.data.get("app", fill_data.get("app"))), ("family", instance.data.get("family", fill_data.get("family"))), ("version", str(instance.data.get("version", From 6bcbf34d27e3806ae7f62a09d7d5a6002671a2eb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Sep 2022 13:53:39 +0200 Subject: [PATCH 211/274] changed class name to 'CollectUsernameForWebpublish' --- openpype/modules/ftrack/plugins/publish/collect_username.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/collect_username.py b/openpype/modules/ftrack/plugins/publish/collect_username.py index a9b746ea51..0e232bf83e 100644 --- a/openpype/modules/ftrack/plugins/publish/collect_username.py +++ b/openpype/modules/ftrack/plugins/publish/collect_username.py @@ -13,7 +13,7 @@ import os import pyblish.api -class CollectUsername(pyblish.api.ContextPlugin): +class CollectUsernameForWebpublish(pyblish.api.ContextPlugin): """ Translates user email to Ftrack username. @@ -32,10 +32,8 @@ class CollectUsername(pyblish.api.ContextPlugin): hosts = ["webpublisher", "photoshop"] targets = ["remotepublish", "filespublish", "tvpaint_worker"] - _context = None - def process(self, context): - self.log.info("CollectUsername") + self.log.info("{}".format(self.__class__.__name__)) os.environ["FTRACK_API_USER"] = os.environ["FTRACK_BOT_API_USER"] os.environ["FTRACK_API_KEY"] = os.environ["FTRACK_BOT_API_KEY"] From 8e6b0567c967008b0b64151eeceed23a426ed073 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Sep 2022 13:54:29 +0200 Subject: [PATCH 212/274] skip 'WEBPUBLISH_OPENPYPE_USERNAME' usage --- .../modules/ftrack/plugins/publish/collect_username.py | 1 - openpype/plugins/publish/extract_burnin.py | 7 ------- 2 files changed, 8 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/collect_username.py b/openpype/modules/ftrack/plugins/publish/collect_username.py index 0e232bf83e..ab1f7d8d5d 100644 --- a/openpype/modules/ftrack/plugins/publish/collect_username.py +++ b/openpype/modules/ftrack/plugins/publish/collect_username.py @@ -65,5 +65,4 @@ class CollectUsernameForWebpublish(pyblish.api.ContextPlugin): burnin_name = username if '@' in burnin_name: burnin_name = burnin_name[:burnin_name.index('@')] - os.environ["WEBPUBLISH_OPENPYPE_USERNAME"] = burnin_name context.data["user"] = burnin_name diff --git a/openpype/plugins/publish/extract_burnin.py b/openpype/plugins/publish/extract_burnin.py index 36a28beb5d..c8b2b73874 100644 --- a/openpype/plugins/publish/extract_burnin.py +++ b/openpype/plugins/publish/extract_burnin.py @@ -488,13 +488,6 @@ class ExtractBurnin(openpype.api.Extractor): "frame_end_handle": frame_end_handle } - # use explicit username for webpublishes as rewriting - # OPENPYPE_USERNAME might have side effects - webpublish_user_name = os.environ.get("WEBPUBLISH_OPENPYPE_USERNAME") - if webpublish_user_name: - burnin_data["user"] = webpublish_user_name - burnin_data["username"] = webpublish_user_name - self.log.debug( "Basic burnin_data: {}".format(json.dumps(burnin_data, indent=4)) ) From 3ac5305f65b17083b126eeb76675064311bf4f4a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Sep 2022 13:54:58 +0200 Subject: [PATCH 213/274] better user query --- .../modules/ftrack/plugins/publish/collect_username.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/collect_username.py b/openpype/modules/ftrack/plugins/publish/collect_username.py index ab1f7d8d5d..798f3960a8 100644 --- a/openpype/modules/ftrack/plugins/publish/collect_username.py +++ b/openpype/modules/ftrack/plugins/publish/collect_username.py @@ -1,5 +1,8 @@ """Loads publishing context from json and continues in publish process. +Should run before 'CollectAnatomyContextData' so the user on context is +changed before it's stored to context anatomy data or instance anatomy data. + Requires: anatomy -> context["anatomy"] *(pyblish.api.CollectorOrder - 0.11) @@ -52,12 +55,14 @@ class CollectUsernameForWebpublish(pyblish.api.ContextPlugin): return session = ftrack_api.Session(auto_connect_event_hub=False) - user = session.query("User where email like '{}'".format(user_email)) + user = session.query( + "User where email like '{}'".format(user_email) + ).first() if not user: raise ValueError( "Couldn't find user with {} email".format(user_email)) - user = user[0] + username = user.get("username") self.log.debug("Resolved ftrack username:: {}".format(username)) os.environ["FTRACK_API_USER"] = username From 3c6b999d544d65a8d31cc37ae54c731ff098f73a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Albert?= Date: Thu, 1 Sep 2022 17:22:14 +0200 Subject: [PATCH 214/274] Kitsu - sync_all_project - add list ignore_projects --- openpype/modules/kitsu/utils/update_op_with_zou.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 8d65591c0b..f013251bb1 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -318,13 +318,13 @@ def write_project_to_op(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: ) -def sync_all_projects(login: str, password: str): +def sync_all_projects(login: str, password: str, ignore_projects: list = []): """Update all OP projects in DB with Zou data. Args: login (str): Kitsu user login password (str): Kitsu user password - + ignore_projects (list): List of unsynced project names Raises: gazu.exception.AuthFailedException: Wrong user login and/or password """ @@ -340,7 +340,8 @@ def sync_all_projects(login: str, password: str): dbcon.install() all_projects = gazu.project.all_open_projects() for project in all_projects: - sync_project_from_kitsu(dbcon, project) + if project["name"] not in ignore_projects: + sync_project_from_kitsu(dbcon, project) def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): From d90f9fb5b819e874012141391861b843fc4abc82 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 1 Sep 2022 18:12:16 +0200 Subject: [PATCH 215/274] Fix - changed format of version string in pyproject.toml Since Poetry 1.2 version in .toml must follow PEP-440, in our case there must not be dash sign. Plus sign is valid though. Version in pyproject.toml is not used anywhere yet (only maybe when uploading wheel to pip, which is not really used). --- pyproject.toml | 2 +- tools/ci_tools.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9ed1872eac..0deb4f465b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.14.2-nightly.1" # OpenPype +version = "3.14.2+nightly.1" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" diff --git a/tools/ci_tools.py b/tools/ci_tools.py index 4c59cd6af6..65a615b33e 100644 --- a/tools/ci_tools.py +++ b/tools/ci_tools.py @@ -101,7 +101,7 @@ def bump_file_versions(version): # bump pyproject.toml filename = "pyproject.toml" - regex = "version = \"(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-((0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?\" # OpenPype" + regex = "version = \"(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(+((0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?\" # OpenPype" pyproject_version = f"version = \"{version}\" # OpenPype" file_regex_replace(filename, regex, pyproject_version) From 05043b8578e8fcfa4cae58609fc1c5055a1f019c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 1 Sep 2022 18:15:49 +0200 Subject: [PATCH 216/274] Fix - escaped plus sign --- tools/ci_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ci_tools.py b/tools/ci_tools.py index 65a615b33e..c8f0cd48b4 100644 --- a/tools/ci_tools.py +++ b/tools/ci_tools.py @@ -101,7 +101,7 @@ def bump_file_versions(version): # bump pyproject.toml filename = "pyproject.toml" - regex = "version = \"(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(+((0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?\" # OpenPype" + regex = "version = \"(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(\+((0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?\" # OpenPype" pyproject_version = f"version = \"{version}\" # OpenPype" file_regex_replace(filename, regex, pyproject_version) From 4fc90655d7e5b6869650638e4b7ff064ebc2271e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 1 Sep 2022 18:59:20 +0200 Subject: [PATCH 217/274] Fix - update cx-freeze Old version caused "No module named 'distutils.command.bdist_msi'" error --- poetry.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 21b6bda880..726b248f8c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -339,7 +339,7 @@ test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", [[package]] name = "cx-freeze" -version = "6.9" +version = "6.11.1" description = "Create standalone executables from Python scripts" category = "dev" optional = false From 58724a4c1be465b1429bc32c353ac25db3fc0624 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 1 Sep 2022 23:33:27 +0200 Subject: [PATCH 218/274] Have `get_project` `inactive` argument default to True --- openpype/client/entities.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 3d2730a17c..f9afde0f5d 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -58,7 +58,7 @@ def get_projects(active=True, inactive=False, fields=None): yield project_doc -def get_project(project_name, active=True, inactive=False, fields=None): +def get_project(project_name, active=True, inactive=True, fields=None): # Skip if both are disabled if not active and not inactive: return None From ff88f201ec648a001ca63c770edfa8fcdcbc7f26 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 2 Sep 2022 00:16:52 +0200 Subject: [PATCH 219/274] Ignore empty labels, then still use name like other asset models --- openpype/tools/standalonepublish/widgets/model_asset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/standalonepublish/widgets/model_asset.py b/openpype/tools/standalonepublish/widgets/model_asset.py index abfc0a2145..9fed46b3fe 100644 --- a/openpype/tools/standalonepublish/widgets/model_asset.py +++ b/openpype/tools/standalonepublish/widgets/model_asset.py @@ -81,7 +81,7 @@ class AssetModel(TreeModel): for asset in current_assets: # get label from data, otherwise use name data = asset.get("data", {}) - label = data.get("label", asset["name"]) + label = data.get("label") or asset["name"] tags = data.get("tags", []) # store for the asset for optimization From 8bb526ed8f3f76e2fa1745e9b724ef9ba4051224 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 2 Sep 2022 08:48:30 +0200 Subject: [PATCH 220/274] nuke: validate write node is not failing due wrong type --- .../hosts/nuke/plugins/publish/validate_write_nodes.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py b/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py index 5a8bc2022e..26a563b13b 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py +++ b/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py @@ -2,7 +2,8 @@ import pyblish.api from openpype.pipeline.publish import get_errored_instances_from_context from openpype.hosts.nuke.api.lib import ( get_write_node_template_attr, - set_node_knobs_from_settings + set_node_knobs_from_settings, + color_gui_to_int ) from openpype.pipeline import PublishXmlValidationError @@ -76,8 +77,11 @@ class ValidateNukeWriteNode(pyblish.api.InstancePlugin): # fix type differences if type(node_value) in (int, float): - value = float(value) - node_value = float(node_value) + if isinstance(value, list): + value = color_gui_to_int(value) + else: + value = float(value) + node_value = float(node_value) else: value = str(value) node_value = str(node_value) From 7ed5f5a0ee56d6481d28f0510d1bc6311e89ac38 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 2 Sep 2022 11:04:13 +0200 Subject: [PATCH 221/274] Fix - update cx-freeze Old version caused "No module named 'distutils.command.bdist_msi'" error --- poetry.lock | 990 ++++++++++++++++++++++++++++++++++++++++++------- pyproject.toml | 3 +- 2 files changed, 864 insertions(+), 129 deletions(-) diff --git a/poetry.lock b/poetry.lock index 726b248f8c..b428393a1f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -33,7 +33,7 @@ typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} yarl = ">=1.0,<2.0" [package.extras] -speedups = ["aiodns", "brotli", "cchardet"] +speedups = ["Brotli", "aiodns", "cchardet"] [[package]] name = "aiohttp-json-rpc" @@ -122,6 +122,7 @@ python-versions = ">=3.6.2" [package.dependencies] lazy-object-proxy = ">=1.4.0" +setuptools = ">=20.0" typed-ast = {version = ">=1.4.0,<2.0", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""} typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""} wrapt = ">=1.11,<2" @@ -162,10 +163,10 @@ optional = false python-versions = ">=3.5" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] -docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "cloudpickle"] +dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] +docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] +tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] +tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] [[package]] name = "autopep8" @@ -273,9 +274,9 @@ optional = false python-versions = ">=2.7, <4.0" [package.extras] -dev = ["sphinx (>=2,<4)", "sphinx-rtd-theme (>=0.1.6,<1)", "lowdown (>=0.2.0,<1)", "pytest-runner (>=2.7,<3)", "pytest (>=2.3.5,<5)", "pytest-cov (>=2,<3)"] -doc = ["sphinx (>=2,<4)", "sphinx-rtd-theme (>=0.1.6,<1)", "lowdown (>=0.2.0,<1)"] -test = ["pytest-runner (>=2.7,<3)", "pytest (>=2.3.5,<5)", "pytest-cov (>=2,<3)"] +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 = "colorama" @@ -330,30 +331,39 @@ python-versions = ">=3.6" cffi = ">=1.12" [package.extras] -docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] -docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx_rtd_theme"] +docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] sdist = ["setuptools_rust (>=0.11.4)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] +test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"] [[package]] -name = "cx-freeze" +name = "cx-Freeze" version = "6.11.1" description = "Create standalone executables from Python scripts" -category = "dev" +category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -cx-logging = {version = ">=3.0", markers = "sys_platform == \"win32\""} -importlib-metadata = ">=4.3.1" +cx-logging = {version = ">=3.0", markers = "sys_platform == \"win32\" and python_version < \"3.10\""} +importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.10\""} +lief = {version = ">=0.11.5", markers = "sys_platform == \"win32\" and python_version <= \"3.10\""} +packaging = ">=21.0" +patchelf = {version = ">=0.12", markers = "sys_platform == \"linux\""} +setuptools = ">=59.0.1,<=60.10.0" + +[package.extras] +dev = ["bump2version (>=1.0.1)", "cibuildwheel (==2.6.1)", "pre-commit (>=2.17.0)", "pylint (>=2.13.0)"] +doc = ["sphinx (>=5.0.1,<6.0.0)", "sphinx-rtd-theme (==1.0.0)"] +test = ["nose (==1.3.7)", "pygments (>=2.11.2)", "pytest (>=7.0.1)", "pytest-cov (==3.0.0)", "pytest-mock (>=3.6.1)", "pytest-timeout (>=1.4.2)"] [[package]] name = "cx-logging" version = "3.0" description = "Python and C interfaces for logging" -category = "dev" +category = "main" optional = false python-versions = "*" @@ -369,7 +379,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" wrapt = ">=1.10,<2" [package.extras] -dev = ["tox", "bump2version (<1)", "sphinx (<2)", "importlib-metadata (<3)", "importlib-resources (<4)", "configparser (<5)", "sphinxcontrib-websupport (<2)", "zipp (<2)", "PyTest (<5)", "PyTest-Cov (<2.6)", "pytest", "pytest-cov"] +dev = ["PyTest", "PyTest (<5)", "PyTest-Cov", "PyTest-Cov (<2.6)", "bump2version (<1)", "configparser (<5)", "importlib-metadata (<3)", "importlib-resources (<4)", "sphinx (<2)", "sphinxcontrib-websupport (<2)", "tox", "zipp (<2)"] [[package]] name = "dill" @@ -391,8 +401,8 @@ optional = false python-versions = ">=3.6,<4.0" [package.extras] -dnssec = ["cryptography (>=2.6,<37.0)"] curio = ["curio (>=1.2,<2.0)", "sniffio (>=1.1,<2.0)"] +dnssec = ["cryptography (>=2.6,<37.0)"] doh = ["h2 (>=4.1.0)", "httpx (>=0.21.1)", "requests (>=2.23.0,<3.0.0)", "requests-toolbelt (>=0.9.1,<0.10.0)"] idna = ["idna (>=2.1,<4.0)"] trio = ["trio (>=0.14,<0.20)"] @@ -503,7 +513,7 @@ requests = ">=2.25.1,<=2.27.1" [package.extras] dev = ["wheel"] -test = ["pytest-cov (==2.12.1)", "requests-mock (==1.9.3)", "pytest (==4.6.11)", "pytest (==6.1.2)", "pytest (==6.2.5)", "black (==21.12b0)", "pre-commit (==2.17.0)"] +test = ["black (==21.12b0)", "pre-commit (==2.17.0)", "pytest (==4.6.11)", "pytest (==6.1.2)", "pytest (==6.2.5)", "pytest-cov (==2.12.1)", "requests-mock (==1.9.3)"] [[package]] name = "gitdb" @@ -576,7 +586,7 @@ rsa = {version = ">=3.1.4,<5", markers = "python_version >= \"3.6\""} six = ">=1.9.0" [package.extras] -aiohttp = ["requests (>=2.20.0,<3.0.0dev)", "aiohttp (>=3.6.2,<4.0.0dev)"] +aiohttp = ["aiohttp (>=3.6.2,<4.0.0dev)", "requests (>=2.20.0,<3.0.0dev)"] enterprise_cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"] pyopenssl = ["pyopenssl (>=20.0.0)"] reauth = ["pyu2f (>=0.1.5)"] @@ -648,9 +658,9 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] +docs = ["jaraco.packaging (>=9)", "rst.linker (>=1.9)", "sphinx"] perf = ["ipython"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] [[package]] name = "iniconfig" @@ -669,10 +679,10 @@ optional = false python-versions = ">=3.6.1,<4.0" [package.extras] -pipfile_deprecated_finder = ["pipreqs", "requirementslib"] -requirements_deprecated_finder = ["pipreqs", "pip-api"] colors = ["colorama (>=0.4.3,<0.5.0)"] +pipfile_deprecated_finder = ["pipreqs", "requirementslib"] plugins = ["setuptools"] +requirements_deprecated_finder = ["pip-api", "pipreqs"] [[package]] name = "jedi" @@ -697,8 +707,8 @@ optional = false python-versions = ">=3.7" [package.extras] -trio = ["async-generator", "trio"] -test = ["async-timeout", "trio", "testpath", "pytest-asyncio (>=0.17)", "pytest-trio", "pytest"] +test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] +trio = ["async_generator", "trio"] [[package]] name = "jinja2" @@ -751,8 +761,8 @@ pywin32-ctypes = {version = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1", markers = "sys_ SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "pytest-black (>=0.3.7)", "pytest-mypy"] +docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] +testing = ["pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=1.2.3)", "pytest-cov", "pytest-enabler", "pytest-flake8", "pytest-mypy"] [[package]] name = "lazy-object-proxy" @@ -762,6 +772,14 @@ category = "dev" optional = false python-versions = ">=3.6" +[[package]] +name = "lief" +version = "0.12.1" +description = "Library to instrument executable formats" +category = "main" +optional = false +python-versions = ">=3.6" + [[package]] name = "log4mongo" version = "1.7.0" @@ -809,7 +827,7 @@ python-versions = "*" pyaaf2 = "1.4.0" [package.extras] -dev = ["check-manifest", "flake8 (>=3.5)", "coverage (>=4.5)", "urllib3 (>=1.24.3)"] +dev = ["check-manifest", "coverage (>=4.5)", "flake8 (>=3.5)", "urllib3 (>=1.24.3)"] view = ["PySide2 (>=5.11,<6.0)"] [package.source] @@ -821,7 +839,7 @@ reference = "openpype" name = "packaging" version = "21.3" description = "Core utilities for Python packages" -category = "dev" +category = "main" optional = false python-versions = ">=3.6" @@ -843,9 +861,9 @@ pynacl = ">=1.0.1" six = "*" [package.extras] -all = ["pyasn1 (>=0.1.7)", "pynacl (>=1.0.1)", "bcrypt (>=3.1.3)", "invoke (>=1.3)", "gssapi (>=1.4.1)", "pywin32 (>=2.1.8)"] -ed25519 = ["pynacl (>=1.0.1)", "bcrypt (>=3.1.3)"] -gssapi = ["pyasn1 (>=0.1.7)", "gssapi (>=1.4.1)", "pywin32 (>=2.1.8)"] +all = ["bcrypt (>=3.1.3)", "gssapi (>=1.4.1)", "invoke (>=1.3)", "pyasn1 (>=0.1.7)", "pynacl (>=1.0.1)", "pywin32 (>=2.1.8)"] +ed25519 = ["bcrypt (>=3.1.3)", "pynacl (>=1.0.1)"] +gssapi = ["gssapi (>=1.4.1)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] invoke = ["invoke (>=1.3)"] [[package]] @@ -860,6 +878,17 @@ python-versions = ">=3.6" qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] testing = ["docopt", "pytest (<6.0.0)"] +[[package]] +name = "patchelf" +version = "0.15.0.0" +description = "A small utility to modify the dynamic linker and RPATH of ELF executables." +category = "main" +optional = false +python-versions = "*" + +[package.extras] +test = ["importlib-metadata", "pytest"] + [[package]] name = "pathlib2" version = "2.3.7.post1" @@ -892,8 +921,8 @@ optional = false python-versions = ">=3.7" [package.extras] -docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] -test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] +docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)"] +test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] [[package]] name = "pluggy" @@ -1054,7 +1083,7 @@ python-versions = "*" aws = ["pymongo-auth-aws (<2.0.0)"] encryption = ["pymongocrypt (>=1.1.0,<2.0.0)"] gssapi = ["pykerberos"] -ocsp = ["pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)", "certifi"] +ocsp = ["certifi", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] snappy = ["python-snappy"] srv = ["dnspython (>=1.16.0,<1.17.0)"] tls = ["ipaddress"] @@ -1072,8 +1101,8 @@ python-versions = ">=3.6" cffi = ">=1.4.1" [package.extras] -docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] -tests = ["pytest (>=3.2.1,!=3.3.0)", "hypothesis (>=3.27.0)"] +docs = ["sphinx (>=1.6.5)", "sphinx_rtd_theme"] +tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] [[package]] name = "pynput" @@ -1188,7 +1217,7 @@ coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" [package.extras] -testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] [[package]] name = "pytest-print" @@ -1381,6 +1410,19 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "setuptools" +version = "60.10.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx", "sphinx-favicon", "sphinx-inline-tabs", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-xdist", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + [[package]] name = "shotgun-api3" version = "3.3.3" @@ -1413,8 +1455,8 @@ optional = false python-versions = ">=3.6.0" [package.extras] -optional = ["aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "SQLAlchemy (>=1,<2)", "websockets (>=10,<11)", "websocket-client (>=1,<2)"] -testing = ["pytest (>=6.2.5,<7)", "pytest-asyncio (<1)", "Flask-Sockets (>=0.2,<1)", "Flask (>=1,<2)", "Werkzeug (<2)", "itsdangerous (==1.1.0)", "Jinja2 (==3.0.3)", "pytest-cov (>=2,<3)", "codecov (>=2,<3)", "flake8 (>=4,<5)", "black (==22.3.0)", "click (==8.0.4)", "psutil (>=5,<6)", "databases (>=0.5)", "boto3 (<=2)", "moto (>=3,<4)"] +optional = ["SQLAlchemy (>=1,<2)", "aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "websocket-client (>=1,<2)", "websockets (>=10,<11)"] +testing = ["Flask (>=1,<2)", "Flask-Sockets (>=0.2,<1)", "Jinja2 (==3.0.3)", "Werkzeug (<2)", "black (==22.3.0)", "boto3 (<=2)", "click (==8.0.4)", "codecov (>=2,<3)", "databases (>=0.5)", "flake8 (>=4,<5)", "itsdangerous (==1.1.0)", "moto (>=3,<4)", "psutil (>=5,<6)", "pytest (>=6.2.5,<7)", "pytest-asyncio (<1)", "pytest-cov (>=2,<3)"] [[package]] name = "smmap" @@ -1469,8 +1511,8 @@ sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.950)", "docutils-stubs", "types-typed-ast", "types-requests"] -test = ["pytest (>=4.6)", "html5lib", "cython", "typed-ast"] +lint = ["docutils-stubs", "flake8 (>=3.5.0)", "isort", "mypy (>=0.950)", "types-requests", "types-typed-ast"] +test = ["cython", "html5lib", "pytest (>=4.6)", "typed-ast"] [[package]] name = "sphinx-qt-documentation" @@ -1485,9 +1527,9 @@ docutils = "*" sphinx = "*" [package.extras] -test = ["pytest-cov", "pytest (>=3.0.0)"] -lint = ["pylint", "flake8", "black"] dev = ["pre-commit"] +lint = ["black", "flake8", "pylint"] +test = ["pytest (>=3.0.0)", "pytest-cov"] [[package]] name = "sphinx-rtd-theme" @@ -1502,7 +1544,7 @@ docutils = "<0.18" sphinx = ">=1.6" [package.extras] -dev = ["transifex-client", "sphinxcontrib-httpdomain", "bump2version"] +dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client"] [[package]] name = "sphinxcontrib-applehelp" @@ -1513,7 +1555,7 @@ optional = false python-versions = ">=3.5" [package.extras] -lint = ["flake8", "mypy", "docutils-stubs"] +lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] @@ -1525,7 +1567,7 @@ optional = false python-versions = ">=3.5" [package.extras] -lint = ["flake8", "mypy", "docutils-stubs"] +lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] @@ -1537,8 +1579,8 @@ optional = false python-versions = ">=3.6" [package.extras] -lint = ["flake8", "mypy", "docutils-stubs"] -test = ["pytest", "html5lib"] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["html5lib", "pytest"] [[package]] name = "sphinxcontrib-jsmath" @@ -1549,7 +1591,7 @@ optional = false python-versions = ">=3.5" [package.extras] -test = ["pytest", "flake8", "mypy"] +test = ["flake8", "mypy", "pytest"] [[package]] name = "sphinxcontrib-qthelp" @@ -1560,7 +1602,7 @@ optional = false python-versions = ">=3.5" [package.extras] -lint = ["flake8", "mypy", "docutils-stubs"] +lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] @@ -1572,7 +1614,7 @@ optional = false python-versions = ">=3.5" [package.extras] -lint = ["flake8", "mypy", "docutils-stubs"] +lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] @@ -1588,7 +1630,7 @@ sphinxcontrib-serializinghtml = "*" [package.extras] lint = ["flake8"] -test = ["pytest", "sqlalchemy", "whoosh", "sphinx"] +test = ["Sphinx", "pytest", "sqlalchemy", "whoosh"] [[package]] name = "stone" @@ -1659,8 +1701,8 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" [package.extras] -brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] -secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] @@ -1682,6 +1724,17 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] six = "*" +[[package]] +name = "wheel" +version = "0.37.1" +description = "A built-package format for Python" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[package.extras] +test = ["pytest (>=3.0.0)", "pytest-cov"] + [[package]] name = "wrapt" version = "1.14.1" @@ -1703,8 +1756,8 @@ aiohttp = "<4" yarl = "*" [package.extras] -develop = ["async-timeout", "coverage (!=4.3)", "coveralls", "pytest", "pytest-aiohttp", "pytest-cov", "sphinx", "sphinxcontrib-plantuml", "tox (>=2.4)"] -testing = ["async-timeout", "pytest", "pytest-aiohttp", "pytest-cov", "coverage (!=4.3)", "coveralls"] +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]] @@ -1729,13 +1782,13 @@ optional = false python-versions = ">=3.7" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "jaraco.tidelift (>=1.4)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] +docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"] +testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [metadata] lock-version = "1.1" python-versions = "3.7.*" -content-hash = "de7422afb6aed02f75e1696afdda9ad6c7bf32da76b5022ee3e8f71a1ac4bae2" +content-hash = "6a5f6910109c9ed6fb1cc1cb1cef7b21a0055a17c393175a1a7aabc00e35d54f" [metadata.files] acre = [] @@ -1817,7 +1870,10 @@ aiohttp-json-rpc = [ {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"}, ] -aiohttp-middlewares = [] +aiohttp-middlewares = [ + {file = "aiohttp-middlewares-2.1.0.tar.gz", hash = "sha256:5863970d944dc63faedc96ef324a7fe2bcefefebe29acc90cd641236322d00c3"}, + {file = "aiohttp_middlewares-2.1.0-py3-none-any.whl", hash = "sha256:c83d48702e6a8669981976f39a60e83d059dc01d7b1ee651aec5d4cb807ff784"}, +] aiosignal = [ {file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"}, {file = "aiosignal-1.2.0.tar.gz", hash = "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2"}, @@ -1835,7 +1891,10 @@ arrow = [ {file = "arrow-0.17.0-py2.py3-none-any.whl", hash = "sha256:e098abbd9af3665aea81bdd6c869e93af4feb078e98468dd351c383af187aac5"}, {file = "arrow-0.17.0.tar.gz", hash = "sha256:ff08d10cda1d36c68657d6ad20d74fbea493d980f8b2d45344e00d6ed2bf6ed4"}, ] -astroid = [] +astroid = [ + {file = "astroid-2.11.7-py3-none-any.whl", hash = "sha256:86b0a340a512c65abf4368b80252754cda17c02cdbbd3f587dddf98112233e7b"}, + {file = "astroid-2.11.7.tar.gz", hash = "sha256:bb24615c77f4837c707669d16907331374ae8a964650a66999da3f5ca68dc946"}, +] async-timeout = [ {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, @@ -1844,21 +1903,112 @@ asynctest = [ {file = "asynctest-0.13.0-py3-none-any.whl", hash = "sha256:5da6118a7e6d6b54d83a8f7197769d046922a44d2a99c21382f0a6e4fadae676"}, {file = "asynctest-0.13.0.tar.gz", hash = "sha256:c27862842d15d83e6a34eb0b2866c323880eb3a75e4485b079ea11748fd77fac"}, ] -atomicwrites = [] -attrs = [] +atomicwrites = [ + {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, +] +attrs = [ + {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, + {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, +] autopep8 = [ {file = "autopep8-1.5.7-py2.py3-none-any.whl", hash = "sha256:aa213493c30dcdac99537249ee65b24af0b2c29f2e83cd8b3f68760441ed0db9"}, {file = "autopep8-1.5.7.tar.gz", hash = "sha256:276ced7e9e3cb22e5d7c14748384a5cf5d9002257c0ed50c0e075b68011bb6d0"}, ] -babel = [] -bcrypt = [] +babel = [ + {file = "Babel-2.10.3-py3-none-any.whl", hash = "sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb"}, + {file = "Babel-2.10.3.tar.gz", hash = "sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51"}, +] +bcrypt = [ + {file = "bcrypt-3.2.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:7180d98a96f00b1050e93f5b0f556e658605dd9f524d0b0e68ae7944673f525e"}, + {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:61bae49580dce88095d669226d5076d0b9d927754cedbdf76c6c9f5099ad6f26"}, + {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88273d806ab3a50d06bc6a2fc7c87d737dd669b76ad955f449c43095389bc8fb"}, + {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6d2cb9d969bfca5bc08e45864137276e4c3d3d7de2b162171def3d188bf9d34a"}, + {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b02d6bfc6336d1094276f3f588aa1225a598e27f8e3388f4db9948cb707b521"}, + {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a2c46100e315c3a5b90fdc53e429c006c5f962529bc27e1dfd656292c20ccc40"}, + {file = "bcrypt-3.2.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7d9ba2e41e330d2af4af6b1b6ec9e6128e91343d0b4afb9282e54e5508f31baa"}, + {file = "bcrypt-3.2.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cd43303d6b8a165c29ec6756afd169faba9396a9472cdff753fe9f19b96ce2fa"}, + {file = "bcrypt-3.2.2-cp36-abi3-win32.whl", hash = "sha256:4e029cef560967fb0cf4a802bcf4d562d3d6b4b1bf81de5ec1abbe0f1adb027e"}, + {file = "bcrypt-3.2.2-cp36-abi3-win_amd64.whl", hash = "sha256:7ff2069240c6bbe49109fe84ca80508773a904f5a8cb960e02a977f7f519b129"}, + {file = "bcrypt-3.2.2.tar.gz", hash = "sha256:433c410c2177057705da2a9f2cd01dd157493b2a7ac14c8593a16b3dab6b6bfb"}, +] blessed = [ {file = "blessed-1.19.1-py2.py3-none-any.whl", hash = "sha256:63b8554ae2e0e7f43749b6715c734cc8f3883010a809bf16790102563e6cf25b"}, {file = "blessed-1.19.1.tar.gz", hash = "sha256:9a0d099695bf621d4680dd6c73f6ad547f6a3442fbdbe80c4b1daa1edbc492fc"}, ] -cachetools = [] -certifi = [] -cffi = [] +cachetools = [ + {file = "cachetools-5.2.0-py3-none-any.whl", hash = "sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db"}, + {file = "cachetools-5.2.0.tar.gz", hash = "sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757"}, +] +certifi = [ + {file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"}, + {file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"}, +] +cffi = [ + {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, + {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, + {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, + {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, + {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, + {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, + {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, + {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, + {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, + {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, + {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, + {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, + {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, + {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, + {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, + {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, + {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, + {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, + {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, +] charset-normalizer = [ {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, @@ -1871,7 +2021,10 @@ clique = [ {file = "clique-1.6.1-py2.py3-none-any.whl", hash = "sha256:8619774fa035661928dd8c93cd805acf2d42533ccea1b536c09815ed426c9858"}, {file = "clique-1.6.1.tar.gz", hash = "sha256:90165c1cf162d4dd1baef83ceaa1afc886b453e379094fa5b60ea470d1733e66"}, ] -colorama = [] +colorama = [ + {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, + {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, +] commonmark = [ {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, @@ -1880,20 +2033,99 @@ coolname = [ {file = "coolname-1.1.0-py2.py3-none-any.whl", hash = "sha256:e6a83a0ac88640f4f3d2070438dbe112fe80cfebc119c93bd402976ec84c0978"}, {file = "coolname-1.1.0.tar.gz", hash = "sha256:410fe6ea9999bf96f2856ef0c726d5f38782bbefb7bb1aca0e91e0dc98ed09e3"}, ] -coverage = [] -cryptography = [] -cx-freeze = [ - {file = "cx_Freeze-6.9-cp310-cp310-win32.whl", hash = "sha256:776d4fb68a4831691acbd3c374362b9b48ce2e568514a73c3d4cb14d5dcf1470"}, - {file = "cx_Freeze-6.9-cp310-cp310-win_amd64.whl", hash = "sha256:243f36d35a034a409cd6247d8cb5d1fbfd7374e3e668e813d0811f64d6bd5ed3"}, - {file = "cx_Freeze-6.9-cp36-cp36m-win32.whl", hash = "sha256:ffc855eabc735b693e2d604d71dce6d52d78a6ba1070c55d51e786dd68ed232c"}, - {file = "cx_Freeze-6.9-cp36-cp36m-win_amd64.whl", hash = "sha256:fe4e32a0c75b2b54491882926bf3ba12f8a3d589822a68a8be7c09f1dcca5546"}, - {file = "cx_Freeze-6.9-cp37-cp37m-win32.whl", hash = "sha256:99c292e7a31cb343efc0cf47f82220a44a4a3b8776651624cd8ee03c23104940"}, - {file = "cx_Freeze-6.9-cp37-cp37m-win_amd64.whl", hash = "sha256:738ab22f3a3f6bc220b16dccf2aa0603c3cd271b2a7a9d9480dab82311308b23"}, - {file = "cx_Freeze-6.9-cp38-cp38-win32.whl", hash = "sha256:c1c75df572858e623d0aa39771cd984c0abd8aacb43b2aca2d12d0bc95f25566"}, - {file = "cx_Freeze-6.9-cp38-cp38-win_amd64.whl", hash = "sha256:0788c895c47fdcf375151ce78ff42336c01aca7bc43daecb8f8f8356cdc42b43"}, - {file = "cx_Freeze-6.9-cp39-cp39-win32.whl", hash = "sha256:a31f5ddbc80b29e297370d868791470b0e3e9062db45038c23293a76ed039018"}, - {file = "cx_Freeze-6.9-cp39-cp39-win_amd64.whl", hash = "sha256:30708f603076713c0a839cdfb34f4126d68e9d61afb3d9a59daa9cf252033872"}, - {file = "cx_Freeze-6.9.tar.gz", hash = "sha256:673aa3199af2ef87fc03a43a30e5d78b27ced2cedde925da89c55b5657da267b"}, +coverage = [ + {file = "coverage-6.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f50d3a822947572496ea922ee7825becd8e3ae6fbd2400cd8236b7d64b17f285"}, + {file = "coverage-6.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d5191d53afbe5b6059895fa7f58223d3751c42b8101fb3ce767e1a0b1a1d8f87"}, + {file = "coverage-6.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04010af3c06ce2bfeb3b1e4e05d136f88d88c25f76cd4faff5d1fd84d11581ea"}, + {file = "coverage-6.4.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6630d8d943644ea62132789940ca97d05fac83f73186eaf0930ffa715fbdab6b"}, + {file = "coverage-6.4.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05de0762c1caed4a162b3e305f36cf20a548ff4da0be6766ad5c870704be3660"}, + {file = "coverage-6.4.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e3a41aad5919613483aad9ebd53336905cab1bd6788afd3995c2a972d89d795"}, + {file = "coverage-6.4.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a2738ba1ee544d6f294278cfb6de2dc1f9a737a780469b5366e662a218f806c3"}, + {file = "coverage-6.4.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a0d2df4227f645a879010461df2cea6b7e3fb5a97d7eafa210f7fb60345af9e8"}, + {file = "coverage-6.4.3-cp310-cp310-win32.whl", hash = "sha256:73a10939dc345460ca0655356a470dd3de9759919186a82383c87b6eb315faf2"}, + {file = "coverage-6.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:53c8edd3b83a4ddba3d8c506f1359401e7770b30f2188f15c17a338adf5a14db"}, + {file = "coverage-6.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f1eda5cae434282712e40b42aaf590b773382afc3642786ac3ed39053973f61f"}, + {file = "coverage-6.4.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59fc88bc13e30f25167e807b8cad3c41b7218ef4473a20c86fd98a7968733083"}, + {file = "coverage-6.4.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d75314b00825d70e1e34b07396e23f47ed1d4feedc0122748f9f6bd31a544840"}, + {file = "coverage-6.4.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52f8b9fcf3c5e427d51bbab1fb92b575a9a9235d516f175b24712bcd4b5be917"}, + {file = "coverage-6.4.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5a559aab40c716de80c7212295d0dc96bc1b6c719371c20dd18c5187c3155518"}, + {file = "coverage-6.4.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:306788fd019bb90e9cbb83d3f3c6becad1c048dd432af24f8320cf38ac085684"}, + {file = "coverage-6.4.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:920a734fe3d311ca01883b4a19aa386c97b82b69fbc023458899cff0a0d621b9"}, + {file = "coverage-6.4.3-cp37-cp37m-win32.whl", hash = "sha256:ab9ef0187d6c62b09dec83a84a3b94f71f9690784c84fd762fb3cf2d2b44c914"}, + {file = "coverage-6.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:39ebd8e120cb77a06ee3d5fc26f9732670d1c397d7cd3acf02f6f62693b89b80"}, + {file = "coverage-6.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc698580216050b5f4a34d2cdd2838b429c53314f1c4835fab7338200a8396f2"}, + {file = "coverage-6.4.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:877ee5478fd78e100362aed56db47ccc5f23f6e7bb035a8896855f4c3e49bc9b"}, + {file = "coverage-6.4.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:555a498999c44f5287cc95500486cd0d4f021af9162982cbe504d4cb388f73b5"}, + {file = "coverage-6.4.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eff095a5aac7011fdb51a2c82a8fae9ec5211577f4b764e1e59cfa27ceeb1b59"}, + {file = "coverage-6.4.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5de1e9335e2569974e20df0ce31493d315a830d7987e71a24a2a335a8d8459d3"}, + {file = "coverage-6.4.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7856ea39059d75f822ff0df3a51ea6d76307c897048bdec3aad1377e4e9dca20"}, + {file = "coverage-6.4.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:411fdd9f4203afd93b056c0868c8f9e5e16813e765de962f27e4e5798356a052"}, + {file = "coverage-6.4.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cdf7b83f04a313a21afb1f8730fe4dd09577fefc53bbdfececf78b2006f4268e"}, + {file = "coverage-6.4.3-cp38-cp38-win32.whl", hash = "sha256:ab2b1a89d2bc7647622e9eaf06128a5b5451dccf7c242deaa31420b055716481"}, + {file = "coverage-6.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:0e34247274bde982bbc613894d33f9e36358179db2ed231dd101c48dd298e7b0"}, + {file = "coverage-6.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b104b6b1827d6a22483c469e3983a204bcf9c6bf7544bf90362c4654ebc2edf3"}, + {file = "coverage-6.4.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:adf1a0d272633b21d645dd6e02e3293429c1141c7d65a58e4cbcd592d53b8e01"}, + {file = "coverage-6.4.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff9832434a9193fbd716fbe05f9276484e18d26cc4cf850853594bb322807ac3"}, + {file = "coverage-6.4.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:923f9084d7e1d31b5f74c92396b05b18921ed01ee5350402b561a79dce3ea48d"}, + {file = "coverage-6.4.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d64304acf79766e650f7acb81d263a3ea6e2d0d04c5172b7189180ff2c023c"}, + {file = "coverage-6.4.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fc294de50941d3da66a09dca06e206297709332050973eca17040278cb0918ff"}, + {file = "coverage-6.4.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a42eaaae772f14a5194f181740a67bfd48e8806394b8c67aa4399e09d0d6b5db"}, + {file = "coverage-6.4.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4822327b35cb032ff16af3bec27f73985448f08e874146b5b101e0e558b613dd"}, + {file = "coverage-6.4.3-cp39-cp39-win32.whl", hash = "sha256:f217850ac0e046ede611312703423767ca032a7b952b5257efac963942c055de"}, + {file = "coverage-6.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:0a84376e4fd13cebce2c0ef8c2f037929c8307fb94af1e5dbe50272a1c651b5d"}, + {file = "coverage-6.4.3-pp36.pp37.pp38-none-any.whl", hash = "sha256:068d6f2a893af838291b8809c876973d885543411ea460f3e6886ac0ee941732"}, + {file = "coverage-6.4.3.tar.gz", hash = "sha256:ec2ae1f398e5aca655b7084392d23e80efb31f7a660d2eecf569fb9f79b3fb94"}, +] +cryptography = [ + {file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884"}, + {file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280"}, + {file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3"}, + {file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59"}, + {file = "cryptography-37.0.4-cp36-abi3-win32.whl", hash = "sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157"}, + {file = "cryptography-37.0.4-cp36-abi3-win_amd64.whl", hash = "sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327"}, + {file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b"}, + {file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9"}, + {file = "cryptography-37.0.4-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67"}, + {file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d"}, + {file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282"}, + {file = "cryptography-37.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa"}, + {file = "cryptography-37.0.4-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441"}, + {file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596"}, + {file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a"}, + {file = "cryptography-37.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab"}, + {file = "cryptography-37.0.4.tar.gz", hash = "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82"}, +] +cx-Freeze = [ + {file = "cx_Freeze-6.11.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e372b9e72ac0e2207ee65a9d404e2669da1134dc37f5ace9a2a779099d3aa868"}, + {file = "cx_Freeze-6.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd293382e1ad270dddf5a2707db5dbb8600a1e0b0c9b0da7af9d61326eb1b325"}, + {file = "cx_Freeze-6.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:feec2f36bce042da6a0d92690bc592b0dcec29218adc2278535cd13b28ec3485"}, + {file = "cx_Freeze-6.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5aafcc6337856d5921b20f41acdcc8d0fe770388f3a072eb25163f8825f6c5d"}, + {file = "cx_Freeze-6.11.1-cp310-cp310-win32.whl", hash = "sha256:b99cc0b6d6c1ba51bd9fe11dbfae9aabcf089ba779ea86d83d280e2e40f484e7"}, + {file = "cx_Freeze-6.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:f0dfe6acf25eb096faba7d4b4b001bcd0f818e372ea1f05d900665b0ad82b0b9"}, + {file = "cx_Freeze-6.11.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3a68e70dcb27b0720b131a35c5fdd096012fe00119a8e51d935f3fb3cd251c39"}, + {file = "cx_Freeze-6.11.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f7bde925042d8843af9b6242a1bf3865dbbae088f3183a89a575124ec2e14a4"}, + {file = "cx_Freeze-6.11.1-cp36-cp36m-win32.whl", hash = "sha256:7698fb82b6f84b3426774b5f3bee770601f26b612306319664a02f1ec5160861"}, + {file = "cx_Freeze-6.11.1-cp36-cp36m-win_amd64.whl", hash = "sha256:9848c975401b21a98aa896baabfed067c3e981afd5b5b0a8a5eabe5c9f23d3c5"}, + {file = "cx_Freeze-6.11.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:87dcf5ceb78dc6af910c45238128fda2394b7c430d3fa469e87e1efdeeb5d4cc"}, + {file = "cx_Freeze-6.11.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb74d8cc1f8c658986acc19ea6875b985a979421f9bb9c310b43cd2ff5d90c44"}, + {file = "cx_Freeze-6.11.1-cp37-cp37m-win32.whl", hash = "sha256:971c0a8356ef0ee09a3097f9c9d5b52cde6d08d1ef80e997eb4a6e22fe0eff2f"}, + {file = "cx_Freeze-6.11.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7c1cb44379b2093cbdde77e302a376f29aa61999c73be6e8a559463db84b85c4"}, + {file = "cx_Freeze-6.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc90d6dbde66e8ddfe6b26f63fb2ea7d6d0e4568205f40660a63b8b200dcabcf"}, + {file = "cx_Freeze-6.11.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f56f618a23d86bdcfff22b29ec993117effd32a401060013105517301c0bf32"}, + {file = "cx_Freeze-6.11.1-cp38-cp38-win32.whl", hash = "sha256:4edfb5d65afb11eb9f0326d40d15445366481585705b3096f2cd090e30a36247"}, + {file = "cx_Freeze-6.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:cfb5a8032bf424c04814c9426425fa1db4cf8c280da948969eead9f616c0fd92"}, + {file = "cx_Freeze-6.11.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0a3e32980269cfabc2e814978bfdf4382fe3cbc9ac64f9f1bdb1cd2ddf3a40d0"}, + {file = "cx_Freeze-6.11.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:798bb7ca037c3c885efd3eda6756c84c7927c712b730b22a7f256440faa36d38"}, + {file = "cx_Freeze-6.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5aa1759098ca4853200a79138b626a9caa2ccf829d662b28c82ec7e71ea97cde"}, + {file = "cx_Freeze-6.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7962680ae04ee3afda1012026b5394a534e2526b68681d591158b7d8bc733bcf"}, + {file = "cx_Freeze-6.11.1-cp39-cp39-win32.whl", hash = "sha256:da4f82fe27e71571c0ab9d700b5e6c6c631ae39133d9b6d7157939f1e9f37312"}, + {file = "cx_Freeze-6.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:aaf399b6ed5d54b7271980ae354605620bedcd52d722f57ad527bd989c56a875"}, + {file = "cx_Freeze-6.11.1.tar.gz", hash = "sha256:8f3a30c9e3394f290655e346d3b460910656b30ac6347a87499bb5ad365c6e7c"}, ] cx-logging = [ {file = "cx_Logging-3.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:9fcd297e5c51470521c47eff0f86ba844aeca6be97e13c3e2114ebdf03fa3c96"}, @@ -1910,8 +2142,14 @@ cx-logging = [ {file = "cx_Logging-3.0-cp39-cp39-win_amd64.whl", hash = "sha256:302e9c4f65a936c288a4fa59a90e7e142d9ef994aa29676731acafdcccdbb3f5"}, {file = "cx_Logging-3.0.tar.gz", hash = "sha256:ba8a7465facf7b98d8f494030fb481a2e8aeee29dc191e10383bb54ed42bdb34"}, ] -deprecated = [] -dill = [] +deprecated = [ + {file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"}, + {file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"}, +] +dill = [ + {file = "dill-0.3.5.1-py2.py3-none-any.whl", hash = "sha256:33501d03270bbe410c72639b350e941882a8b0fd55357580fbc873fba0c59302"}, + {file = "dill-0.3.5.1.tar.gz", hash = "sha256:d75e41f3eff1eee599d738e76ba8f4ad98ea229db8b085318aa2b3333a208c86"}, +] dnspython = [ {file = "dnspython-2.2.1-py3-none-any.whl", hash = "sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f"}, {file = "dnspython-2.2.1.tar.gz", hash = "sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e"}, @@ -1920,22 +2158,93 @@ docutils = [ {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, ] -dropbox = [] +dropbox = [ + {file = "dropbox-11.33.0-py2-none-any.whl", hash = "sha256:3ee9024631b80f18938556d5e27cbdede26d6dc0b73aeaa90fc075ce96c950b1"}, + {file = "dropbox-11.33.0-py3-none-any.whl", hash = "sha256:1a0cbc22b0d1dae96e18b37e3520e5c289de7eb1303935db40e4dbfc9bb9e59b"}, + {file = "dropbox-11.33.0.tar.gz", hash = "sha256:7c638b521169a460de38b9eaeb204fe918874f72d6c3eed005d064b6f37da9c1"}, +] enlighten = [ {file = "enlighten-1.10.2-py2.py3-none-any.whl", hash = "sha256:b237fe562b320bf9f1d4bb76d0c98e0daf914372a76ab87c35cd02f57aa9d8c1"}, {file = "enlighten-1.10.2.tar.gz", hash = "sha256:7a5b83cd0f4d095e59d80c648ebb5f7ffca0cd8bcf7ae6639828ee1ad000632a"}, ] -evdev = [] +evdev = [ + {file = "evdev-1.6.0.tar.gz", hash = "sha256:ecfa01b5c84f7e8c6ced3367ac95288f43cd84efbfd7dd7d0cdbfc0d18c87a6a"}, +] flake8 = [ {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, ] -frozenlist = [] -ftrack-python-api = [] +frozenlist = [ + {file = "frozenlist-1.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5f271c93f001748fc26ddea409241312a75e13466b06c94798d1a341cf0e6989"}, + {file = "frozenlist-1.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9c6ef8014b842f01f5d2b55315f1af5cbfde284eb184075c189fd657c2fd8204"}, + {file = "frozenlist-1.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:219a9676e2eae91cb5cc695a78b4cb43d8123e4160441d2b6ce8d2c70c60e2f3"}, + {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b47d64cdd973aede3dd71a9364742c542587db214e63b7529fbb487ed67cddd9"}, + {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2af6f7a4e93f5d08ee3f9152bce41a6015b5cf87546cb63872cc19b45476e98a"}, + {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a718b427ff781c4f4e975525edb092ee2cdef6a9e7bc49e15063b088961806f8"}, + {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c56c299602c70bc1bb5d1e75f7d8c007ca40c9d7aebaf6e4ba52925d88ef826d"}, + {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:717470bfafbb9d9be624da7780c4296aa7935294bd43a075139c3d55659038ca"}, + {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:31b44f1feb3630146cffe56344704b730c33e042ffc78d21f2125a6a91168131"}, + {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c3b31180b82c519b8926e629bf9f19952c743e089c41380ddca5db556817b221"}, + {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:d82bed73544e91fb081ab93e3725e45dd8515c675c0e9926b4e1f420a93a6ab9"}, + {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49459f193324fbd6413e8e03bd65789e5198a9fa3095e03f3620dee2f2dabff2"}, + {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:94e680aeedc7fd3b892b6fa8395b7b7cc4b344046c065ed4e7a1e390084e8cb5"}, + {file = "frozenlist-1.3.1-cp310-cp310-win32.whl", hash = "sha256:fabb953ab913dadc1ff9dcc3a7a7d3dc6a92efab3a0373989b8063347f8705be"}, + {file = "frozenlist-1.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:eee0c5ecb58296580fc495ac99b003f64f82a74f9576a244d04978a7e97166db"}, + {file = "frozenlist-1.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0bc75692fb3770cf2b5856a6c2c9de967ca744863c5e89595df64e252e4b3944"}, + {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086ca1ac0a40e722d6833d4ce74f5bf1aba2c77cbfdc0cd83722ffea6da52a04"}, + {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b51eb355e7f813bcda00276b0114c4172872dc5fb30e3fea059b9367c18fbcb"}, + {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:74140933d45271c1a1283f708c35187f94e1256079b3c43f0c2267f9db5845ff"}, + {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee4c5120ddf7d4dd1eaf079af3af7102b56d919fa13ad55600a4e0ebe532779b"}, + {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97d9e00f3ac7c18e685320601f91468ec06c58acc185d18bb8e511f196c8d4b2"}, + {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6e19add867cebfb249b4e7beac382d33215d6d54476bb6be46b01f8cafb4878b"}, + {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a027f8f723d07c3f21963caa7d585dcc9b089335565dabe9c814b5f70c52705a"}, + {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:61d7857950a3139bce035ad0b0945f839532987dfb4c06cfe160254f4d19df03"}, + {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:53b2b45052e7149ee8b96067793db8ecc1ae1111f2f96fe1f88ea5ad5fd92d10"}, + {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bbb1a71b1784e68870800b1bc9f3313918edc63dbb8f29fbd2e767ce5821696c"}, + {file = "frozenlist-1.3.1-cp37-cp37m-win32.whl", hash = "sha256:ab6fa8c7871877810e1b4e9392c187a60611fbf0226a9e0b11b7b92f5ac72792"}, + {file = "frozenlist-1.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f89139662cc4e65a4813f4babb9ca9544e42bddb823d2ec434e18dad582543bc"}, + {file = "frozenlist-1.3.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:4c0c99e31491a1d92cde8648f2e7ccad0e9abb181f6ac3ddb9fc48b63301808e"}, + {file = "frozenlist-1.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:61e8cb51fba9f1f33887e22488bad1e28dd8325b72425f04517a4d285a04c519"}, + {file = "frozenlist-1.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc2f3e368ee5242a2cbe28323a866656006382872c40869b49b265add546703f"}, + {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58fb94a01414cddcdc6839807db77ae8057d02ddafc94a42faee6004e46c9ba8"}, + {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:022178b277cb9277d7d3b3f2762d294f15e85cd2534047e68a118c2bb0058f3e"}, + {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:572ce381e9fe027ad5e055f143763637dcbac2542cfe27f1d688846baeef5170"}, + {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19127f8dcbc157ccb14c30e6f00392f372ddb64a6ffa7106b26ff2196477ee9f"}, + {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42719a8bd3792744c9b523674b752091a7962d0d2d117f0b417a3eba97d1164b"}, + {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2743bb63095ef306041c8f8ea22bd6e4d91adabf41887b1ad7886c4c1eb43d5f"}, + {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:fa47319a10e0a076709644a0efbcaab9e91902c8bd8ef74c6adb19d320f69b83"}, + {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52137f0aea43e1993264a5180c467a08a3e372ca9d378244c2d86133f948b26b"}, + {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:f5abc8b4d0c5b556ed8cd41490b606fe99293175a82b98e652c3f2711b452988"}, + {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1e1cf7bc8cbbe6ce3881863671bac258b7d6bfc3706c600008925fb799a256e2"}, + {file = "frozenlist-1.3.1-cp38-cp38-win32.whl", hash = "sha256:0dde791b9b97f189874d654c55c24bf7b6782343e14909c84beebd28b7217845"}, + {file = "frozenlist-1.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:9494122bf39da6422b0972c4579e248867b6b1b50c9b05df7e04a3f30b9a413d"}, + {file = "frozenlist-1.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:31bf9539284f39ff9398deabf5561c2b0da5bb475590b4e13dd8b268d7a3c5c1"}, + {file = "frozenlist-1.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e0c8c803f2f8db7217898d11657cb6042b9b0553a997c4a0601f48a691480fab"}, + {file = "frozenlist-1.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da5ba7b59d954f1f214d352308d1d86994d713b13edd4b24a556bcc43d2ddbc3"}, + {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74e6b2b456f21fc93ce1aff2b9728049f1464428ee2c9752a4b4f61e98c4db96"}, + {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526d5f20e954d103b1d47232e3839f3453c02077b74203e43407b962ab131e7b"}, + {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b499c6abe62a7a8d023e2c4b2834fce78a6115856ae95522f2f974139814538c"}, + {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab386503f53bbbc64d1ad4b6865bf001414930841a870fc97f1546d4d133f141"}, + {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f63c308f82a7954bf8263a6e6de0adc67c48a8b484fab18ff87f349af356efd"}, + {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:12607804084d2244a7bd4685c9d0dca5df17a6a926d4f1967aa7978b1028f89f"}, + {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:da1cdfa96425cbe51f8afa43e392366ed0b36ce398f08b60de6b97e3ed4affef"}, + {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f810e764617b0748b49a731ffaa525d9bb36ff38332411704c2400125af859a6"}, + {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:35c3d79b81908579beb1fb4e7fcd802b7b4921f1b66055af2578ff7734711cfa"}, + {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c92deb5d9acce226a501b77307b3b60b264ca21862bd7d3e0c1f3594022f01bc"}, + {file = "frozenlist-1.3.1-cp39-cp39-win32.whl", hash = "sha256:5e77a8bd41e54b05e4fb2708dc6ce28ee70325f8c6f50f3df86a44ecb1d7a19b"}, + {file = "frozenlist-1.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:625d8472c67f2d96f9a4302a947f92a7adbc1e20bedb6aff8dbc8ff039ca6189"}, + {file = "frozenlist-1.3.1.tar.gz", hash = "sha256:3a735e4211a04ccfa3f4833547acdf5d2f863bfeb01cfd3edaffbc251f15cec8"}, +] +ftrack-python-api = [ + {file = "ftrack-python-api-2.3.3.tar.gz", hash = "sha256:358f37e5b1c5635eab107c19e27a0c890d512877f78af35b1ac416e90c037295"}, + {file = "ftrack_python_api-2.3.3-py2.py3-none-any.whl", hash = "sha256:82834c4d5def5557a2ea547a7e6f6ba84d3129e8f90457d8bbd85b287a2c39f6"}, +] future = [ {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, ] -gazu = [] +gazu = [ + {file = "gazu-0.8.30-py2.py3-none-any.whl", hash = "sha256:d692927a11314151bc33e7d67edee634053f70a3b09e4500dfc6626bfea18753"}, +] gitdb = [ {file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"}, {file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"}, @@ -1944,24 +2253,42 @@ gitpython = [ {file = "GitPython-3.1.27-py3-none-any.whl", hash = "sha256:5b68b000463593e05ff2b261acff0ff0972df8ab1b70d3cdbd41b546c8b8fc3d"}, {file = "GitPython-3.1.27.tar.gz", hash = "sha256:1c885ce809e8ba2d88a29befeb385fcea06338d3640712b59ca623c220bb5704"}, ] -google-api-core = [] +google-api-core = [ + {file = "google-api-core-2.8.2.tar.gz", hash = "sha256:06f7244c640322b508b125903bb5701bebabce8832f85aba9335ec00b3d02edc"}, + {file = "google_api_core-2.8.2-py3-none-any.whl", hash = "sha256:93c6a91ccac79079ac6bbf8b74ee75db970cc899278b97d53bc012f35908cf50"}, +] google-api-python-client = [ {file = "google-api-python-client-1.12.11.tar.gz", hash = "sha256:1b4bd42a46321e13c0542a9e4d96fa05d73626f07b39f83a73a947d70ca706a9"}, {file = "google_api_python_client-1.12.11-py2.py3-none-any.whl", hash = "sha256:7e0a1a265c8d3088ee1987778c72683fcb376e32bada8d7767162bd9c503fd9b"}, ] -google-auth = [] +google-auth = [ + {file = "google-auth-2.10.0.tar.gz", hash = "sha256:7904dbd44b745c7323fef29565adee2fe7ff48473e2d94443aced40b0404a395"}, + {file = "google_auth-2.10.0-py2.py3-none-any.whl", hash = "sha256:1deba4a54f95ef67b4139eaf5c20eaa7047215eec9f6a2344599b8596db8863b"}, +] google-auth-httplib2 = [ {file = "google-auth-httplib2-0.1.0.tar.gz", hash = "sha256:a07c39fd632becacd3f07718dfd6021bf396978f03ad3ce4321d060015cc30ac"}, {file = "google_auth_httplib2-0.1.0-py2.py3-none-any.whl", hash = "sha256:31e49c36c6b5643b57e82617cb3e021e3e1d2df9da63af67252c02fa9c1f4a10"}, ] -googleapis-common-protos = [] +googleapis-common-protos = [ + {file = "googleapis-common-protos-1.56.4.tar.gz", hash = "sha256:c25873c47279387cfdcbdafa36149887901d36202cb645a0e4f29686bf6e4417"}, + {file = "googleapis_common_protos-1.56.4-py2.py3-none-any.whl", hash = "sha256:8eb2cbc91b69feaf23e32452a7ae60e791e09967d81d4fcc7fc388182d1bd394"}, +] httplib2 = [ {file = "httplib2-0.20.4-py3-none-any.whl", hash = "sha256:8b6a905cb1c79eefd03f8669fd993c36dc341f7c558f056cb5a33b5c2f458543"}, {file = "httplib2-0.20.4.tar.gz", hash = "sha256:58a98e45b4b1a48273073f905d2961666ecf0fbac4250ea5b47aef259eb5c585"}, ] -idna = [] -imagesize = [] -importlib-metadata = [] +idna = [ + {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, + {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, +] +imagesize = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] +importlib-metadata = [ + {file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"}, + {file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"}, +] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, @@ -1974,12 +2301,18 @@ jedi = [ {file = "jedi-0.13.3-py2.py3-none-any.whl", hash = "sha256:2c6bcd9545c7d6440951b12b44d373479bf18123a401a52025cf98563fbd826c"}, {file = "jedi-0.13.3.tar.gz", hash = "sha256:2bb0603e3506f708e792c7f4ad8fc2a7a9d9c2d292a358fbbd58da531695595b"}, ] -jeepney = [] +jeepney = [ + {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, + {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, +] jinja2 = [ {file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"}, {file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"}, ] -jinxed = [] +jinxed = [ + {file = "jinxed-1.2.0-py2.py3-none-any.whl", hash = "sha256:cfc2b2e4e3b4326954d546ba6d6b9a7a796ddcb0aef8d03161d005177eb0d48b"}, + {file = "jinxed-1.2.0.tar.gz", hash = "sha256:032acda92d5c57cd216033cbbd53de731e6ed50deb63eb4781336ca55f72cda5"}, +] jsonschema = [ {file = "jsonschema-2.6.0-py2.py3-none-any.whl", hash = "sha256:000e68abd33c972a5248544925a0cae7d1125f9bf6c58280d37546b946769a08"}, {file = "jsonschema-2.6.0.tar.gz", hash = "sha256:6ff5f3180870836cae40f06fa10419f557208175f13ad7bc26caa77beb1f6e02"}, @@ -2027,16 +2360,58 @@ lazy-object-proxy = [ {file = "lazy_object_proxy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61"}, {file = "lazy_object_proxy-1.7.1-pp37.pp38-none-any.whl", hash = "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84"}, ] +lief = [ + {file = "lief-0.12.1-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:4fbbc9d520de87ac22210c62d22a9b088e5460f9a028741311e6f68ef8877ddd"}, + {file = "lief-0.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:443e4494df448ea1a021976258c7a6aca27d81b0612783fa3a84fab196fb9fcb"}, + {file = "lief-0.12.1-cp310-cp310-win32.whl", hash = "sha256:1c4019dddf03a5185462fb5ea04327cee08d40f46777b02f0773c7dc294552ea"}, + {file = "lief-0.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:d7e09968f99ddf1e3983d3bcc16c62d1b6635a345fee8d8139f82b31bad457d6"}, + {file = "lief-0.12.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:9fa6269ec4fa3f874b807fbba3c48a46af30df2497723f6966080e3eb630cb26"}, + {file = "lief-0.12.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a78b05cac5fa491e01e1819573bbbbcaea0a4229f4aa3a2edb231b5695ddaf2d"}, + {file = "lief-0.12.1-cp36-cp36m-win32.whl", hash = "sha256:f1292bff96579c18e01e20b7a14043052379fe6e9a476c1d6d88aca43e5f9ac7"}, + {file = "lief-0.12.1-cp36-cp36m-win_amd64.whl", hash = "sha256:dab63876113bd573d64ce043f50153f6e2810e5e78256397aa0fe1fedf82ab84"}, + {file = "lief-0.12.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:5771f5226b62c885a7aa30c1b98040d39229a1dab889d03155e5538e57d0054b"}, + {file = "lief-0.12.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:8ec307a762505076a6d31566225a231c44ec7063c0e7d751ac4654c674454c47"}, + {file = "lief-0.12.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a755f6088d3b2041e4402adf917ac87e5ad9d1c5278973f48a29a5631fe393eb"}, + {file = "lief-0.12.1-cp37-cp37m-win32.whl", hash = "sha256:5d746f7eb6d3bf35a0230c7184aaaf434cb1ea89d7e7c8e8fe14a49cf2bb17a0"}, + {file = "lief-0.12.1-cp37-cp37m-win_amd64.whl", hash = "sha256:2d3ab7212da696bcbe5ca9dd78ceaa32dfb8a0e85e18001793b4441ef4624561"}, + {file = "lief-0.12.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:4360b0acd525ba77777cc38f0e5128c90c93cc4e91ab566ef3aa45b7f8a8c57e"}, + {file = "lief-0.12.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:5e82e466d36cbabb28cc1a787b554d2feae5ab55c39cab58ef64fb6513bad92a"}, + {file = "lief-0.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efa0022a3bf70ef46335639e61b946cc2d9cf012d60e263c215e3e64b1ce38b4"}, + {file = "lief-0.12.1-cp38-cp38-win32.whl", hash = "sha256:d29f91d9f64f67d3ada5b7e0e48ab084d825fb4601d32d9fecdd2bdf23cdad23"}, + {file = "lief-0.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:7dea6b3f17d362f93165379c46dadb012c73b1f751c8ceac256e5f43842cd86d"}, + {file = "lief-0.12.1-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:44012da4c32c670a97bb8a055a4ff16168cfaa757d03986f319aa3329a43e343"}, + {file = "lief-0.12.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:e1d23997b0a71d34e766ff183be07854c6f698fd3d6aa44bf30b6b7f4f77ef55"}, + {file = "lief-0.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b845eca79c772041efb38b50cfaf951e24bc047ec462450b7e54e75b7e2bee0d"}, + {file = "lief-0.12.1-cp39-cp39-win32.whl", hash = "sha256:0df84ac2df20b14db12e69442d39b0e8cd89428ba3b131995e0570bcd3725460"}, + {file = "lief-0.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:960a2da9f28c8d5dba753bb9ab77e26b3c6ff9b9658918be95650ceb8ee91e68"}, + {file = "lief-0.12.1.zip", hash = "sha256:4ff4ccfae2e1ee4ccba2b5556027dbb56282b8a973c5835c5b597e8b7b664416"}, +] log4mongo = [ {file = "log4mongo-1.7.0.tar.gz", hash = "sha256:dc374617206162a0b14167fbb5feac01dbef587539a235dadba6200362984a68"}, ] markupsafe = [ + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, @@ -2045,14 +2420,27 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, @@ -2062,6 +2450,12 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, @@ -2131,22 +2525,109 @@ multidict = [ {file = "multidict-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae"}, {file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"}, ] -opentimelineio = [] +opentimelineio = [ + {file = "OpenTimelineIO-0.14.0.dev1-cp310-cp310-linux_x86_64.whl", hash = "sha256:112c27ad419a79c88cd4ebac96278ab8f446fda4c6e1a70c871f2b24b3d003ef"}, + {file = "OpenTimelineIO-0.14.0.dev1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e2416ca76805a07ecbcdda65e5a31ce447e04e2db7082d72582740cbd8a16d7"}, + {file = "OpenTimelineIO-0.14.0.dev1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8806dc240496b72e16a0fddacb0b2e825d19656d80689098e6c5bd6a805bc84"}, + {file = "OpenTimelineIO-0.14.0.dev1-cp36-cp36m-win_amd64.whl", hash = "sha256:0aa54488ca50b53ac247610cef23fb63619dd1993016c0cd4069e54526d5905c"}, + {file = "OpenTimelineIO-0.14.0.dev1-cp37-cp37m-linux_x86_64.whl", hash = "sha256:8b11287eb733ad1c7fc53d4af3e3f926c396add6c3a3c1417b9c2b001f7ef4ba"}, + {file = "OpenTimelineIO-0.14.0.dev1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:2cba2ce567fc06f042365393dbe8e99dc4c1361999fb7ddc03d4b8b0d5ddb894"}, + {file = "OpenTimelineIO-0.14.0.dev1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:3d96da307c1969c309974a6734c7f3e39925236c845c8289f25d4d7d00be3f0c"}, + {file = "OpenTimelineIO-0.14.0.dev1-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:968cb0c5e6e7d697037b1cd4f7707521995a32fc51664139ed15004b93ab8106"}, + {file = "OpenTimelineIO-0.14.0.dev1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dae64aa418193ca094854a55efa8bcc5a5c2855b3679509987e8b48610d31"}, + {file = "OpenTimelineIO-0.14.0.dev1-cp37-cp37m-win_amd64.whl", hash = "sha256:85dfae42f5a992ef85d0015f33f999751d846a484ef907e8834407d545a7ee6a"}, + {file = "OpenTimelineIO-0.14.0.dev1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aaac346cb758de719f88ac175d3948409ded39c5eed0844068402f70f3e90b6"}, + {file = "OpenTimelineIO-0.14.0.dev1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a09f6dd199218cf69faf42a8b21f5be2cd01764e57dbcc8456b3ced564eb110f"}, + {file = "OpenTimelineIO-0.14.0.dev1.tar.gz", hash = "sha256:d29eafd5188c3ad6c7b6d2095bf69984d590c331b701d3bbc644d7abd5f08606"}, +] packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] -paramiko = [] +paramiko = [ + {file = "paramiko-2.11.0-py2.py3-none-any.whl", hash = "sha256:655f25dc8baf763277b933dfcea101d636581df8d6b9774d1fb653426b72c270"}, + {file = "paramiko-2.11.0.tar.gz", hash = "sha256:003e6bee7c034c21fbb051bf83dc0a9ee4106204dd3c53054c71452cc4ec3938"}, +] parso = [ {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, ] +patchelf = [ + {file = "patchelf-0.15.0.0-py2.py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:08e5e30a9415a8628de47726fbf15bfcd89be35df51c8a0a12372aebd0c5b4f6"}, + {file = "patchelf-0.15.0.0-py2.py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:4ce9d08119816bc4316c8ecc5f33da42384934fc0fc9cfbdded53a4930705466"}, + {file = "patchelf-0.15.0.0-py2.py3-none-manylinux_2_17_s390x.manylinux2014_s390x.musllinux_1_1_s390x.whl", hash = "sha256:ae19b0f91aabc9af2608a4ca0395533f1df9122e6abc11ef2c8db6e4db0f98c2"}, + {file = "patchelf-0.15.0.0-py2.py3-none-manylinux_2_5_i686.manylinux1_i686.musllinux_1_1_i686.whl", hash = "sha256:f3f87aee44d1d1b2209e38c4227b0316bb03538df68d20b3d96205aa87868d95"}, + {file = "patchelf-0.15.0.0-py2.py3-none-manylinux_2_5_x86_64.manylinux1_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:52e48c08110f2988a9761a5a383f7ae35b1e8e06a140e320d18386d3510697ed"}, + {file = "patchelf-0.15.0.0.tar.gz", hash = "sha256:0f8dcf0df0ba919ce37e8aef67a08bde5326897098451df94ab3a5eedc9e08d9"}, +] pathlib2 = [ {file = "pathlib2-2.3.7.post1-py2.py3-none-any.whl", hash = "sha256:5266a0fd000452f1b3467d782f079a4343c63aaa119221fbdc4e39577489ca5b"}, {file = "pathlib2-2.3.7.post1.tar.gz", hash = "sha256:9fe0edad898b83c0c3e199c842b27ed216645d2e177757b2dd67384d4113c641"}, ] -pillow = [] -platformdirs = [] +pillow = [ + {file = "Pillow-9.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:a9c9bc489f8ab30906d7a85afac4b4944a572a7432e00698a7239f44a44e6efb"}, + {file = "Pillow-9.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:510cef4a3f401c246cfd8227b300828715dd055463cdca6176c2e4036df8bd4f"}, + {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7888310f6214f19ab2b6df90f3f06afa3df7ef7355fc025e78a3044737fab1f5"}, + {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831e648102c82f152e14c1a0938689dbb22480c548c8d4b8b248b3e50967b88c"}, + {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cc1d2451e8a3b4bfdb9caf745b58e6c7a77d2e469159b0d527a4554d73694d1"}, + {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:136659638f61a251e8ed3b331fc6ccd124590eeff539de57c5f80ef3a9594e58"}, + {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:6e8c66f70fb539301e064f6478d7453e820d8a2c631da948a23384865cd95544"}, + {file = "Pillow-9.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:37ff6b522a26d0538b753f0b4e8e164fdada12db6c6f00f62145d732d8a3152e"}, + {file = "Pillow-9.2.0-cp310-cp310-win32.whl", hash = "sha256:c79698d4cd9318d9481d89a77e2d3fcaeff5486be641e60a4b49f3d2ecca4e28"}, + {file = "Pillow-9.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:254164c57bab4b459f14c64e93df11eff5ded575192c294a0c49270f22c5d93d"}, + {file = "Pillow-9.2.0-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:408673ed75594933714482501fe97e055a42996087eeca7e5d06e33218d05aa8"}, + {file = "Pillow-9.2.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:727dd1389bc5cb9827cbd1f9d40d2c2a1a0c9b32dd2261db522d22a604a6eec9"}, + {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50dff9cc21826d2977ef2d2a205504034e3a4563ca6f5db739b0d1026658e004"}, + {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb6259196a589123d755380b65127ddc60f4c64b21fc3bb46ce3a6ea663659b0"}, + {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b0554af24df2bf96618dac71ddada02420f946be943b181108cac55a7a2dcd4"}, + {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:15928f824870535c85dbf949c09d6ae7d3d6ac2d6efec80f3227f73eefba741c"}, + {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:bdd0de2d64688ecae88dd8935012c4a72681e5df632af903a1dca8c5e7aa871a"}, + {file = "Pillow-9.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5b87da55a08acb586bad5c3aa3b86505f559b84f39035b233d5bf844b0834b1"}, + {file = "Pillow-9.2.0-cp311-cp311-win32.whl", hash = "sha256:b6d5e92df2b77665e07ddb2e4dbd6d644b78e4c0d2e9272a852627cdba0d75cf"}, + {file = "Pillow-9.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6bf088c1ce160f50ea40764f825ec9b72ed9da25346216b91361eef8ad1b8f8c"}, + {file = "Pillow-9.2.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:2c58b24e3a63efd22554c676d81b0e57f80e0a7d3a5874a7e14ce90ec40d3069"}, + {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eef7592281f7c174d3d6cbfbb7ee5984a671fcd77e3fc78e973d492e9bf0eb3f"}, + {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dcd7b9c7139dc8258d164b55696ecd16c04607f1cc33ba7af86613881ffe4ac8"}, + {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a138441e95562b3c078746a22f8fca8ff1c22c014f856278bdbdd89ca36cff1b"}, + {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:93689632949aff41199090eff5474f3990b6823404e45d66a5d44304e9cdc467"}, + {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:f3fac744f9b540148fa7715a435d2283b71f68bfb6d4aae24482a890aed18b59"}, + {file = "Pillow-9.2.0-cp37-cp37m-win32.whl", hash = "sha256:fa768eff5f9f958270b081bb33581b4b569faabf8774726b283edb06617101dc"}, + {file = "Pillow-9.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:69bd1a15d7ba3694631e00df8de65a8cb031911ca11f44929c97fe05eb9b6c1d"}, + {file = "Pillow-9.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:030e3460861488e249731c3e7ab59b07c7853838ff3b8e16aac9561bb345da14"}, + {file = "Pillow-9.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:74a04183e6e64930b667d321524e3c5361094bb4af9083db5c301db64cd341f3"}, + {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d33a11f601213dcd5718109c09a52c2a1c893e7461f0be2d6febc2879ec2402"}, + {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fd6f5e3c0e4697fa7eb45b6e93996299f3feee73a3175fa451f49a74d092b9f"}, + {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a647c0d4478b995c5e54615a2e5360ccedd2f85e70ab57fbe817ca613d5e63b8"}, + {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:4134d3f1ba5f15027ff5c04296f13328fecd46921424084516bdb1b2548e66ff"}, + {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:bc431b065722a5ad1dfb4df354fb9333b7a582a5ee39a90e6ffff688d72f27a1"}, + {file = "Pillow-9.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1536ad017a9f789430fb6b8be8bf99d2f214c76502becc196c6f2d9a75b01b76"}, + {file = "Pillow-9.2.0-cp38-cp38-win32.whl", hash = "sha256:2ad0d4df0f5ef2247e27fc790d5c9b5a0af8ade9ba340db4a73bb1a4a3e5fb4f"}, + {file = "Pillow-9.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:ec52c351b35ca269cb1f8069d610fc45c5bd38c3e91f9ab4cbbf0aebc136d9c8"}, + {file = "Pillow-9.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ed2c4ef2451de908c90436d6e8092e13a43992f1860275b4d8082667fbb2ffc"}, + {file = "Pillow-9.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ad2f835e0ad81d1689f1b7e3fbac7b01bb8777d5a985c8962bedee0cc6d43da"}, + {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea98f633d45f7e815db648fd7ff0f19e328302ac36427343e4432c84432e7ff4"}, + {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7761afe0126d046974a01e030ae7529ed0ca6a196de3ec6937c11df0df1bc91c"}, + {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a54614049a18a2d6fe156e68e188da02a046a4a93cf24f373bffd977e943421"}, + {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:5aed7dde98403cd91d86a1115c78d8145c83078e864c1de1064f52e6feb61b20"}, + {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:13b725463f32df1bfeacbf3dd197fb358ae8ebcd8c5548faa75126ea425ccb60"}, + {file = "Pillow-9.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:808add66ea764ed97d44dda1ac4f2cfec4c1867d9efb16a33d158be79f32b8a4"}, + {file = "Pillow-9.2.0-cp39-cp39-win32.whl", hash = "sha256:337a74fd2f291c607d220c793a8135273c4c2ab001b03e601c36766005f36885"}, + {file = "Pillow-9.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:fac2d65901fb0fdf20363fbd345c01958a742f2dc62a8dd4495af66e3ff502a4"}, + {file = "Pillow-9.2.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ad2277b185ebce47a63f4dc6302e30f05762b688f8dc3de55dbae4651872cdf3"}, + {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c7b502bc34f6e32ba022b4a209638f9e097d7a9098104ae420eb8186217ebbb"}, + {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d1f14f5f691f55e1b47f824ca4fdcb4b19b4323fe43cc7bb105988cad7496be"}, + {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:dfe4c1fedfde4e2fbc009d5ad420647f7730d719786388b7de0999bf32c0d9fd"}, + {file = "Pillow-9.2.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:f07f1f00e22b231dd3d9b9208692042e29792d6bd4f6639415d2f23158a80013"}, + {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1802f34298f5ba11d55e5bb09c31997dc0c6aed919658dfdf0198a2fe75d5490"}, + {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17d4cafe22f050b46d983b71c707162d63d796a1235cdf8b9d7a112e97b15bac"}, + {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:96b5e6874431df16aee0c1ba237574cb6dff1dcb173798faa6a9d8b399a05d0e"}, + {file = "Pillow-9.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:0030fdbd926fb85844b8b92e2f9449ba89607231d3dd597a21ae72dc7fe26927"}, + {file = "Pillow-9.2.0.tar.gz", hash = "sha256:75e636fd3e0fb872693f23ccb8a5ff2cd578801251f3a4f6854c6a5d437d3c04"}, +] +platformdirs = [ + {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, + {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, +] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, @@ -2159,7 +2640,22 @@ prefixed = [ {file = "prefixed-0.3.2-py2.py3-none-any.whl", hash = "sha256:5e107306462d63f2f03c529dbf11b0026fdfec621a9a008ca639d71de22995c3"}, {file = "prefixed-0.3.2.tar.gz", hash = "sha256:ca48277ba5fa8346dd4b760847da930c7b84416387c39e93affef086add2c029"}, ] -protobuf = [] +protobuf = [ + {file = "protobuf-4.21.5-cp310-abi3-win32.whl", hash = "sha256:5310cbe761e87f0c1decce019d23f2101521d4dfff46034f8a12a53546036ec7"}, + {file = "protobuf-4.21.5-cp310-abi3-win_amd64.whl", hash = "sha256:e5c5a2886ae48d22a9d32fbb9b6636a089af3cd26b706750258ce1ca96cc0116"}, + {file = "protobuf-4.21.5-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:ee04f5823ed98bb9a8c3b1dc503c49515e0172650875c3f76e225b223793a1f2"}, + {file = "protobuf-4.21.5-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:b04484d6f42f48c57dd2737a72692f4c6987529cdd148fb5b8e5f616862a2e37"}, + {file = "protobuf-4.21.5-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:5e0b272217aad8971763960238c1a1e6a65d50ef7824e23300da97569a251c55"}, + {file = "protobuf-4.21.5-cp37-cp37m-win32.whl", hash = "sha256:5eb0724615e90075f1d763983e708e1cef08e66b1891d8b8b6c33bc3b2f1a02b"}, + {file = "protobuf-4.21.5-cp37-cp37m-win_amd64.whl", hash = "sha256:011c0f267e85f5d73750b6c25f0155d5db1e9443cd3590ab669a6221dd8fcdb0"}, + {file = "protobuf-4.21.5-cp38-cp38-win32.whl", hash = "sha256:7b6f22463e2d1053d03058b7b4ceca6e4ed4c14f8c286c32824df751137bf8e7"}, + {file = "protobuf-4.21.5-cp38-cp38-win_amd64.whl", hash = "sha256:b52e7a522911a40445a5f588bd5b5e584291bfc5545e09b7060685e4b2ff814f"}, + {file = "protobuf-4.21.5-cp39-cp39-win32.whl", hash = "sha256:a7faa62b183d6a928e3daffd06af843b4287d16ef6e40f331575ecd236a7974d"}, + {file = "protobuf-4.21.5-cp39-cp39-win_amd64.whl", hash = "sha256:5e0ce02418ef03d7657a420ae8fd6fec4995ac713a3cb09164e95f694dbcf085"}, + {file = "protobuf-4.21.5-py2.py3-none-any.whl", hash = "sha256:bf711b451212dc5b0fa45ae7dada07d8e71a4b0ff0bc8e4783ee145f47ac4f82"}, + {file = "protobuf-4.21.5-py3-none-any.whl", hash = "sha256:3ec6f5b37935406bb9df9b277e79f8ed81d697146e07ef2ba8a5a272fb24b2c9"}, + {file = "protobuf-4.21.5.tar.gz", hash = "sha256:eb1106e87e095628e96884a877a51cdb90087106ee693925ec0a300468a9be3a"}, +] py = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, @@ -2218,8 +2714,14 @@ pyflakes = [ {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, ] -pygments = [] -pylint = [] +pygments = [ + {file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"}, + {file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"}, +] +pylint = [ + {file = "pylint-2.13.9-py3-none-any.whl", hash = "sha256:705c620d388035bdd9ff8b44c5bcdd235bfb49d276d488dd2c8ff1736aa42526"}, + {file = "pylint-2.13.9.tar.gz", hash = "sha256:095567c96e19e6f57b5b907e67d265ff535e588fe26b12b5ebe1fc5645b2c731"}, +] pymongo = [ {file = "pymongo-3.12.3-cp27-cp27m-macosx_10_14_intel.whl", hash = "sha256:c164eda0be9048f83c24b9b2656900041e069ddf72de81c17d874d0c32f6079f"}, {file = "pymongo-3.12.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:a055d29f1302892a9389a382bed10a3f77708bcf3e49bfb76f7712fa5f391cc6"}, @@ -2346,10 +2848,42 @@ pynput = [ {file = "pynput-1.7.6-py3.9.egg", hash = "sha256:264429fbe676e98e9050ad26a7017453bdd08768adb25cafb918347cf9f1eb4a"}, {file = "pynput-1.7.6.tar.gz", hash = "sha256:3a5726546da54116b687785d38b1db56997ce1d28e53e8d22fc656d8b92e533c"}, ] -pyobjc-core = [] -pyobjc-framework-applicationservices = [] -pyobjc-framework-cocoa = [] -pyobjc-framework-quartz = [] +pyobjc-core = [ + {file = "pyobjc-core-8.5.tar.gz", hash = "sha256:704c275439856c0d1287469f0d589a7d808d48b754a93d9ce5415d4eaf06d576"}, + {file = "pyobjc_core-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0c234143b48334443f5adcf26e668945a6d47bc1fa6223e80918c6c735a029d9"}, + {file = "pyobjc_core-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1486ee533f0d76f666804ce89723ada4db56bfde55e56151ba512d3f849857f8"}, + {file = "pyobjc_core-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:412de06dfa728301c04b3e46fd7453320a8ae8b862e85236e547cd797a73b490"}, + {file = "pyobjc_core-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b3e09cccb1be574a82cc9f929ae27fc4283eccc75496cb5d51534caa6bb83a3"}, + {file = "pyobjc_core-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:eeafe21f879666ab7f57efcc6b007c9f5f8733d367b7e380c925203ed83f000d"}, + {file = "pyobjc_core-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c0071686976d7ea8c14690950e504a13cb22b4ebb2bc7b5ec47c1c1c0f6eff41"}, +] +pyobjc-framework-applicationservices = [ + {file = "pyobjc-framework-ApplicationServices-8.5.tar.gz", hash = "sha256:fa3015ef8e3add90af3447d7fdcc7f8dd083cc2a1d58f99a569480a2df10d2b1"}, + {file = "pyobjc_framework_ApplicationServices-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:436b16ebe448a829a8312e10208eec81a2adcae1fff674dbcc3262e1bd76e0ca"}, + {file = "pyobjc_framework_ApplicationServices-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:408958d14aa7fcf46f2163754c211078bc63be1368934d86188202914dce077d"}, + {file = "pyobjc_framework_ApplicationServices-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1d6cd4ce192859a22e208da4d7177a1c3ceb1ef2f64c339fd881102b1210cadd"}, + {file = "pyobjc_framework_ApplicationServices-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0251d092adb1d2d116fd9f147ceef0e53b158a46c21245131c40b9d7b786d0db"}, + {file = "pyobjc_framework_ApplicationServices-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:9742e69fe6d4545d0e02b0ad0a7a2432bc9944569ee07d6e90ffa5ef614df9f7"}, + {file = "pyobjc_framework_ApplicationServices-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16f5677c14ea903c6aaca1dd121521825c39e816cae696d6ae32c0b287252ab2"}, +] +pyobjc-framework-cocoa = [ + {file = "pyobjc-framework-Cocoa-8.5.tar.gz", hash = "sha256:569bd3a020f64b536fb2d1c085b37553e50558c9f907e08b73ffc16ae68e1861"}, + {file = "pyobjc_framework_Cocoa-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7a7c160416696bf6035dfcdf0e603aaa52858d6afcddfcc5ab41733619ac2529"}, + {file = "pyobjc_framework_Cocoa-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6ceba444282030be8596b812260e8d28b671254a51052ad778d32da6e17db847"}, + {file = "pyobjc_framework_Cocoa-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f46b2b161b8dd40c7b9e00bc69636c3e6480b2704a69aee22ee0154befbe163a"}, + {file = "pyobjc_framework_Cocoa-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b31d425aee8698cbf62b187338f5ca59427fa4dca2153a73866f7cb410713119"}, + {file = "pyobjc_framework_Cocoa-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:898359ac1f76eedec8aa156847682378a8950824421c40edb89391286e607dc4"}, + {file = "pyobjc_framework_Cocoa-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:baa2947f76b119a3360973d74d57d6dada87ac527bab9a88f31596af392f123c"}, +] +pyobjc-framework-quartz = [ + {file = "pyobjc-framework-Quartz-8.5.tar.gz", hash = "sha256:d2bc5467a792ddc04814f12a1e9c2fcaf699a1c3ad3d4264cfdce6b9c7b10624"}, + {file = "pyobjc_framework_Quartz-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e9f0fb663f7872c9de94169031ac42b91ad01bd4cad49a9f1a0164be8f028426"}, + {file = "pyobjc_framework_Quartz-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:567eec91287cfe9a1b6433717192c585935de8f3daa28d82ce72fdd6c7ac00f6"}, + {file = "pyobjc_framework_Quartz-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f910ab41a712ffc7a8c3e3716a2d6f39ea4419004b26a2fd2d2f740ff5c262c"}, + {file = "pyobjc_framework_Quartz-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:29d07066781628278bf0e5278abcfc96ef6724c66c5629a0b4c214d319a82e55"}, + {file = "pyobjc_framework_Quartz-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:72abcde1a3d72be11f2c881c9b9872044c8f2de86d2047b67fe771713638b107"}, + {file = "pyobjc_framework_Quartz-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8809b9a2df2f461697bdb45b6d1b5a4f881f88f09450e3990858e64e3e26c530"}, +] pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, @@ -2373,8 +2907,14 @@ python-dateutil = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] -python-engineio = [] -python-socketio = [] +python-engineio = [ + {file = "python-engineio-3.14.2.tar.gz", hash = "sha256:eab4553f2804c1ce97054c8b22cf0d5a9ab23128075248b97e1a5b2f29553085"}, + {file = "python_engineio-3.14.2-py2.py3-none-any.whl", hash = "sha256:5a9e6086d192463b04a1428ff1f85b6ba631bbb19d453b144ffc04f530542b84"}, +] +python-socketio = [ + {file = "python-socketio-4.6.1.tar.gz", hash = "sha256:cd1f5aa492c1eb2be77838e837a495f117e17f686029ebc03d62c09e33f4fa10"}, + {file = "python_socketio-4.6.1-py2.py3-none-any.whl", hash = "sha256:5a21da53fdbdc6bb6c8071f40e13d100e0b279ad997681c2492478e06f370523"}, +] python-xlib = [ {file = "python-xlib-0.31.tar.gz", hash = "sha256:74d83a081f532bc07f6d7afcd6416ec38403d68f68b9b9dc9e1f28fbf2d799e9"}, {file = "python_xlib-0.31-py2.py3-none-any.whl", hash = "sha256:1ec6ce0de73d9e6592ead666779a5732b384e5b8fb1f1886bd0a81cafa477759"}, @@ -2382,7 +2922,10 @@ python-xlib = [ python3-xlib = [ {file = "python3-xlib-0.15.tar.gz", hash = "sha256:dc4245f3ae4aa5949c1d112ee4723901ade37a96721ba9645f2bfa56e5b383f8"}, ] -pytz = [] +pytz = [ + {file = "pytz-2022.2-py2.py3-none-any.whl", hash = "sha256:d9b245e63af49c4e51afdec5402f56b99c0cb483a84a12bb8b7db980386baade"}, + {file = "pytz-2022.2.tar.gz", hash = "sha256:bc824559e43e8ab983426a49525079d186b25372ff63aa3430ccd527d95edc3a"}, +] pywin32 = [ {file = "pywin32-301-cp35-cp35m-win32.whl", hash = "sha256:93367c96e3a76dfe5003d8291ae16454ca7d84bb24d721e0b74a07610b7be4a7"}, {file = "pywin32-301-cp35-cp35m-win_amd64.whl", hash = "sha256:9635df6998a70282bd36e7ac2a5cef9ead1627b0a63b17c731312c7a0daebb72"}, @@ -2399,7 +2942,10 @@ pywin32-ctypes = [ {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"}, {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, ] -"qt.py" = [] +"qt.py" = [ + {file = "Qt.py-1.3.7-py2.py3-none-any.whl", hash = "sha256:150099d1c6f64c9621a2c9d79d45102ec781c30ee30ee69fc082c6e9be7324fe"}, + {file = "Qt.py-1.3.7.tar.gz", hash = "sha256:803c7bdf4d6230f9a466be19d55934a173eabb61406d21cb91e80c2a3f773b1f"}, +] qtawesome = [ {file = "QtAwesome-0.7.3-py2.py3-none-any.whl", hash = "sha256:ddf4530b4af71cec13b24b88a4cdb56ec85b1e44c43c42d0698804c7137b09b0"}, {file = "QtAwesome-0.7.3.tar.gz", hash = "sha256:b98b9038d19190e83ab26d91c4d8fc3a36591ee2bc7f5016d4438b8240d097bd"}, @@ -2412,19 +2958,35 @@ recommonmark = [ {file = "recommonmark-0.7.1-py2.py3-none-any.whl", hash = "sha256:1b1db69af0231efce3fa21b94ff627ea33dee7079a01dd0a7f8482c3da148b3f"}, {file = "recommonmark-0.7.1.tar.gz", hash = "sha256:bdb4db649f2222dcd8d2d844f0006b958d627f732415d399791ee436a3686d67"}, ] -requests = [] -rsa = [] -secretstorage = [] +requests = [ + {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, + {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, +] +rsa = [ + {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, + {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, +] +secretstorage = [ + {file = "SecretStorage-3.3.2-py3-none-any.whl", hash = "sha256:755dc845b6ad76dcbcbc07ea3da75ae54bb1ea529eb72d15f83d26499a5df319"}, + {file = "SecretStorage-3.3.2.tar.gz", hash = "sha256:0a8eb9645b320881c222e827c26f4cfcf55363e8b374a021981ef886657a912f"}, +] semver = [ {file = "semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"}, {file = "semver-2.13.0.tar.gz", hash = "sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f"}, ] +setuptools = [ + {file = "setuptools-60.10.0-py3-none-any.whl", hash = "sha256:782ef48d58982ddb49920c11a0c5c9c0b02e7d7d1c2ad0aa44e1a1e133051c96"}, + {file = "setuptools-60.10.0.tar.gz", hash = "sha256:6599055eeb23bfef457d5605d33a4d68804266e6cb430b0fb12417c5efeae36c"}, +] shotgun-api3 = [] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] -slack-sdk = [] +slack-sdk = [ + {file = "slack_sdk-3.18.1-py2.py3-none-any.whl", hash = "sha256:63ce5e6253a31873d6c921c9feaa842a93a2f56e6e009cb7daf406f4bc4df798"}, + {file = "slack_sdk-3.18.1.tar.gz", hash = "sha256:a25d3d2bf0bf605d54d764d4a463fe7c0659ee24c13d75653e2bec247bd5998b"}, +] smmap = [ {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, @@ -2433,9 +2995,18 @@ snowballstemmer = [ {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] -speedcopy = [] -sphinx = [] -sphinx-qt-documentation = [] +speedcopy = [ + {file = "speedcopy-2.1.4-py3-none-any.whl", hash = "sha256:e09eb1de67ae0e0b51d5b99a28882009d565a37a3cb3c6bae121e3a5d3cccb17"}, + {file = "speedcopy-2.1.4.tar.gz", hash = "sha256:eff007a97e49ec1934df4fa8074f4bd1cf4a3b14c5499d914988785cff0c199a"}, +] +sphinx = [ + {file = "Sphinx-5.0.1-py3-none-any.whl", hash = "sha256:36aa2a3c2f6d5230be94585bc5d74badd5f9ed8f3388b8eedc1726fe45b1ad30"}, + {file = "Sphinx-5.0.1.tar.gz", hash = "sha256:f4da1187785a5bc7312cc271b0e867a93946c319d106363e102936a3d9857306"}, +] +sphinx-qt-documentation = [ + {file = "sphinx_qt_documentation-0.4-py3-none-any.whl", hash = "sha256:fa131093f75cd1bd48699cd132e18e4d46ba9eaadc070e6026867cea75ecdb7b"}, + {file = "sphinx_qt_documentation-0.4.tar.gz", hash = "sha256:f43ba17baa93e353fb94045027fb67f9d935ed158ce8662de93f08b88eec6774"}, +] sphinx-rtd-theme = [ {file = "sphinx_rtd_theme-1.0.0-py2.py3-none-any.whl", hash = "sha256:4d35a56f4508cfee4c4fb604373ede6feae2a306731d533f409ef5c3496fdbd8"}, {file = "sphinx_rtd_theme-1.0.0.tar.gz", hash = "sha256:eec6d497e4c2195fa0e8b2016b337532b8a699a68bcb22a512870e16925c6a5c"}, @@ -2484,13 +3055,44 @@ tomli = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] -typed-ast = [] -typing-extensions = [] +typed-ast = [ + {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, + {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, + {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, + {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, + {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, + {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, + {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, + {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, + {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, + {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, +] +typing-extensions = [ + {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, + {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, +] uritemplate = [ {file = "uritemplate-3.0.1-py2.py3-none-any.whl", hash = "sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f"}, {file = "uritemplate-3.0.1.tar.gz", hash = "sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae"}, ] -urllib3 = [] +urllib3 = [ + {file = "urllib3-1.26.11-py2.py3-none-any.whl", hash = "sha256:c33ccba33c819596124764c23a97d25f32b28433ba0dedeb77d873a38722c9bc"}, + {file = "urllib3-1.26.11.tar.gz", hash = "sha256:ea6e8fb210b19d950fab93b60c9009226c63a28808bc8386e05301e25883ac0a"}, +] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, @@ -2499,10 +3101,142 @@ websocket-client = [ {file = "websocket-client-0.59.0.tar.gz", hash = "sha256:d376bd60eace9d437ab6d7ee16f4ab4e821c9dae591e1b783c58ebd8aaf80c5c"}, {file = "websocket_client-0.59.0-py2.py3-none-any.whl", hash = "sha256:2e50d26ca593f70aba7b13a489435ef88b8fc3b5c5643c1ce8808ff9b40f0b32"}, ] -wrapt = [] +wheel = [ + {file = "wheel-0.37.1-py2.py3-none-any.whl", hash = "sha256:4bdcd7d840138086126cd09254dc6195fb4fc6f01c050a1d7236f2630db1d22a"}, + {file = "wheel-0.37.1.tar.gz", hash = "sha256:e9a504e793efbca1b8e0e9cb979a249cf4a0a7b5b8c9e8b65a5e39d49529c1c4"}, +] +wrapt = [ + {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"}, + {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"}, + {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"}, + {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"}, + {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"}, + {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"}, + {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"}, + {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"}, + {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"}, + {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"}, + {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"}, + {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"}, + {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"}, + {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"}, + {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"}, + {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"}, + {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"}, + {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"}, + {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"}, + {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"}, + {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"}, + {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"}, +] wsrpc-aiohttp = [ {file = "wsrpc-aiohttp-3.2.0.tar.gz", hash = "sha256:f467abc51bcdc760fc5aeb7041abdeef46eeca3928dc43dd6e7fa7a533563818"}, {file = "wsrpc_aiohttp-3.2.0-py3-none-any.whl", hash = "sha256:fa9b0bf5cb056898cb5c9f64cbc5eacb8a5dd18ab1b7f0cd4a2208b4a7fde282"}, ] -yarl = [] -zipp = [] +yarl = [ + {file = "yarl-1.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:abc06b97407868ef38f3d172762f4069323de52f2b70d133d096a48d72215d28"}, + {file = "yarl-1.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:07b21e274de4c637f3e3b7104694e53260b5fc10d51fb3ec5fed1da8e0f754e3"}, + {file = "yarl-1.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9de955d98e02fab288c7718662afb33aab64212ecb368c5dc866d9a57bf48880"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ec362167e2c9fd178f82f252b6d97669d7245695dc057ee182118042026da40"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:20df6ff4089bc86e4a66e3b1380460f864df3dd9dccaf88d6b3385d24405893b"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5999c4662631cb798496535afbd837a102859568adc67d75d2045e31ec3ac497"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed19b74e81b10b592084a5ad1e70f845f0aacb57577018d31de064e71ffa267a"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e4808f996ca39a6463f45182e2af2fae55e2560be586d447ce8016f389f626f"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2d800b9c2eaf0684c08be5f50e52bfa2aa920e7163c2ea43f4f431e829b4f0fd"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6628d750041550c5d9da50bb40b5cf28a2e63b9388bac10fedd4f19236ef4957"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f5af52738e225fcc526ae64071b7e5342abe03f42e0e8918227b38c9aa711e28"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:76577f13333b4fe345c3704811ac7509b31499132ff0181f25ee26619de2c843"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0c03f456522d1ec815893d85fccb5def01ffaa74c1b16ff30f8aaa03eb21e453"}, + {file = "yarl-1.8.1-cp310-cp310-win32.whl", hash = "sha256:ea30a42dc94d42f2ba4d0f7c0ffb4f4f9baa1b23045910c0c32df9c9902cb272"}, + {file = "yarl-1.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:9130ddf1ae9978abe63808b6b60a897e41fccb834408cde79522feb37fb72fb0"}, + {file = "yarl-1.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0ab5a138211c1c366404d912824bdcf5545ccba5b3ff52c42c4af4cbdc2c5035"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0fb2cb4204ddb456a8e32381f9a90000429489a25f64e817e6ff94879d432fc"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:85cba594433915d5c9a0d14b24cfba0339f57a2fff203a5d4fd070e593307d0b"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ca7e596c55bd675432b11320b4eacc62310c2145d6801a1f8e9ad160685a231"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0f77539733e0ec2475ddcd4e26777d08996f8cd55d2aef82ec4d3896687abda"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29e256649f42771829974e742061c3501cc50cf16e63f91ed8d1bf98242e5507"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7fce6cbc6c170ede0221cc8c91b285f7f3c8b9fe28283b51885ff621bbe0f8ee"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:59ddd85a1214862ce7c7c66457f05543b6a275b70a65de366030d56159a979f0"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:12768232751689c1a89b0376a96a32bc7633c08da45ad985d0c49ede691f5c0d"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:b19255dde4b4f4c32e012038f2c169bb72e7f081552bea4641cab4d88bc409dd"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6c8148e0b52bf9535c40c48faebb00cb294ee577ca069d21bd5c48d302a83780"}, + {file = "yarl-1.8.1-cp37-cp37m-win32.whl", hash = "sha256:de839c3a1826a909fdbfe05f6fe2167c4ab033f1133757b5936efe2f84904c07"}, + {file = "yarl-1.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:dd032e8422a52e5a4860e062eb84ac94ea08861d334a4bcaf142a63ce8ad4802"}, + {file = "yarl-1.8.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:19cd801d6f983918a3f3a39f3a45b553c015c5aac92ccd1fac619bd74beece4a"}, + {file = "yarl-1.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6347f1a58e658b97b0a0d1ff7658a03cb79bdbda0331603bed24dd7054a6dea1"}, + {file = "yarl-1.8.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c0da7e44d0c9108d8b98469338705e07f4bb7dab96dbd8fa4e91b337db42548"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5587bba41399854703212b87071c6d8638fa6e61656385875f8c6dff92b2e461"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31a9a04ecccd6b03e2b0e12e82131f1488dea5555a13a4d32f064e22a6003cfe"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:205904cffd69ae972a1707a1bd3ea7cded594b1d773a0ce66714edf17833cdae"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea513a25976d21733bff523e0ca836ef1679630ef4ad22d46987d04b372d57fc"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0b51530877d3ad7a8d47b2fff0c8df3b8f3b8deddf057379ba50b13df2a5eae"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d2b8f245dad9e331540c350285910b20dd913dc86d4ee410c11d48523c4fd546"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ab2a60d57ca88e1d4ca34a10e9fb4ab2ac5ad315543351de3a612bbb0560bead"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:449c957ffc6bc2309e1fbe67ab7d2c1efca89d3f4912baeb8ead207bb3cc1cd4"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a165442348c211b5dea67c0206fc61366212d7082ba8118c8c5c1c853ea4d82e"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b3ded839a5c5608eec8b6f9ae9a62cb22cd037ea97c627f38ae0841a48f09eae"}, + {file = "yarl-1.8.1-cp38-cp38-win32.whl", hash = "sha256:c1445a0c562ed561d06d8cbc5c8916c6008a31c60bc3655cdd2de1d3bf5174a0"}, + {file = "yarl-1.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:56c11efb0a89700987d05597b08a1efcd78d74c52febe530126785e1b1a285f4"}, + {file = "yarl-1.8.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e80ed5a9939ceb6fda42811542f31c8602be336b1fb977bccb012e83da7e4936"}, + {file = "yarl-1.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6afb336e23a793cd3b6476c30f030a0d4c7539cd81649683b5e0c1b0ab0bf350"}, + {file = "yarl-1.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4c322cbaa4ed78a8aac89b2174a6df398faf50e5fc12c4c191c40c59d5e28357"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fae37373155f5ef9b403ab48af5136ae9851151f7aacd9926251ab26b953118b"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5395da939ffa959974577eff2cbfc24b004a2fb6c346918f39966a5786874e54"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:076eede537ab978b605f41db79a56cad2e7efeea2aa6e0fa8f05a26c24a034fb"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d1a50e461615747dd93c099f297c1994d472b0f4d2db8a64e55b1edf704ec1c"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7de89c8456525650ffa2bb56a3eee6af891e98f498babd43ae307bd42dca98f6"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4a88510731cd8d4befaba5fbd734a7dd914de5ab8132a5b3dde0bbd6c9476c64"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2d93a049d29df172f48bcb09acf9226318e712ce67374f893b460b42cc1380ae"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:21ac44b763e0eec15746a3d440f5e09ad2ecc8b5f6dcd3ea8cb4773d6d4703e3"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d0272228fabe78ce00a3365ffffd6f643f57a91043e119c289aaba202f4095b0"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:99449cd5366fe4608e7226c6cae80873296dfa0cde45d9b498fefa1de315a09e"}, + {file = "yarl-1.8.1-cp39-cp39-win32.whl", hash = "sha256:8b0af1cf36b93cee99a31a545fe91d08223e64390c5ecc5e94c39511832a4bb6"}, + {file = "yarl-1.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:de49d77e968de6626ba7ef4472323f9d2e5a56c1d85b7c0e2a190b2173d3b9be"}, + {file = "yarl-1.8.1.tar.gz", hash = "sha256:af887845b8c2e060eb5605ff72b6f2dd2aab7a761379373fd89d314f4752abbf"}, +] +zipp = [ + {file = "zipp-3.8.1-py3-none-any.whl", hash = "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009"}, + {file = "zipp-3.8.1.tar.gz", hash = "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2"}, +] diff --git a/pyproject.toml b/pyproject.toml index 0deb4f465b..2a0606a10c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,12 +70,13 @@ requests = "^2.25.1" pysftp = "^0.2.9" dropbox = "^11.20.0" aiohttp-middlewares = "^2.0.0" +cx-Freeze = "6.11.1" [tool.poetry.dev-dependencies] flake8 = "^3.7" autopep8 = "^1.4" coverage = "*" -cx_freeze = "~6.9" +cx_freeze = "^6.11.1" GitPython = "^3.1.17" jedi = "^0.13" Jinja2 = "^2.11" From 4bdd18cb817bbc58c1143e0e02442a9346ce9a1e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 2 Sep 2022 11:50:14 +0200 Subject: [PATCH 222/274] Use DeadlineKeyValueVar for EnvironmentKeyValue on Job Info - To improve readability of code that sets the values --- .../deadline/abstract_submit_deadline.py | 58 +++++++++++++++---- .../publish/submit_aftereffects_deadline.py | 11 ++-- .../publish/submit_harmony_deadline.py | 10 ++-- .../plugins/publish/submit_maya_deadline.py | 7 +-- 4 files changed, 59 insertions(+), 27 deletions(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index 35b114da95..beb1cd0fae 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -67,6 +67,43 @@ def requests_get(*args, **kwargs): return requests.get(*args, **kwargs) +class DeadlineKeyValueVar(dict): + """ + + Serializes dictionary key values as "{key}={value}" like Deadline uses + for EnvironmentKeyValue. + + As an example: + EnvironmentKeyValue0="A_KEY=VALUE_A" + EnvironmentKeyValue1="OTHER_KEY=VALUE_B" + + The keys are serialized in alphabetical order (sorted). + + Example: + >>> var = DeadlineKeyValueVar("EnvironmentKeyValue") + >>> var["my_var"] = "hello" + >>> var["my_other_var"] = "hello2" + >>> var.serialize() + + + """ + def __init__(self, key): + super(DeadlineKeyValueVar, self).__init__() + self.__key = key + + def serialize(self): + key = self.__key + + # Allow custom location for index in serialized string + if "{}" not in key: + key = key + "{}" + + return { + key.format(index): "{}={}".format(var_key, var_value) + for index, (var_key, var_value) in enumerate(sorted(self.items())) + } + + class DeadlineIndexedVar(dict): """ @@ -80,15 +117,9 @@ class DeadlineIndexedVar(dict): """ def __init__(self, key): + super(DeadlineIndexedVar, self).__init__() self.__key = key - def next_available_index(self): - # Add as first unused entry - i = 0 - while i in self.keys(): - i += 1 - return i - def serialize(self): key = self.__key @@ -100,6 +131,13 @@ class DeadlineIndexedVar(dict): key.format(index): value for index, value in sorted(self.items()) } + def next_available_index(self): + # Add as first unused entry + i = 0 + while i in self.keys(): + i += 1 + return i + def update(self, data): # Force the integer key check for key, value in data.items(): @@ -271,7 +309,7 @@ class DeadlineJobInfo(object): # Environment # ---------------------------------------------- - EnvironmentKeyValue = attr.ib(factory=partial(DeadlineIndexedVar, + EnvironmentKeyValue = attr.ib(factory=partial(DeadlineKeyValueVar, "EnvironmentKeyValue")) IncludeEnvironment = attr.ib(default=None) # Default: false @@ -281,7 +319,7 @@ class DeadlineJobInfo(object): # Job Extra Info # ---------------------------------------------- ExtraInfo = attr.ib(factory=partial(DeadlineIndexedVar, "ExtraInfo")) - ExtraInfoKeyValue = attr.ib(factory=partial(DeadlineIndexedVar, + ExtraInfoKeyValue = attr.ib(factory=partial(DeadlineKeyValueVar, "ExtraInfoKeyValue")) # Task Extra Info Names @@ -326,7 +364,7 @@ class DeadlineJobInfo(object): """ def filter_data(a, v): - if isinstance(v, DeadlineIndexedVar): + if isinstance(v, (DeadlineIndexedVar, DeadlineKeyValueVar)): return False if v is None: return False diff --git a/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py b/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py index 1d68793d53..55acd92043 100644 --- a/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py @@ -92,13 +92,12 @@ class AfterEffectsSubmitDeadline( environment = dict({key: os.environ[key] for key in keys if key in os.environ}, **legacy_io.Session) for key in keys: - val = environment.get(key) - if val: - dln_job_info.EnvironmentKeyValue = "{key}={value}".format( - key=key, - value=val) + value = environment.get(key) + if value: + dln_job_info.EnvironmentKeyValue[key] = value + # to recognize job from PYPE for turning Event On/Off - dln_job_info.EnvironmentKeyValue = "OPENPYPE_RENDER_JOB=1" + dln_job_info.EnvironmentKeyValue["OPENPYPE_RENDER_JOB"] = "1" return dln_job_info diff --git a/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py b/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py index 3f9c09b592..6327143623 100644 --- a/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py @@ -284,14 +284,12 @@ class HarmonySubmitDeadline( environment = dict({key: os.environ[key] for key in keys if key in os.environ}, **legacy_io.Session) for key in keys: - val = environment.get(key) - if val: - job_info.EnvironmentKeyValue = "{key}={value}".format( - key=key, - value=val) + value = environment.get(key) + if value: + job_info.EnvironmentKeyValue[key] = value # to recognize job from PYPE for turning Event On/Off - job_info.EnvironmentKeyValue = "OPENPYPE_RENDER_JOB=1" + job_info.EnvironmentKeyValue["OPENPYPE_RENDER_JOB"] = "1" return job_info diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 9692b136e9..ad46feea03 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -137,8 +137,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): for key, value in environment.items(): if not value: continue - job_info.EnvironmentKeyValue = "{key}={value}".format(key=key, - value=value) + job_info.EnvironmentKeyValue[key] = value # Adding file dependencies. if self.asset_dependencies: @@ -538,9 +537,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): for key, value in envs.items(): if not value: continue - - job_info.EnvironmentKeyValue = "{key}={value}".format(key=key, - value=value) + job_info.EnvironmentKeyValue[key] = value plugin_info.update({ "Version": "3.6", From 1e87c9d6d2c7338f8e53e8a06d9f1983056797b6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 2 Sep 2022 12:02:39 +0200 Subject: [PATCH 223/274] Use DeadlineIndexedVar `__iadd__` functionality --- .../plugins/publish/submit_aftereffects_deadline.py | 4 ++-- .../deadline/plugins/publish/submit_maya_deadline.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py b/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py index 55acd92043..0c1ffa6bd7 100644 --- a/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py @@ -67,9 +67,9 @@ class AfterEffectsSubmitDeadline( dln_job_info.Group = self.group dln_job_info.Department = self.department dln_job_info.ChunkSize = self.chunk_size - dln_job_info.OutputFilename = \ + dln_job_info.OutputFilename += \ os.path.basename(self._instance.data["expectedFiles"][0]) - dln_job_info.OutputDirectory = \ + dln_job_info.OutputDirectory += \ os.path.dirname(self._instance.data["expectedFiles"][0]) dln_job_info.JobDelay = "00:00:00" diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index ad46feea03..6b08f9894d 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -144,14 +144,14 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): dependencies = instance.context.data["fileDependencies"] dependencies.append(context.data["currentFile"]) for dependency in dependencies: - job_info.AssetDependency = dependency + job_info.AssetDependency += dependency # Add list of expected files to job # --------------------------------- exp = instance.data.get("expectedFiles") for filepath in self._iter_expected_files(exp): - job_info.OutputDirectory = os.path.dirname(filepath) - job_info.OutputFilename = os.path.basename(filepath) + job_info.OutputDirectory += os.path.dirname(filepath) + job_info.OutputFilename += os.path.basename(filepath) return job_info @@ -443,7 +443,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): if self.asset_dependencies: # Asset dependency to wait for at least the scene file to sync. - job_info.AssetDependency = self.scene_path + job_info.AssetDependency += self.scene_path # Get layer prefix render_products = self._instance.data["renderProducts"] From 2c01cb806d68aa04a733e16d4cfd1abb15f438fe Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 2 Sep 2022 12:03:34 +0200 Subject: [PATCH 224/274] Remove backwards compatibility for append functionality in old style vars --- openpype/modules/deadline/abstract_submit_deadline.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index beb1cd0fae..f698b7688e 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -393,16 +393,6 @@ class DeadlineJobInfo(object): for key, value in data.items(): setattr(self, key, value) - def __setattr__(self, key, value): - # Backwards compatibility: Allow appending to index vars by setting - # it on Job Info directly like: JobInfo.OutputFilename = filename - existing = getattr(self, key, None) - if isinstance(existing, DeadlineIndexedVar): - existing += value - return - - object.__setattr__(self, key, value) - @six.add_metaclass(AbstractMetaInstancePlugin) class AbstractSubmitDeadline(pyblish.api.InstancePlugin): From 47164b36effa0f4986ccf15aa3a4967ccb014e26 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 2 Sep 2022 12:11:35 +0200 Subject: [PATCH 225/274] Be more explicit about what keys to include from Session This way it matches more with logic of other host submitters (e.g. AfterEffects + Harmony) --- .../deadline/plugins/publish/submit_maya_deadline.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 6b08f9894d..bb48fe6902 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -130,15 +130,16 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): environment = dict({key: os.environ[key] for key in keys if key in os.environ}, **legacy_io.Session) - # to recognize job from PYPE for turning Event On/Off - environment["OPENPYPE_RENDER_JOB"] = "1" - environment["OPENPYPE_LOG_NO_COLORS"] = "1" - - for key, value in environment.items(): + for key in keys: + value = environment.get(key) if not value: continue job_info.EnvironmentKeyValue[key] = value + # to recognize job from PYPE for turning Event On/Off + job_info.EnvironmentKeyValue["OPENPYPE_RENDER_JOB"] = "1" + job_info.EnvironmentKeyValue["OPENPYPE_LOG_NO_COLORS"] = "1" + # Adding file dependencies. if self.asset_dependencies: dependencies = instance.context.data["fileDependencies"] From ec171443d2fd0832d63d821a65be5cc343829059 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 2 Sep 2022 12:14:32 +0200 Subject: [PATCH 226/274] Small grammar fixes --- .../docs/admin_settings_project_anatomy.md | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/website/docs/admin_settings_project_anatomy.md b/website/docs/admin_settings_project_anatomy.md index 361710fc9c..b2a3d9f969 100644 --- a/website/docs/admin_settings_project_anatomy.md +++ b/website/docs/admin_settings_project_anatomy.md @@ -20,17 +20,17 @@ It defines: - Colour Management - File Formats -Anatomy is the only configuration that is always saved as project override. This is to make sure, that any updates to OpenPype or Studio default values, don't affect currently running productions. +Anatomy is the only configuration that is always saved as an project override. This is to make sure that any updates to OpenPype or Studio default values, don't affect currently running productions. ![anatomy_01](assets/settings/anatomy_01.png) ## Roots -Roots define where files are stored with path to shared folder. It is required to set root path for each platform you are using in studio. All paths must point to same folder! +Roots define where files are stored with path to a shared folder. It is required to set the root path for each platform you are using in the studio. All paths must point to the same folder! ![roots01](assets/settings/anatomy_roots01.png) -It is possible to set multiple roots when necessary. That may be handy when you need to store specific type of data on another disk. +It is possible to set multiple roots when necessary. That may be handy when you need to store a specific type of data on another disk. ![roots02](assets/settings/anatomy_roots02.png) @@ -40,7 +40,7 @@ Note how multiple roots are used here, to push different types of files to diffe ## Templates -Templates define project's folder structure and filenames. +Templates define the project's folder structure and filenames. We have a few required anatomy templates for OpenPype to work properly, however we keep adding more when needed. @@ -102,34 +102,34 @@ We have a few required anatomy templates for OpenPype to work properly, however ### Anatomy reference keys -Anatomy templates have ability to use "referenced keys". Best example is `path` in publish or work templates which are just referencing to `folder` and `file` (`{@folder}/{@file}`) so any changes in folder or file template are propagated to the path template. The other advantage is to simplify version and frame formatting with paddings. In default templates you can notice that keys `{@version}` or `{@frame}` are used in templates. They are referencing to `Anatomy` -> `Templates` -> `Version` or `Frame` which handle version and frame formatting with padding. +Anatomy templates have the ability to use "referenced keys". Best example is `path` in publish or work templates which just contains references to `folder` and `file` (`{@folder}/{@file}`). Any changes in folder or file template are propagated to the path template. The another example is simplification of version and frame formatting with paddings. You can notice that keys `{@version}` or `{@frame}` are used in default templates. They are referencing `Anatomy` -> `Templates` -> `Version` or `Frame` which handle version and frame formatting with padding. -So if you set `project_anatomy/templates/defaults/version_padding` to `5` the `{@version}` key will be transformed to `v{version:0>5}` and version number in paths will have 5 numbers -> `v00001`. +So if you set `project_anatomy/templates/defaults/version_padding` to `5` the `{@version}` key will be transformed to `v{version:0>5}` automatically and version number in paths will have 5 numbers -> `v00001`. ### Optional keys -In some cases are not all keys available and should be just ignored. For example `{frame}` should be available only for sequences but we have single publish template. To handle these cases it is possible to use optional marks which will ignore segment of template if can't be filled because of missing keys. To mark these segments use `<` and `>`. +In some cases of template formatting not all keys are available and should be just ignored. For example `{frame}` should be available only for sequences but we have single publish template. To handle these cases it is possible to use special characters to mark segment of template which should be ignored, if it can't be filled because of missing keys. To mark these segments use `<` and `>`. . Template `{project[code]}_{asset}_{subset}<_{output}><.{@frame}>.{ext}` can handle all 4 possible situations when `output` and `frame` keys are available or not. The optional segments can contain additional text, like in the example dot (`.`) for frame and underscore (`_`) for output, those are also ignored if the keys are not available. Optional segments without formatting keys are kept untouched: `
` -> stays as `
`. It is possible to nest optional segments inside optional segments `<{asset}<.{@frame}>
>` which may result in empty string if `asset` key is not available. ## Attributes -Project attributes are used as default values for new assets under project, except `Applications` and `Active project` which are project specific. Values of attributes that are **not** project specific are always used from assets. So if `tools` are not loading as expected it is because the asset have different value. +Project attributes are used as default values for new assets created under project, except `Applications` and `Active project` which are project specific. Values of attributes that are **not** project specific are always used from assets. So if `tools` are not loading as expected it is because the assets have different values. ![anatomy_attributes](assets/settings/anatomy_attributes.png) **Most of attributes don't need detailed explanation.** -| Attribute | Description | -| --- | --- | -| `Applications` | List of applications that can be used in the project. At the moment only possible filter of applications. | -| `Tools` | List of application tools. This value can be overridden per asset. | +| Attribute | Description | +| --- |-----------------------------------------------------------------------------------------------------------------------------| +| `Applications` | List of applications that can be used in the project. At the moment used only as a possible filter of applications. | +| `Tools` | List of application tools. This value can be overridden per asset. | | `Active project` | Project won't be visible in tools if enabled.
- To revert check `Show Inactive projects` checkbox in project settings. | ## Task Types -Available task types on a project. Each task on an asset is referencing to a task type on project which allows access to task type attributes. At this moment only `short_name` is available (can be used in templates as `{task[short_name]}`). +Available task types on a project. Each task on an asset is referencing a task type on the project which allows access to additional task type attributes. At this moment only `short_name` is available (can be used in templates as `{task[short_name]}`). ![tasks](assets/settings/anatomy_tasks.png) From 585d43cf48497982be916ac7abc45141f47ff8a4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 2 Sep 2022 13:40:03 +0200 Subject: [PATCH 227/274] modified table --- website/docs/admin_settings_project_anatomy.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/website/docs/admin_settings_project_anatomy.md b/website/docs/admin_settings_project_anatomy.md index b2a3d9f969..2068c5cde2 100644 --- a/website/docs/admin_settings_project_anatomy.md +++ b/website/docs/admin_settings_project_anatomy.md @@ -120,10 +120,10 @@ Project attributes are used as default values for new assets created under proje **Most of attributes don't need detailed explanation.** -| Attribute | Description | -| --- |-----------------------------------------------------------------------------------------------------------------------------| -| `Applications` | List of applications that can be used in the project. At the moment used only as a possible filter of applications. | -| `Tools` | List of application tools. This value can be overridden per asset. | +| Attribute | Description | +| --- | --- | +| `Applications` | List of applications that can be used in the project. At the moment used only as a possible filter of applications. | +| `Tools` | List of application tools. This value can be overridden per asset. | | `Active project` | Project won't be visible in tools if enabled.
- To revert check `Show Inactive projects` checkbox in project settings. | From 37371936cf293200b74b7d5ee5381aac8a0551ba Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 2 Sep 2022 14:08:40 +0200 Subject: [PATCH 228/274] safer task data --- .../plugins/publish/integrate_slack_api.py | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index 4a8e9f773f..643e55915b 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -112,13 +112,19 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): if review_path: fill_pairs.append(("review_filepath", review_path)) - task_data = instance.data.get("task") - if not task_data: - task_data = fill_data.get("task") - for key, value in task_data.items(): - fill_key = "task[{}]".format(key) - fill_pairs.append((fill_key, value)) - fill_pairs.append(("task", task_data["name"])) + task_data = fill_data.get("task") + if task_data: + if ( + "{task}" in message_templ + or "{Task}" in message_templ + or "{TASK}" in message_templ + ): + fill_pairs.append(("task", task_data["name"])) + + else: + for key, value in task_data.items(): + fill_key = "task[{}]".format(key) + fill_pairs.append((fill_key, value)) self.log.debug("fill_pairs ::{}".format(fill_pairs)) multiple_case_variants = prepare_template_data(fill_pairs) From fc8d525207216b7ce8a47499f2419e1febd56f13 Mon Sep 17 00:00:00 2001 From: Hayley GUILLOT Date: Fri, 2 Sep 2022 14:14:33 +0200 Subject: [PATCH 229/274] Added an event triggered when user connects to kitsu --- openpype/modules/kitsu/utils/credentials.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/openpype/modules/kitsu/utils/credentials.py b/openpype/modules/kitsu/utils/credentials.py index 0529380d6d..b80852dd6a 100644 --- a/openpype/modules/kitsu/utils/credentials.py +++ b/openpype/modules/kitsu/utils/credentials.py @@ -5,6 +5,7 @@ from typing import Tuple import gazu from openpype.lib.local_settings import OpenPypeSecureRegistry +from openpype.lib import emit_event def validate_credentials( @@ -32,6 +33,7 @@ def validate_credentials( except gazu.exception.AuthFailedException: return False + emit_on_kitsu_login(login) return True @@ -102,3 +104,13 @@ def set_credentials_envs(login: str, password: str): """ os.environ["KITSU_LOGIN"] = login os.environ["KITSU_PWD"] = password + +def emit_on_kitsu_login(login:str): + """Notifies listeners that Kitsu module succesfully connected, + and passes them data + + Args: + login (str): Kitsu username + """ + event_data = {"username": login} + emit_event("kitsu.user.logged", data = event_data, source = "kitsu") \ No newline at end of file From 75e2ec1a65316a0da8fa6b72530e684b6a77b4fe Mon Sep 17 00:00:00 2001 From: Hayley GUILLOT Date: Fri, 2 Sep 2022 15:44:47 +0200 Subject: [PATCH 230/274] Update credentials.py Linted, should fix hounds comments --- openpype/modules/kitsu/utils/credentials.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/openpype/modules/kitsu/utils/credentials.py b/openpype/modules/kitsu/utils/credentials.py index b80852dd6a..a3b90a992b 100644 --- a/openpype/modules/kitsu/utils/credentials.py +++ b/openpype/modules/kitsu/utils/credentials.py @@ -8,9 +8,7 @@ from openpype.lib.local_settings import OpenPypeSecureRegistry from openpype.lib import emit_event -def validate_credentials( - login: str, password: str, kitsu_url: str = None -) -> bool: +def validate_credentials(login: str, password: str, kitsu_url: str = None) -> bool: """Validate credentials by trying to connect to Kitsu host URL. Args: @@ -105,12 +103,13 @@ def set_credentials_envs(login: str, password: str): os.environ["KITSU_LOGIN"] = login os.environ["KITSU_PWD"] = password -def emit_on_kitsu_login(login:str): - """Notifies listeners that Kitsu module succesfully connected, + +def emit_on_kitsu_login(login: str): + """Notifies listeners that Kitsu module succesfully connected, and passes them data Args: login (str): Kitsu username """ event_data = {"username": login} - emit_event("kitsu.user.logged", data = event_data, source = "kitsu") \ No newline at end of file + emit_event("kitsu.user.logged", data=event_data, source="kitsu") From 7a98e083de1f151d686601ca620c9432a1d756b8 Mon Sep 17 00:00:00 2001 From: Hayley GUILLOT Date: Fri, 2 Sep 2022 15:48:23 +0200 Subject: [PATCH 231/274] Shortened max line length to 79 Hopefully hounds will be happy --- openpype/modules/kitsu/utils/credentials.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/utils/credentials.py b/openpype/modules/kitsu/utils/credentials.py index a3b90a992b..d853d5b437 100644 --- a/openpype/modules/kitsu/utils/credentials.py +++ b/openpype/modules/kitsu/utils/credentials.py @@ -8,7 +8,9 @@ from openpype.lib.local_settings import OpenPypeSecureRegistry from openpype.lib import emit_event -def validate_credentials(login: str, password: str, kitsu_url: str = None) -> bool: +def validate_credentials( + login: str, password: str, kitsu_url: str = None +) -> bool: """Validate credentials by trying to connect to Kitsu host URL. Args: From 74a404021975c52d6e22bf3ea5125fa23f9bcc3f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 2 Sep 2022 15:49:18 +0200 Subject: [PATCH 232/274] Revert "Fix - update cx-freeze" This reverts commit 7ed5f5a0ee56d6481d28f0510d1bc6311e89ac38. --- poetry.lock | 990 +++++++------------------------------------------ pyproject.toml | 3 +- 2 files changed, 129 insertions(+), 864 deletions(-) diff --git a/poetry.lock b/poetry.lock index b428393a1f..726b248f8c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -33,7 +33,7 @@ typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} yarl = ">=1.0,<2.0" [package.extras] -speedups = ["Brotli", "aiodns", "cchardet"] +speedups = ["aiodns", "brotli", "cchardet"] [[package]] name = "aiohttp-json-rpc" @@ -122,7 +122,6 @@ python-versions = ">=3.6.2" [package.dependencies] lazy-object-proxy = ">=1.4.0" -setuptools = ">=20.0" typed-ast = {version = ">=1.4.0,<2.0", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""} typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""} wrapt = ">=1.11,<2" @@ -163,10 +162,10 @@ optional = false python-versions = ">=3.5" [package.extras] -dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] -docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] -tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] -tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "cloudpickle"] [[package]] name = "autopep8" @@ -274,9 +273,9 @@ optional = false python-versions = ">=2.7, <4.0" [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)"] +dev = ["sphinx (>=2,<4)", "sphinx-rtd-theme (>=0.1.6,<1)", "lowdown (>=0.2.0,<1)", "pytest-runner (>=2.7,<3)", "pytest (>=2.3.5,<5)", "pytest-cov (>=2,<3)"] +doc = ["sphinx (>=2,<4)", "sphinx-rtd-theme (>=0.1.6,<1)", "lowdown (>=0.2.0,<1)"] +test = ["pytest-runner (>=2.7,<3)", "pytest (>=2.3.5,<5)", "pytest-cov (>=2,<3)"] [[package]] name = "colorama" @@ -331,39 +330,30 @@ python-versions = ">=3.6" cffi = ">=1.12" [package.extras] -docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx_rtd_theme"] -docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] +docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] sdist = ["setuptools_rust (>=0.11.4)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"] +test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] [[package]] -name = "cx-Freeze" +name = "cx-freeze" version = "6.11.1" description = "Create standalone executables from Python scripts" -category = "main" +category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] -cx-logging = {version = ">=3.0", markers = "sys_platform == \"win32\" and python_version < \"3.10\""} -importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.10\""} -lief = {version = ">=0.11.5", markers = "sys_platform == \"win32\" and python_version <= \"3.10\""} -packaging = ">=21.0" -patchelf = {version = ">=0.12", markers = "sys_platform == \"linux\""} -setuptools = ">=59.0.1,<=60.10.0" - -[package.extras] -dev = ["bump2version (>=1.0.1)", "cibuildwheel (==2.6.1)", "pre-commit (>=2.17.0)", "pylint (>=2.13.0)"] -doc = ["sphinx (>=5.0.1,<6.0.0)", "sphinx-rtd-theme (==1.0.0)"] -test = ["nose (==1.3.7)", "pygments (>=2.11.2)", "pytest (>=7.0.1)", "pytest-cov (==3.0.0)", "pytest-mock (>=3.6.1)", "pytest-timeout (>=1.4.2)"] +cx-logging = {version = ">=3.0", markers = "sys_platform == \"win32\""} +importlib-metadata = ">=4.3.1" [[package]] name = "cx-logging" version = "3.0" description = "Python and C interfaces for logging" -category = "main" +category = "dev" optional = false python-versions = "*" @@ -379,7 +369,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" wrapt = ">=1.10,<2" [package.extras] -dev = ["PyTest", "PyTest (<5)", "PyTest-Cov", "PyTest-Cov (<2.6)", "bump2version (<1)", "configparser (<5)", "importlib-metadata (<3)", "importlib-resources (<4)", "sphinx (<2)", "sphinxcontrib-websupport (<2)", "tox", "zipp (<2)"] +dev = ["tox", "bump2version (<1)", "sphinx (<2)", "importlib-metadata (<3)", "importlib-resources (<4)", "configparser (<5)", "sphinxcontrib-websupport (<2)", "zipp (<2)", "PyTest (<5)", "PyTest-Cov (<2.6)", "pytest", "pytest-cov"] [[package]] name = "dill" @@ -401,8 +391,8 @@ optional = false python-versions = ">=3.6,<4.0" [package.extras] -curio = ["curio (>=1.2,<2.0)", "sniffio (>=1.1,<2.0)"] dnssec = ["cryptography (>=2.6,<37.0)"] +curio = ["curio (>=1.2,<2.0)", "sniffio (>=1.1,<2.0)"] doh = ["h2 (>=4.1.0)", "httpx (>=0.21.1)", "requests (>=2.23.0,<3.0.0)", "requests-toolbelt (>=0.9.1,<0.10.0)"] idna = ["idna (>=2.1,<4.0)"] trio = ["trio (>=0.14,<0.20)"] @@ -513,7 +503,7 @@ requests = ">=2.25.1,<=2.27.1" [package.extras] dev = ["wheel"] -test = ["black (==21.12b0)", "pre-commit (==2.17.0)", "pytest (==4.6.11)", "pytest (==6.1.2)", "pytest (==6.2.5)", "pytest-cov (==2.12.1)", "requests-mock (==1.9.3)"] +test = ["pytest-cov (==2.12.1)", "requests-mock (==1.9.3)", "pytest (==4.6.11)", "pytest (==6.1.2)", "pytest (==6.2.5)", "black (==21.12b0)", "pre-commit (==2.17.0)"] [[package]] name = "gitdb" @@ -586,7 +576,7 @@ rsa = {version = ">=3.1.4,<5", markers = "python_version >= \"3.6\""} six = ">=1.9.0" [package.extras] -aiohttp = ["aiohttp (>=3.6.2,<4.0.0dev)", "requests (>=2.20.0,<3.0.0dev)"] +aiohttp = ["requests (>=2.20.0,<3.0.0dev)", "aiohttp (>=3.6.2,<4.0.0dev)"] enterprise_cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"] pyopenssl = ["pyopenssl (>=20.0.0)"] reauth = ["pyu2f (>=0.1.5)"] @@ -658,9 +648,9 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["jaraco.packaging (>=9)", "rst.linker (>=1.9)", "sphinx"] +docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] [[package]] name = "iniconfig" @@ -679,10 +669,10 @@ optional = false python-versions = ">=3.6.1,<4.0" [package.extras] -colors = ["colorama (>=0.4.3,<0.5.0)"] pipfile_deprecated_finder = ["pipreqs", "requirementslib"] +requirements_deprecated_finder = ["pipreqs", "pip-api"] +colors = ["colorama (>=0.4.3,<0.5.0)"] plugins = ["setuptools"] -requirements_deprecated_finder = ["pip-api", "pipreqs"] [[package]] name = "jedi" @@ -707,8 +697,8 @@ optional = false python-versions = ">=3.7" [package.extras] -test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] -trio = ["async_generator", "trio"] +trio = ["async-generator", "trio"] +test = ["async-timeout", "trio", "testpath", "pytest-asyncio (>=0.17)", "pytest-trio", "pytest"] [[package]] name = "jinja2" @@ -761,8 +751,8 @@ pywin32-ctypes = {version = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1", markers = "sys_ SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} [package.extras] -docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] -testing = ["pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=1.2.3)", "pytest-cov", "pytest-enabler", "pytest-flake8", "pytest-mypy"] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "pytest-black (>=0.3.7)", "pytest-mypy"] [[package]] name = "lazy-object-proxy" @@ -772,14 +762,6 @@ category = "dev" optional = false python-versions = ">=3.6" -[[package]] -name = "lief" -version = "0.12.1" -description = "Library to instrument executable formats" -category = "main" -optional = false -python-versions = ">=3.6" - [[package]] name = "log4mongo" version = "1.7.0" @@ -827,7 +809,7 @@ python-versions = "*" pyaaf2 = "1.4.0" [package.extras] -dev = ["check-manifest", "coverage (>=4.5)", "flake8 (>=3.5)", "urllib3 (>=1.24.3)"] +dev = ["check-manifest", "flake8 (>=3.5)", "coverage (>=4.5)", "urllib3 (>=1.24.3)"] view = ["PySide2 (>=5.11,<6.0)"] [package.source] @@ -839,7 +821,7 @@ reference = "openpype" name = "packaging" version = "21.3" description = "Core utilities for Python packages" -category = "main" +category = "dev" optional = false python-versions = ">=3.6" @@ -861,9 +843,9 @@ pynacl = ">=1.0.1" six = "*" [package.extras] -all = ["bcrypt (>=3.1.3)", "gssapi (>=1.4.1)", "invoke (>=1.3)", "pyasn1 (>=0.1.7)", "pynacl (>=1.0.1)", "pywin32 (>=2.1.8)"] -ed25519 = ["bcrypt (>=3.1.3)", "pynacl (>=1.0.1)"] -gssapi = ["gssapi (>=1.4.1)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] +all = ["pyasn1 (>=0.1.7)", "pynacl (>=1.0.1)", "bcrypt (>=3.1.3)", "invoke (>=1.3)", "gssapi (>=1.4.1)", "pywin32 (>=2.1.8)"] +ed25519 = ["pynacl (>=1.0.1)", "bcrypt (>=3.1.3)"] +gssapi = ["pyasn1 (>=0.1.7)", "gssapi (>=1.4.1)", "pywin32 (>=2.1.8)"] invoke = ["invoke (>=1.3)"] [[package]] @@ -878,17 +860,6 @@ python-versions = ">=3.6" qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] testing = ["docopt", "pytest (<6.0.0)"] -[[package]] -name = "patchelf" -version = "0.15.0.0" -description = "A small utility to modify the dynamic linker and RPATH of ELF executables." -category = "main" -optional = false -python-versions = "*" - -[package.extras] -test = ["importlib-metadata", "pytest"] - [[package]] name = "pathlib2" version = "2.3.7.post1" @@ -921,8 +892,8 @@ optional = false python-versions = ">=3.7" [package.extras] -docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)"] -test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] +docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] +test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] [[package]] name = "pluggy" @@ -1083,7 +1054,7 @@ python-versions = "*" aws = ["pymongo-auth-aws (<2.0.0)"] encryption = ["pymongocrypt (>=1.1.0,<2.0.0)"] gssapi = ["pykerberos"] -ocsp = ["certifi", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] +ocsp = ["pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)", "certifi"] snappy = ["python-snappy"] srv = ["dnspython (>=1.16.0,<1.17.0)"] tls = ["ipaddress"] @@ -1101,8 +1072,8 @@ python-versions = ">=3.6" cffi = ">=1.4.1" [package.extras] -docs = ["sphinx (>=1.6.5)", "sphinx_rtd_theme"] -tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] +docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] +tests = ["pytest (>=3.2.1,!=3.3.0)", "hypothesis (>=3.27.0)"] [[package]] name = "pynput" @@ -1217,7 +1188,7 @@ coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" [package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] +testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] [[package]] name = "pytest-print" @@ -1410,19 +1381,6 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -[[package]] -name = "setuptools" -version = "60.10.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx", "sphinx-favicon", "sphinx-inline-tabs", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-xdist", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - [[package]] name = "shotgun-api3" version = "3.3.3" @@ -1455,8 +1413,8 @@ optional = false python-versions = ">=3.6.0" [package.extras] -optional = ["SQLAlchemy (>=1,<2)", "aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "websocket-client (>=1,<2)", "websockets (>=10,<11)"] -testing = ["Flask (>=1,<2)", "Flask-Sockets (>=0.2,<1)", "Jinja2 (==3.0.3)", "Werkzeug (<2)", "black (==22.3.0)", "boto3 (<=2)", "click (==8.0.4)", "codecov (>=2,<3)", "databases (>=0.5)", "flake8 (>=4,<5)", "itsdangerous (==1.1.0)", "moto (>=3,<4)", "psutil (>=5,<6)", "pytest (>=6.2.5,<7)", "pytest-asyncio (<1)", "pytest-cov (>=2,<3)"] +optional = ["aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "SQLAlchemy (>=1,<2)", "websockets (>=10,<11)", "websocket-client (>=1,<2)"] +testing = ["pytest (>=6.2.5,<7)", "pytest-asyncio (<1)", "Flask-Sockets (>=0.2,<1)", "Flask (>=1,<2)", "Werkzeug (<2)", "itsdangerous (==1.1.0)", "Jinja2 (==3.0.3)", "pytest-cov (>=2,<3)", "codecov (>=2,<3)", "flake8 (>=4,<5)", "black (==22.3.0)", "click (==8.0.4)", "psutil (>=5,<6)", "databases (>=0.5)", "boto3 (<=2)", "moto (>=3,<4)"] [[package]] name = "smmap" @@ -1511,8 +1469,8 @@ sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["docutils-stubs", "flake8 (>=3.5.0)", "isort", "mypy (>=0.950)", "types-requests", "types-typed-ast"] -test = ["cython", "html5lib", "pytest (>=4.6)", "typed-ast"] +lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.950)", "docutils-stubs", "types-typed-ast", "types-requests"] +test = ["pytest (>=4.6)", "html5lib", "cython", "typed-ast"] [[package]] name = "sphinx-qt-documentation" @@ -1527,9 +1485,9 @@ docutils = "*" sphinx = "*" [package.extras] +test = ["pytest-cov", "pytest (>=3.0.0)"] +lint = ["pylint", "flake8", "black"] dev = ["pre-commit"] -lint = ["black", "flake8", "pylint"] -test = ["pytest (>=3.0.0)", "pytest-cov"] [[package]] name = "sphinx-rtd-theme" @@ -1544,7 +1502,7 @@ docutils = "<0.18" sphinx = ">=1.6" [package.extras] -dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client"] +dev = ["transifex-client", "sphinxcontrib-httpdomain", "bump2version"] [[package]] name = "sphinxcontrib-applehelp" @@ -1555,7 +1513,7 @@ optional = false python-versions = ">=3.5" [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] +lint = ["flake8", "mypy", "docutils-stubs"] test = ["pytest"] [[package]] @@ -1567,7 +1525,7 @@ optional = false python-versions = ">=3.5" [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] +lint = ["flake8", "mypy", "docutils-stubs"] test = ["pytest"] [[package]] @@ -1579,8 +1537,8 @@ optional = false python-versions = ">=3.6" [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["html5lib", "pytest"] +lint = ["flake8", "mypy", "docutils-stubs"] +test = ["pytest", "html5lib"] [[package]] name = "sphinxcontrib-jsmath" @@ -1591,7 +1549,7 @@ optional = false python-versions = ">=3.5" [package.extras] -test = ["flake8", "mypy", "pytest"] +test = ["pytest", "flake8", "mypy"] [[package]] name = "sphinxcontrib-qthelp" @@ -1602,7 +1560,7 @@ optional = false python-versions = ">=3.5" [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] +lint = ["flake8", "mypy", "docutils-stubs"] test = ["pytest"] [[package]] @@ -1614,7 +1572,7 @@ optional = false python-versions = ">=3.5" [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] +lint = ["flake8", "mypy", "docutils-stubs"] test = ["pytest"] [[package]] @@ -1630,7 +1588,7 @@ sphinxcontrib-serializinghtml = "*" [package.extras] lint = ["flake8"] -test = ["Sphinx", "pytest", "sqlalchemy", "whoosh"] +test = ["pytest", "sqlalchemy", "whoosh", "sphinx"] [[package]] name = "stone" @@ -1701,8 +1659,8 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)"] +brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] @@ -1724,17 +1682,6 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] six = "*" -[[package]] -name = "wheel" -version = "0.37.1" -description = "A built-package format for Python" -category = "dev" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" - -[package.extras] -test = ["pytest (>=3.0.0)", "pytest-cov"] - [[package]] name = "wrapt" version = "1.14.1" @@ -1756,8 +1703,8 @@ 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"] +develop = ["async-timeout", "coverage (!=4.3)", "coveralls", "pytest", "pytest-aiohttp", "pytest-cov", "sphinx", "sphinxcontrib-plantuml", "tox (>=2.4)"] +testing = ["async-timeout", "pytest", "pytest-aiohttp", "pytest-cov", "coverage (!=4.3)", "coveralls"] ujson = ["ujson"] [[package]] @@ -1782,13 +1729,13 @@ optional = false python-versions = ">=3.7" [package.extras] -docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"] -testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "jaraco.tidelift (>=1.4)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] [metadata] lock-version = "1.1" python-versions = "3.7.*" -content-hash = "6a5f6910109c9ed6fb1cc1cb1cef7b21a0055a17c393175a1a7aabc00e35d54f" +content-hash = "de7422afb6aed02f75e1696afdda9ad6c7bf32da76b5022ee3e8f71a1ac4bae2" [metadata.files] acre = [] @@ -1870,10 +1817,7 @@ aiohttp-json-rpc = [ {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"}, ] -aiohttp-middlewares = [ - {file = "aiohttp-middlewares-2.1.0.tar.gz", hash = "sha256:5863970d944dc63faedc96ef324a7fe2bcefefebe29acc90cd641236322d00c3"}, - {file = "aiohttp_middlewares-2.1.0-py3-none-any.whl", hash = "sha256:c83d48702e6a8669981976f39a60e83d059dc01d7b1ee651aec5d4cb807ff784"}, -] +aiohttp-middlewares = [] aiosignal = [ {file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"}, {file = "aiosignal-1.2.0.tar.gz", hash = "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2"}, @@ -1891,10 +1835,7 @@ arrow = [ {file = "arrow-0.17.0-py2.py3-none-any.whl", hash = "sha256:e098abbd9af3665aea81bdd6c869e93af4feb078e98468dd351c383af187aac5"}, {file = "arrow-0.17.0.tar.gz", hash = "sha256:ff08d10cda1d36c68657d6ad20d74fbea493d980f8b2d45344e00d6ed2bf6ed4"}, ] -astroid = [ - {file = "astroid-2.11.7-py3-none-any.whl", hash = "sha256:86b0a340a512c65abf4368b80252754cda17c02cdbbd3f587dddf98112233e7b"}, - {file = "astroid-2.11.7.tar.gz", hash = "sha256:bb24615c77f4837c707669d16907331374ae8a964650a66999da3f5ca68dc946"}, -] +astroid = [] async-timeout = [ {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, @@ -1903,112 +1844,21 @@ asynctest = [ {file = "asynctest-0.13.0-py3-none-any.whl", hash = "sha256:5da6118a7e6d6b54d83a8f7197769d046922a44d2a99c21382f0a6e4fadae676"}, {file = "asynctest-0.13.0.tar.gz", hash = "sha256:c27862842d15d83e6a34eb0b2866c323880eb3a75e4485b079ea11748fd77fac"}, ] -atomicwrites = [ - {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, -] -attrs = [ - {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, - {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, -] +atomicwrites = [] +attrs = [] autopep8 = [ {file = "autopep8-1.5.7-py2.py3-none-any.whl", hash = "sha256:aa213493c30dcdac99537249ee65b24af0b2c29f2e83cd8b3f68760441ed0db9"}, {file = "autopep8-1.5.7.tar.gz", hash = "sha256:276ced7e9e3cb22e5d7c14748384a5cf5d9002257c0ed50c0e075b68011bb6d0"}, ] -babel = [ - {file = "Babel-2.10.3-py3-none-any.whl", hash = "sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb"}, - {file = "Babel-2.10.3.tar.gz", hash = "sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51"}, -] -bcrypt = [ - {file = "bcrypt-3.2.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:7180d98a96f00b1050e93f5b0f556e658605dd9f524d0b0e68ae7944673f525e"}, - {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:61bae49580dce88095d669226d5076d0b9d927754cedbdf76c6c9f5099ad6f26"}, - {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88273d806ab3a50d06bc6a2fc7c87d737dd669b76ad955f449c43095389bc8fb"}, - {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6d2cb9d969bfca5bc08e45864137276e4c3d3d7de2b162171def3d188bf9d34a"}, - {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b02d6bfc6336d1094276f3f588aa1225a598e27f8e3388f4db9948cb707b521"}, - {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a2c46100e315c3a5b90fdc53e429c006c5f962529bc27e1dfd656292c20ccc40"}, - {file = "bcrypt-3.2.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7d9ba2e41e330d2af4af6b1b6ec9e6128e91343d0b4afb9282e54e5508f31baa"}, - {file = "bcrypt-3.2.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cd43303d6b8a165c29ec6756afd169faba9396a9472cdff753fe9f19b96ce2fa"}, - {file = "bcrypt-3.2.2-cp36-abi3-win32.whl", hash = "sha256:4e029cef560967fb0cf4a802bcf4d562d3d6b4b1bf81de5ec1abbe0f1adb027e"}, - {file = "bcrypt-3.2.2-cp36-abi3-win_amd64.whl", hash = "sha256:7ff2069240c6bbe49109fe84ca80508773a904f5a8cb960e02a977f7f519b129"}, - {file = "bcrypt-3.2.2.tar.gz", hash = "sha256:433c410c2177057705da2a9f2cd01dd157493b2a7ac14c8593a16b3dab6b6bfb"}, -] +babel = [] +bcrypt = [] blessed = [ {file = "blessed-1.19.1-py2.py3-none-any.whl", hash = "sha256:63b8554ae2e0e7f43749b6715c734cc8f3883010a809bf16790102563e6cf25b"}, {file = "blessed-1.19.1.tar.gz", hash = "sha256:9a0d099695bf621d4680dd6c73f6ad547f6a3442fbdbe80c4b1daa1edbc492fc"}, ] -cachetools = [ - {file = "cachetools-5.2.0-py3-none-any.whl", hash = "sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db"}, - {file = "cachetools-5.2.0.tar.gz", hash = "sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757"}, -] -certifi = [ - {file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"}, - {file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"}, -] -cffi = [ - {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, - {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, - {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, - {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, - {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, - {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, - {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, - {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, - {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, - {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, - {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, - {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, - {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, - {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, - {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, - {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, - {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, - {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, - {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, -] +cachetools = [] +certifi = [] +cffi = [] charset-normalizer = [ {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, @@ -2021,10 +1871,7 @@ clique = [ {file = "clique-1.6.1-py2.py3-none-any.whl", hash = "sha256:8619774fa035661928dd8c93cd805acf2d42533ccea1b536c09815ed426c9858"}, {file = "clique-1.6.1.tar.gz", hash = "sha256:90165c1cf162d4dd1baef83ceaa1afc886b453e379094fa5b60ea470d1733e66"}, ] -colorama = [ - {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, - {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, -] +colorama = [] commonmark = [ {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, @@ -2033,99 +1880,20 @@ coolname = [ {file = "coolname-1.1.0-py2.py3-none-any.whl", hash = "sha256:e6a83a0ac88640f4f3d2070438dbe112fe80cfebc119c93bd402976ec84c0978"}, {file = "coolname-1.1.0.tar.gz", hash = "sha256:410fe6ea9999bf96f2856ef0c726d5f38782bbefb7bb1aca0e91e0dc98ed09e3"}, ] -coverage = [ - {file = "coverage-6.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f50d3a822947572496ea922ee7825becd8e3ae6fbd2400cd8236b7d64b17f285"}, - {file = "coverage-6.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d5191d53afbe5b6059895fa7f58223d3751c42b8101fb3ce767e1a0b1a1d8f87"}, - {file = "coverage-6.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04010af3c06ce2bfeb3b1e4e05d136f88d88c25f76cd4faff5d1fd84d11581ea"}, - {file = "coverage-6.4.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6630d8d943644ea62132789940ca97d05fac83f73186eaf0930ffa715fbdab6b"}, - {file = "coverage-6.4.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05de0762c1caed4a162b3e305f36cf20a548ff4da0be6766ad5c870704be3660"}, - {file = "coverage-6.4.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e3a41aad5919613483aad9ebd53336905cab1bd6788afd3995c2a972d89d795"}, - {file = "coverage-6.4.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a2738ba1ee544d6f294278cfb6de2dc1f9a737a780469b5366e662a218f806c3"}, - {file = "coverage-6.4.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a0d2df4227f645a879010461df2cea6b7e3fb5a97d7eafa210f7fb60345af9e8"}, - {file = "coverage-6.4.3-cp310-cp310-win32.whl", hash = "sha256:73a10939dc345460ca0655356a470dd3de9759919186a82383c87b6eb315faf2"}, - {file = "coverage-6.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:53c8edd3b83a4ddba3d8c506f1359401e7770b30f2188f15c17a338adf5a14db"}, - {file = "coverage-6.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f1eda5cae434282712e40b42aaf590b773382afc3642786ac3ed39053973f61f"}, - {file = "coverage-6.4.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59fc88bc13e30f25167e807b8cad3c41b7218ef4473a20c86fd98a7968733083"}, - {file = "coverage-6.4.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d75314b00825d70e1e34b07396e23f47ed1d4feedc0122748f9f6bd31a544840"}, - {file = "coverage-6.4.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52f8b9fcf3c5e427d51bbab1fb92b575a9a9235d516f175b24712bcd4b5be917"}, - {file = "coverage-6.4.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5a559aab40c716de80c7212295d0dc96bc1b6c719371c20dd18c5187c3155518"}, - {file = "coverage-6.4.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:306788fd019bb90e9cbb83d3f3c6becad1c048dd432af24f8320cf38ac085684"}, - {file = "coverage-6.4.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:920a734fe3d311ca01883b4a19aa386c97b82b69fbc023458899cff0a0d621b9"}, - {file = "coverage-6.4.3-cp37-cp37m-win32.whl", hash = "sha256:ab9ef0187d6c62b09dec83a84a3b94f71f9690784c84fd762fb3cf2d2b44c914"}, - {file = "coverage-6.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:39ebd8e120cb77a06ee3d5fc26f9732670d1c397d7cd3acf02f6f62693b89b80"}, - {file = "coverage-6.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc698580216050b5f4a34d2cdd2838b429c53314f1c4835fab7338200a8396f2"}, - {file = "coverage-6.4.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:877ee5478fd78e100362aed56db47ccc5f23f6e7bb035a8896855f4c3e49bc9b"}, - {file = "coverage-6.4.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:555a498999c44f5287cc95500486cd0d4f021af9162982cbe504d4cb388f73b5"}, - {file = "coverage-6.4.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eff095a5aac7011fdb51a2c82a8fae9ec5211577f4b764e1e59cfa27ceeb1b59"}, - {file = "coverage-6.4.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5de1e9335e2569974e20df0ce31493d315a830d7987e71a24a2a335a8d8459d3"}, - {file = "coverage-6.4.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7856ea39059d75f822ff0df3a51ea6d76307c897048bdec3aad1377e4e9dca20"}, - {file = "coverage-6.4.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:411fdd9f4203afd93b056c0868c8f9e5e16813e765de962f27e4e5798356a052"}, - {file = "coverage-6.4.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cdf7b83f04a313a21afb1f8730fe4dd09577fefc53bbdfececf78b2006f4268e"}, - {file = "coverage-6.4.3-cp38-cp38-win32.whl", hash = "sha256:ab2b1a89d2bc7647622e9eaf06128a5b5451dccf7c242deaa31420b055716481"}, - {file = "coverage-6.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:0e34247274bde982bbc613894d33f9e36358179db2ed231dd101c48dd298e7b0"}, - {file = "coverage-6.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b104b6b1827d6a22483c469e3983a204bcf9c6bf7544bf90362c4654ebc2edf3"}, - {file = "coverage-6.4.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:adf1a0d272633b21d645dd6e02e3293429c1141c7d65a58e4cbcd592d53b8e01"}, - {file = "coverage-6.4.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff9832434a9193fbd716fbe05f9276484e18d26cc4cf850853594bb322807ac3"}, - {file = "coverage-6.4.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:923f9084d7e1d31b5f74c92396b05b18921ed01ee5350402b561a79dce3ea48d"}, - {file = "coverage-6.4.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d64304acf79766e650f7acb81d263a3ea6e2d0d04c5172b7189180ff2c023c"}, - {file = "coverage-6.4.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fc294de50941d3da66a09dca06e206297709332050973eca17040278cb0918ff"}, - {file = "coverage-6.4.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a42eaaae772f14a5194f181740a67bfd48e8806394b8c67aa4399e09d0d6b5db"}, - {file = "coverage-6.4.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4822327b35cb032ff16af3bec27f73985448f08e874146b5b101e0e558b613dd"}, - {file = "coverage-6.4.3-cp39-cp39-win32.whl", hash = "sha256:f217850ac0e046ede611312703423767ca032a7b952b5257efac963942c055de"}, - {file = "coverage-6.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:0a84376e4fd13cebce2c0ef8c2f037929c8307fb94af1e5dbe50272a1c651b5d"}, - {file = "coverage-6.4.3-pp36.pp37.pp38-none-any.whl", hash = "sha256:068d6f2a893af838291b8809c876973d885543411ea460f3e6886ac0ee941732"}, - {file = "coverage-6.4.3.tar.gz", hash = "sha256:ec2ae1f398e5aca655b7084392d23e80efb31f7a660d2eecf569fb9f79b3fb94"}, -] -cryptography = [ - {file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884"}, - {file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6"}, - {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046"}, - {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5"}, - {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b"}, - {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8"}, - {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280"}, - {file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3"}, - {file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59"}, - {file = "cryptography-37.0.4-cp36-abi3-win32.whl", hash = "sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157"}, - {file = "cryptography-37.0.4-cp36-abi3-win_amd64.whl", hash = "sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327"}, - {file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b"}, - {file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9"}, - {file = "cryptography-37.0.4-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67"}, - {file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d"}, - {file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282"}, - {file = "cryptography-37.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa"}, - {file = "cryptography-37.0.4-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441"}, - {file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596"}, - {file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a"}, - {file = "cryptography-37.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab"}, - {file = "cryptography-37.0.4.tar.gz", hash = "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82"}, -] -cx-Freeze = [ - {file = "cx_Freeze-6.11.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e372b9e72ac0e2207ee65a9d404e2669da1134dc37f5ace9a2a779099d3aa868"}, - {file = "cx_Freeze-6.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd293382e1ad270dddf5a2707db5dbb8600a1e0b0c9b0da7af9d61326eb1b325"}, - {file = "cx_Freeze-6.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:feec2f36bce042da6a0d92690bc592b0dcec29218adc2278535cd13b28ec3485"}, - {file = "cx_Freeze-6.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5aafcc6337856d5921b20f41acdcc8d0fe770388f3a072eb25163f8825f6c5d"}, - {file = "cx_Freeze-6.11.1-cp310-cp310-win32.whl", hash = "sha256:b99cc0b6d6c1ba51bd9fe11dbfae9aabcf089ba779ea86d83d280e2e40f484e7"}, - {file = "cx_Freeze-6.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:f0dfe6acf25eb096faba7d4b4b001bcd0f818e372ea1f05d900665b0ad82b0b9"}, - {file = "cx_Freeze-6.11.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3a68e70dcb27b0720b131a35c5fdd096012fe00119a8e51d935f3fb3cd251c39"}, - {file = "cx_Freeze-6.11.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f7bde925042d8843af9b6242a1bf3865dbbae088f3183a89a575124ec2e14a4"}, - {file = "cx_Freeze-6.11.1-cp36-cp36m-win32.whl", hash = "sha256:7698fb82b6f84b3426774b5f3bee770601f26b612306319664a02f1ec5160861"}, - {file = "cx_Freeze-6.11.1-cp36-cp36m-win_amd64.whl", hash = "sha256:9848c975401b21a98aa896baabfed067c3e981afd5b5b0a8a5eabe5c9f23d3c5"}, - {file = "cx_Freeze-6.11.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:87dcf5ceb78dc6af910c45238128fda2394b7c430d3fa469e87e1efdeeb5d4cc"}, - {file = "cx_Freeze-6.11.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb74d8cc1f8c658986acc19ea6875b985a979421f9bb9c310b43cd2ff5d90c44"}, - {file = "cx_Freeze-6.11.1-cp37-cp37m-win32.whl", hash = "sha256:971c0a8356ef0ee09a3097f9c9d5b52cde6d08d1ef80e997eb4a6e22fe0eff2f"}, - {file = "cx_Freeze-6.11.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7c1cb44379b2093cbdde77e302a376f29aa61999c73be6e8a559463db84b85c4"}, - {file = "cx_Freeze-6.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc90d6dbde66e8ddfe6b26f63fb2ea7d6d0e4568205f40660a63b8b200dcabcf"}, - {file = "cx_Freeze-6.11.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f56f618a23d86bdcfff22b29ec993117effd32a401060013105517301c0bf32"}, - {file = "cx_Freeze-6.11.1-cp38-cp38-win32.whl", hash = "sha256:4edfb5d65afb11eb9f0326d40d15445366481585705b3096f2cd090e30a36247"}, - {file = "cx_Freeze-6.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:cfb5a8032bf424c04814c9426425fa1db4cf8c280da948969eead9f616c0fd92"}, - {file = "cx_Freeze-6.11.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0a3e32980269cfabc2e814978bfdf4382fe3cbc9ac64f9f1bdb1cd2ddf3a40d0"}, - {file = "cx_Freeze-6.11.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:798bb7ca037c3c885efd3eda6756c84c7927c712b730b22a7f256440faa36d38"}, - {file = "cx_Freeze-6.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5aa1759098ca4853200a79138b626a9caa2ccf829d662b28c82ec7e71ea97cde"}, - {file = "cx_Freeze-6.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7962680ae04ee3afda1012026b5394a534e2526b68681d591158b7d8bc733bcf"}, - {file = "cx_Freeze-6.11.1-cp39-cp39-win32.whl", hash = "sha256:da4f82fe27e71571c0ab9d700b5e6c6c631ae39133d9b6d7157939f1e9f37312"}, - {file = "cx_Freeze-6.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:aaf399b6ed5d54b7271980ae354605620bedcd52d722f57ad527bd989c56a875"}, - {file = "cx_Freeze-6.11.1.tar.gz", hash = "sha256:8f3a30c9e3394f290655e346d3b460910656b30ac6347a87499bb5ad365c6e7c"}, +coverage = [] +cryptography = [] +cx-freeze = [ + {file = "cx_Freeze-6.9-cp310-cp310-win32.whl", hash = "sha256:776d4fb68a4831691acbd3c374362b9b48ce2e568514a73c3d4cb14d5dcf1470"}, + {file = "cx_Freeze-6.9-cp310-cp310-win_amd64.whl", hash = "sha256:243f36d35a034a409cd6247d8cb5d1fbfd7374e3e668e813d0811f64d6bd5ed3"}, + {file = "cx_Freeze-6.9-cp36-cp36m-win32.whl", hash = "sha256:ffc855eabc735b693e2d604d71dce6d52d78a6ba1070c55d51e786dd68ed232c"}, + {file = "cx_Freeze-6.9-cp36-cp36m-win_amd64.whl", hash = "sha256:fe4e32a0c75b2b54491882926bf3ba12f8a3d589822a68a8be7c09f1dcca5546"}, + {file = "cx_Freeze-6.9-cp37-cp37m-win32.whl", hash = "sha256:99c292e7a31cb343efc0cf47f82220a44a4a3b8776651624cd8ee03c23104940"}, + {file = "cx_Freeze-6.9-cp37-cp37m-win_amd64.whl", hash = "sha256:738ab22f3a3f6bc220b16dccf2aa0603c3cd271b2a7a9d9480dab82311308b23"}, + {file = "cx_Freeze-6.9-cp38-cp38-win32.whl", hash = "sha256:c1c75df572858e623d0aa39771cd984c0abd8aacb43b2aca2d12d0bc95f25566"}, + {file = "cx_Freeze-6.9-cp38-cp38-win_amd64.whl", hash = "sha256:0788c895c47fdcf375151ce78ff42336c01aca7bc43daecb8f8f8356cdc42b43"}, + {file = "cx_Freeze-6.9-cp39-cp39-win32.whl", hash = "sha256:a31f5ddbc80b29e297370d868791470b0e3e9062db45038c23293a76ed039018"}, + {file = "cx_Freeze-6.9-cp39-cp39-win_amd64.whl", hash = "sha256:30708f603076713c0a839cdfb34f4126d68e9d61afb3d9a59daa9cf252033872"}, + {file = "cx_Freeze-6.9.tar.gz", hash = "sha256:673aa3199af2ef87fc03a43a30e5d78b27ced2cedde925da89c55b5657da267b"}, ] cx-logging = [ {file = "cx_Logging-3.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:9fcd297e5c51470521c47eff0f86ba844aeca6be97e13c3e2114ebdf03fa3c96"}, @@ -2142,14 +1910,8 @@ cx-logging = [ {file = "cx_Logging-3.0-cp39-cp39-win_amd64.whl", hash = "sha256:302e9c4f65a936c288a4fa59a90e7e142d9ef994aa29676731acafdcccdbb3f5"}, {file = "cx_Logging-3.0.tar.gz", hash = "sha256:ba8a7465facf7b98d8f494030fb481a2e8aeee29dc191e10383bb54ed42bdb34"}, ] -deprecated = [ - {file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"}, - {file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"}, -] -dill = [ - {file = "dill-0.3.5.1-py2.py3-none-any.whl", hash = "sha256:33501d03270bbe410c72639b350e941882a8b0fd55357580fbc873fba0c59302"}, - {file = "dill-0.3.5.1.tar.gz", hash = "sha256:d75e41f3eff1eee599d738e76ba8f4ad98ea229db8b085318aa2b3333a208c86"}, -] +deprecated = [] +dill = [] dnspython = [ {file = "dnspython-2.2.1-py3-none-any.whl", hash = "sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f"}, {file = "dnspython-2.2.1.tar.gz", hash = "sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e"}, @@ -2158,93 +1920,22 @@ docutils = [ {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, ] -dropbox = [ - {file = "dropbox-11.33.0-py2-none-any.whl", hash = "sha256:3ee9024631b80f18938556d5e27cbdede26d6dc0b73aeaa90fc075ce96c950b1"}, - {file = "dropbox-11.33.0-py3-none-any.whl", hash = "sha256:1a0cbc22b0d1dae96e18b37e3520e5c289de7eb1303935db40e4dbfc9bb9e59b"}, - {file = "dropbox-11.33.0.tar.gz", hash = "sha256:7c638b521169a460de38b9eaeb204fe918874f72d6c3eed005d064b6f37da9c1"}, -] +dropbox = [] enlighten = [ {file = "enlighten-1.10.2-py2.py3-none-any.whl", hash = "sha256:b237fe562b320bf9f1d4bb76d0c98e0daf914372a76ab87c35cd02f57aa9d8c1"}, {file = "enlighten-1.10.2.tar.gz", hash = "sha256:7a5b83cd0f4d095e59d80c648ebb5f7ffca0cd8bcf7ae6639828ee1ad000632a"}, ] -evdev = [ - {file = "evdev-1.6.0.tar.gz", hash = "sha256:ecfa01b5c84f7e8c6ced3367ac95288f43cd84efbfd7dd7d0cdbfc0d18c87a6a"}, -] +evdev = [] flake8 = [ {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, ] -frozenlist = [ - {file = "frozenlist-1.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5f271c93f001748fc26ddea409241312a75e13466b06c94798d1a341cf0e6989"}, - {file = "frozenlist-1.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9c6ef8014b842f01f5d2b55315f1af5cbfde284eb184075c189fd657c2fd8204"}, - {file = "frozenlist-1.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:219a9676e2eae91cb5cc695a78b4cb43d8123e4160441d2b6ce8d2c70c60e2f3"}, - {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b47d64cdd973aede3dd71a9364742c542587db214e63b7529fbb487ed67cddd9"}, - {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2af6f7a4e93f5d08ee3f9152bce41a6015b5cf87546cb63872cc19b45476e98a"}, - {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a718b427ff781c4f4e975525edb092ee2cdef6a9e7bc49e15063b088961806f8"}, - {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c56c299602c70bc1bb5d1e75f7d8c007ca40c9d7aebaf6e4ba52925d88ef826d"}, - {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:717470bfafbb9d9be624da7780c4296aa7935294bd43a075139c3d55659038ca"}, - {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:31b44f1feb3630146cffe56344704b730c33e042ffc78d21f2125a6a91168131"}, - {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c3b31180b82c519b8926e629bf9f19952c743e089c41380ddca5db556817b221"}, - {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:d82bed73544e91fb081ab93e3725e45dd8515c675c0e9926b4e1f420a93a6ab9"}, - {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49459f193324fbd6413e8e03bd65789e5198a9fa3095e03f3620dee2f2dabff2"}, - {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:94e680aeedc7fd3b892b6fa8395b7b7cc4b344046c065ed4e7a1e390084e8cb5"}, - {file = "frozenlist-1.3.1-cp310-cp310-win32.whl", hash = "sha256:fabb953ab913dadc1ff9dcc3a7a7d3dc6a92efab3a0373989b8063347f8705be"}, - {file = "frozenlist-1.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:eee0c5ecb58296580fc495ac99b003f64f82a74f9576a244d04978a7e97166db"}, - {file = "frozenlist-1.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0bc75692fb3770cf2b5856a6c2c9de967ca744863c5e89595df64e252e4b3944"}, - {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086ca1ac0a40e722d6833d4ce74f5bf1aba2c77cbfdc0cd83722ffea6da52a04"}, - {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b51eb355e7f813bcda00276b0114c4172872dc5fb30e3fea059b9367c18fbcb"}, - {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:74140933d45271c1a1283f708c35187f94e1256079b3c43f0c2267f9db5845ff"}, - {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee4c5120ddf7d4dd1eaf079af3af7102b56d919fa13ad55600a4e0ebe532779b"}, - {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97d9e00f3ac7c18e685320601f91468ec06c58acc185d18bb8e511f196c8d4b2"}, - {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6e19add867cebfb249b4e7beac382d33215d6d54476bb6be46b01f8cafb4878b"}, - {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a027f8f723d07c3f21963caa7d585dcc9b089335565dabe9c814b5f70c52705a"}, - {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:61d7857950a3139bce035ad0b0945f839532987dfb4c06cfe160254f4d19df03"}, - {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:53b2b45052e7149ee8b96067793db8ecc1ae1111f2f96fe1f88ea5ad5fd92d10"}, - {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bbb1a71b1784e68870800b1bc9f3313918edc63dbb8f29fbd2e767ce5821696c"}, - {file = "frozenlist-1.3.1-cp37-cp37m-win32.whl", hash = "sha256:ab6fa8c7871877810e1b4e9392c187a60611fbf0226a9e0b11b7b92f5ac72792"}, - {file = "frozenlist-1.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f89139662cc4e65a4813f4babb9ca9544e42bddb823d2ec434e18dad582543bc"}, - {file = "frozenlist-1.3.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:4c0c99e31491a1d92cde8648f2e7ccad0e9abb181f6ac3ddb9fc48b63301808e"}, - {file = "frozenlist-1.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:61e8cb51fba9f1f33887e22488bad1e28dd8325b72425f04517a4d285a04c519"}, - {file = "frozenlist-1.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc2f3e368ee5242a2cbe28323a866656006382872c40869b49b265add546703f"}, - {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58fb94a01414cddcdc6839807db77ae8057d02ddafc94a42faee6004e46c9ba8"}, - {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:022178b277cb9277d7d3b3f2762d294f15e85cd2534047e68a118c2bb0058f3e"}, - {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:572ce381e9fe027ad5e055f143763637dcbac2542cfe27f1d688846baeef5170"}, - {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19127f8dcbc157ccb14c30e6f00392f372ddb64a6ffa7106b26ff2196477ee9f"}, - {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42719a8bd3792744c9b523674b752091a7962d0d2d117f0b417a3eba97d1164b"}, - {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2743bb63095ef306041c8f8ea22bd6e4d91adabf41887b1ad7886c4c1eb43d5f"}, - {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:fa47319a10e0a076709644a0efbcaab9e91902c8bd8ef74c6adb19d320f69b83"}, - {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52137f0aea43e1993264a5180c467a08a3e372ca9d378244c2d86133f948b26b"}, - {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:f5abc8b4d0c5b556ed8cd41490b606fe99293175a82b98e652c3f2711b452988"}, - {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1e1cf7bc8cbbe6ce3881863671bac258b7d6bfc3706c600008925fb799a256e2"}, - {file = "frozenlist-1.3.1-cp38-cp38-win32.whl", hash = "sha256:0dde791b9b97f189874d654c55c24bf7b6782343e14909c84beebd28b7217845"}, - {file = "frozenlist-1.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:9494122bf39da6422b0972c4579e248867b6b1b50c9b05df7e04a3f30b9a413d"}, - {file = "frozenlist-1.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:31bf9539284f39ff9398deabf5561c2b0da5bb475590b4e13dd8b268d7a3c5c1"}, - {file = "frozenlist-1.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e0c8c803f2f8db7217898d11657cb6042b9b0553a997c4a0601f48a691480fab"}, - {file = "frozenlist-1.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da5ba7b59d954f1f214d352308d1d86994d713b13edd4b24a556bcc43d2ddbc3"}, - {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74e6b2b456f21fc93ce1aff2b9728049f1464428ee2c9752a4b4f61e98c4db96"}, - {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526d5f20e954d103b1d47232e3839f3453c02077b74203e43407b962ab131e7b"}, - {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b499c6abe62a7a8d023e2c4b2834fce78a6115856ae95522f2f974139814538c"}, - {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab386503f53bbbc64d1ad4b6865bf001414930841a870fc97f1546d4d133f141"}, - {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f63c308f82a7954bf8263a6e6de0adc67c48a8b484fab18ff87f349af356efd"}, - {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:12607804084d2244a7bd4685c9d0dca5df17a6a926d4f1967aa7978b1028f89f"}, - {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:da1cdfa96425cbe51f8afa43e392366ed0b36ce398f08b60de6b97e3ed4affef"}, - {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f810e764617b0748b49a731ffaa525d9bb36ff38332411704c2400125af859a6"}, - {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:35c3d79b81908579beb1fb4e7fcd802b7b4921f1b66055af2578ff7734711cfa"}, - {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c92deb5d9acce226a501b77307b3b60b264ca21862bd7d3e0c1f3594022f01bc"}, - {file = "frozenlist-1.3.1-cp39-cp39-win32.whl", hash = "sha256:5e77a8bd41e54b05e4fb2708dc6ce28ee70325f8c6f50f3df86a44ecb1d7a19b"}, - {file = "frozenlist-1.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:625d8472c67f2d96f9a4302a947f92a7adbc1e20bedb6aff8dbc8ff039ca6189"}, - {file = "frozenlist-1.3.1.tar.gz", hash = "sha256:3a735e4211a04ccfa3f4833547acdf5d2f863bfeb01cfd3edaffbc251f15cec8"}, -] -ftrack-python-api = [ - {file = "ftrack-python-api-2.3.3.tar.gz", hash = "sha256:358f37e5b1c5635eab107c19e27a0c890d512877f78af35b1ac416e90c037295"}, - {file = "ftrack_python_api-2.3.3-py2.py3-none-any.whl", hash = "sha256:82834c4d5def5557a2ea547a7e6f6ba84d3129e8f90457d8bbd85b287a2c39f6"}, -] +frozenlist = [] +ftrack-python-api = [] future = [ {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, ] -gazu = [ - {file = "gazu-0.8.30-py2.py3-none-any.whl", hash = "sha256:d692927a11314151bc33e7d67edee634053f70a3b09e4500dfc6626bfea18753"}, -] +gazu = [] gitdb = [ {file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"}, {file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"}, @@ -2253,42 +1944,24 @@ gitpython = [ {file = "GitPython-3.1.27-py3-none-any.whl", hash = "sha256:5b68b000463593e05ff2b261acff0ff0972df8ab1b70d3cdbd41b546c8b8fc3d"}, {file = "GitPython-3.1.27.tar.gz", hash = "sha256:1c885ce809e8ba2d88a29befeb385fcea06338d3640712b59ca623c220bb5704"}, ] -google-api-core = [ - {file = "google-api-core-2.8.2.tar.gz", hash = "sha256:06f7244c640322b508b125903bb5701bebabce8832f85aba9335ec00b3d02edc"}, - {file = "google_api_core-2.8.2-py3-none-any.whl", hash = "sha256:93c6a91ccac79079ac6bbf8b74ee75db970cc899278b97d53bc012f35908cf50"}, -] +google-api-core = [] google-api-python-client = [ {file = "google-api-python-client-1.12.11.tar.gz", hash = "sha256:1b4bd42a46321e13c0542a9e4d96fa05d73626f07b39f83a73a947d70ca706a9"}, {file = "google_api_python_client-1.12.11-py2.py3-none-any.whl", hash = "sha256:7e0a1a265c8d3088ee1987778c72683fcb376e32bada8d7767162bd9c503fd9b"}, ] -google-auth = [ - {file = "google-auth-2.10.0.tar.gz", hash = "sha256:7904dbd44b745c7323fef29565adee2fe7ff48473e2d94443aced40b0404a395"}, - {file = "google_auth-2.10.0-py2.py3-none-any.whl", hash = "sha256:1deba4a54f95ef67b4139eaf5c20eaa7047215eec9f6a2344599b8596db8863b"}, -] +google-auth = [] google-auth-httplib2 = [ {file = "google-auth-httplib2-0.1.0.tar.gz", hash = "sha256:a07c39fd632becacd3f07718dfd6021bf396978f03ad3ce4321d060015cc30ac"}, {file = "google_auth_httplib2-0.1.0-py2.py3-none-any.whl", hash = "sha256:31e49c36c6b5643b57e82617cb3e021e3e1d2df9da63af67252c02fa9c1f4a10"}, ] -googleapis-common-protos = [ - {file = "googleapis-common-protos-1.56.4.tar.gz", hash = "sha256:c25873c47279387cfdcbdafa36149887901d36202cb645a0e4f29686bf6e4417"}, - {file = "googleapis_common_protos-1.56.4-py2.py3-none-any.whl", hash = "sha256:8eb2cbc91b69feaf23e32452a7ae60e791e09967d81d4fcc7fc388182d1bd394"}, -] +googleapis-common-protos = [] httplib2 = [ {file = "httplib2-0.20.4-py3-none-any.whl", hash = "sha256:8b6a905cb1c79eefd03f8669fd993c36dc341f7c558f056cb5a33b5c2f458543"}, {file = "httplib2-0.20.4.tar.gz", hash = "sha256:58a98e45b4b1a48273073f905d2961666ecf0fbac4250ea5b47aef259eb5c585"}, ] -idna = [ - {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, - {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, -] -imagesize = [ - {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, - {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, -] -importlib-metadata = [ - {file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"}, - {file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"}, -] +idna = [] +imagesize = [] +importlib-metadata = [] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, @@ -2301,18 +1974,12 @@ jedi = [ {file = "jedi-0.13.3-py2.py3-none-any.whl", hash = "sha256:2c6bcd9545c7d6440951b12b44d373479bf18123a401a52025cf98563fbd826c"}, {file = "jedi-0.13.3.tar.gz", hash = "sha256:2bb0603e3506f708e792c7f4ad8fc2a7a9d9c2d292a358fbbd58da531695595b"}, ] -jeepney = [ - {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, - {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, -] +jeepney = [] jinja2 = [ {file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"}, {file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"}, ] -jinxed = [ - {file = "jinxed-1.2.0-py2.py3-none-any.whl", hash = "sha256:cfc2b2e4e3b4326954d546ba6d6b9a7a796ddcb0aef8d03161d005177eb0d48b"}, - {file = "jinxed-1.2.0.tar.gz", hash = "sha256:032acda92d5c57cd216033cbbd53de731e6ed50deb63eb4781336ca55f72cda5"}, -] +jinxed = [] jsonschema = [ {file = "jsonschema-2.6.0-py2.py3-none-any.whl", hash = "sha256:000e68abd33c972a5248544925a0cae7d1125f9bf6c58280d37546b946769a08"}, {file = "jsonschema-2.6.0.tar.gz", hash = "sha256:6ff5f3180870836cae40f06fa10419f557208175f13ad7bc26caa77beb1f6e02"}, @@ -2360,58 +2027,16 @@ lazy-object-proxy = [ {file = "lazy_object_proxy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61"}, {file = "lazy_object_proxy-1.7.1-pp37.pp38-none-any.whl", hash = "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84"}, ] -lief = [ - {file = "lief-0.12.1-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:4fbbc9d520de87ac22210c62d22a9b088e5460f9a028741311e6f68ef8877ddd"}, - {file = "lief-0.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:443e4494df448ea1a021976258c7a6aca27d81b0612783fa3a84fab196fb9fcb"}, - {file = "lief-0.12.1-cp310-cp310-win32.whl", hash = "sha256:1c4019dddf03a5185462fb5ea04327cee08d40f46777b02f0773c7dc294552ea"}, - {file = "lief-0.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:d7e09968f99ddf1e3983d3bcc16c62d1b6635a345fee8d8139f82b31bad457d6"}, - {file = "lief-0.12.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:9fa6269ec4fa3f874b807fbba3c48a46af30df2497723f6966080e3eb630cb26"}, - {file = "lief-0.12.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a78b05cac5fa491e01e1819573bbbbcaea0a4229f4aa3a2edb231b5695ddaf2d"}, - {file = "lief-0.12.1-cp36-cp36m-win32.whl", hash = "sha256:f1292bff96579c18e01e20b7a14043052379fe6e9a476c1d6d88aca43e5f9ac7"}, - {file = "lief-0.12.1-cp36-cp36m-win_amd64.whl", hash = "sha256:dab63876113bd573d64ce043f50153f6e2810e5e78256397aa0fe1fedf82ab84"}, - {file = "lief-0.12.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:5771f5226b62c885a7aa30c1b98040d39229a1dab889d03155e5538e57d0054b"}, - {file = "lief-0.12.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:8ec307a762505076a6d31566225a231c44ec7063c0e7d751ac4654c674454c47"}, - {file = "lief-0.12.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a755f6088d3b2041e4402adf917ac87e5ad9d1c5278973f48a29a5631fe393eb"}, - {file = "lief-0.12.1-cp37-cp37m-win32.whl", hash = "sha256:5d746f7eb6d3bf35a0230c7184aaaf434cb1ea89d7e7c8e8fe14a49cf2bb17a0"}, - {file = "lief-0.12.1-cp37-cp37m-win_amd64.whl", hash = "sha256:2d3ab7212da696bcbe5ca9dd78ceaa32dfb8a0e85e18001793b4441ef4624561"}, - {file = "lief-0.12.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:4360b0acd525ba77777cc38f0e5128c90c93cc4e91ab566ef3aa45b7f8a8c57e"}, - {file = "lief-0.12.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:5e82e466d36cbabb28cc1a787b554d2feae5ab55c39cab58ef64fb6513bad92a"}, - {file = "lief-0.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efa0022a3bf70ef46335639e61b946cc2d9cf012d60e263c215e3e64b1ce38b4"}, - {file = "lief-0.12.1-cp38-cp38-win32.whl", hash = "sha256:d29f91d9f64f67d3ada5b7e0e48ab084d825fb4601d32d9fecdd2bdf23cdad23"}, - {file = "lief-0.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:7dea6b3f17d362f93165379c46dadb012c73b1f751c8ceac256e5f43842cd86d"}, - {file = "lief-0.12.1-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:44012da4c32c670a97bb8a055a4ff16168cfaa757d03986f319aa3329a43e343"}, - {file = "lief-0.12.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:e1d23997b0a71d34e766ff183be07854c6f698fd3d6aa44bf30b6b7f4f77ef55"}, - {file = "lief-0.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b845eca79c772041efb38b50cfaf951e24bc047ec462450b7e54e75b7e2bee0d"}, - {file = "lief-0.12.1-cp39-cp39-win32.whl", hash = "sha256:0df84ac2df20b14db12e69442d39b0e8cd89428ba3b131995e0570bcd3725460"}, - {file = "lief-0.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:960a2da9f28c8d5dba753bb9ab77e26b3c6ff9b9658918be95650ceb8ee91e68"}, - {file = "lief-0.12.1.zip", hash = "sha256:4ff4ccfae2e1ee4ccba2b5556027dbb56282b8a973c5835c5b597e8b7b664416"}, -] log4mongo = [ {file = "log4mongo-1.7.0.tar.gz", hash = "sha256:dc374617206162a0b14167fbb5feac01dbef587539a235dadba6200362984a68"}, ] markupsafe = [ - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, @@ -2420,27 +2045,14 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, @@ -2450,12 +2062,6 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, @@ -2525,109 +2131,22 @@ multidict = [ {file = "multidict-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae"}, {file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"}, ] -opentimelineio = [ - {file = "OpenTimelineIO-0.14.0.dev1-cp310-cp310-linux_x86_64.whl", hash = "sha256:112c27ad419a79c88cd4ebac96278ab8f446fda4c6e1a70c871f2b24b3d003ef"}, - {file = "OpenTimelineIO-0.14.0.dev1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e2416ca76805a07ecbcdda65e5a31ce447e04e2db7082d72582740cbd8a16d7"}, - {file = "OpenTimelineIO-0.14.0.dev1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8806dc240496b72e16a0fddacb0b2e825d19656d80689098e6c5bd6a805bc84"}, - {file = "OpenTimelineIO-0.14.0.dev1-cp36-cp36m-win_amd64.whl", hash = "sha256:0aa54488ca50b53ac247610cef23fb63619dd1993016c0cd4069e54526d5905c"}, - {file = "OpenTimelineIO-0.14.0.dev1-cp37-cp37m-linux_x86_64.whl", hash = "sha256:8b11287eb733ad1c7fc53d4af3e3f926c396add6c3a3c1417b9c2b001f7ef4ba"}, - {file = "OpenTimelineIO-0.14.0.dev1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:2cba2ce567fc06f042365393dbe8e99dc4c1361999fb7ddc03d4b8b0d5ddb894"}, - {file = "OpenTimelineIO-0.14.0.dev1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:3d96da307c1969c309974a6734c7f3e39925236c845c8289f25d4d7d00be3f0c"}, - {file = "OpenTimelineIO-0.14.0.dev1-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:968cb0c5e6e7d697037b1cd4f7707521995a32fc51664139ed15004b93ab8106"}, - {file = "OpenTimelineIO-0.14.0.dev1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dae64aa418193ca094854a55efa8bcc5a5c2855b3679509987e8b48610d31"}, - {file = "OpenTimelineIO-0.14.0.dev1-cp37-cp37m-win_amd64.whl", hash = "sha256:85dfae42f5a992ef85d0015f33f999751d846a484ef907e8834407d545a7ee6a"}, - {file = "OpenTimelineIO-0.14.0.dev1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aaac346cb758de719f88ac175d3948409ded39c5eed0844068402f70f3e90b6"}, - {file = "OpenTimelineIO-0.14.0.dev1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a09f6dd199218cf69faf42a8b21f5be2cd01764e57dbcc8456b3ced564eb110f"}, - {file = "OpenTimelineIO-0.14.0.dev1.tar.gz", hash = "sha256:d29eafd5188c3ad6c7b6d2095bf69984d590c331b701d3bbc644d7abd5f08606"}, -] +opentimelineio = [] packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] -paramiko = [ - {file = "paramiko-2.11.0-py2.py3-none-any.whl", hash = "sha256:655f25dc8baf763277b933dfcea101d636581df8d6b9774d1fb653426b72c270"}, - {file = "paramiko-2.11.0.tar.gz", hash = "sha256:003e6bee7c034c21fbb051bf83dc0a9ee4106204dd3c53054c71452cc4ec3938"}, -] +paramiko = [] parso = [ {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, ] -patchelf = [ - {file = "patchelf-0.15.0.0-py2.py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:08e5e30a9415a8628de47726fbf15bfcd89be35df51c8a0a12372aebd0c5b4f6"}, - {file = "patchelf-0.15.0.0-py2.py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:4ce9d08119816bc4316c8ecc5f33da42384934fc0fc9cfbdded53a4930705466"}, - {file = "patchelf-0.15.0.0-py2.py3-none-manylinux_2_17_s390x.manylinux2014_s390x.musllinux_1_1_s390x.whl", hash = "sha256:ae19b0f91aabc9af2608a4ca0395533f1df9122e6abc11ef2c8db6e4db0f98c2"}, - {file = "patchelf-0.15.0.0-py2.py3-none-manylinux_2_5_i686.manylinux1_i686.musllinux_1_1_i686.whl", hash = "sha256:f3f87aee44d1d1b2209e38c4227b0316bb03538df68d20b3d96205aa87868d95"}, - {file = "patchelf-0.15.0.0-py2.py3-none-manylinux_2_5_x86_64.manylinux1_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:52e48c08110f2988a9761a5a383f7ae35b1e8e06a140e320d18386d3510697ed"}, - {file = "patchelf-0.15.0.0.tar.gz", hash = "sha256:0f8dcf0df0ba919ce37e8aef67a08bde5326897098451df94ab3a5eedc9e08d9"}, -] pathlib2 = [ {file = "pathlib2-2.3.7.post1-py2.py3-none-any.whl", hash = "sha256:5266a0fd000452f1b3467d782f079a4343c63aaa119221fbdc4e39577489ca5b"}, {file = "pathlib2-2.3.7.post1.tar.gz", hash = "sha256:9fe0edad898b83c0c3e199c842b27ed216645d2e177757b2dd67384d4113c641"}, ] -pillow = [ - {file = "Pillow-9.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:a9c9bc489f8ab30906d7a85afac4b4944a572a7432e00698a7239f44a44e6efb"}, - {file = "Pillow-9.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:510cef4a3f401c246cfd8227b300828715dd055463cdca6176c2e4036df8bd4f"}, - {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7888310f6214f19ab2b6df90f3f06afa3df7ef7355fc025e78a3044737fab1f5"}, - {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831e648102c82f152e14c1a0938689dbb22480c548c8d4b8b248b3e50967b88c"}, - {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cc1d2451e8a3b4bfdb9caf745b58e6c7a77d2e469159b0d527a4554d73694d1"}, - {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:136659638f61a251e8ed3b331fc6ccd124590eeff539de57c5f80ef3a9594e58"}, - {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:6e8c66f70fb539301e064f6478d7453e820d8a2c631da948a23384865cd95544"}, - {file = "Pillow-9.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:37ff6b522a26d0538b753f0b4e8e164fdada12db6c6f00f62145d732d8a3152e"}, - {file = "Pillow-9.2.0-cp310-cp310-win32.whl", hash = "sha256:c79698d4cd9318d9481d89a77e2d3fcaeff5486be641e60a4b49f3d2ecca4e28"}, - {file = "Pillow-9.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:254164c57bab4b459f14c64e93df11eff5ded575192c294a0c49270f22c5d93d"}, - {file = "Pillow-9.2.0-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:408673ed75594933714482501fe97e055a42996087eeca7e5d06e33218d05aa8"}, - {file = "Pillow-9.2.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:727dd1389bc5cb9827cbd1f9d40d2c2a1a0c9b32dd2261db522d22a604a6eec9"}, - {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50dff9cc21826d2977ef2d2a205504034e3a4563ca6f5db739b0d1026658e004"}, - {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb6259196a589123d755380b65127ddc60f4c64b21fc3bb46ce3a6ea663659b0"}, - {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b0554af24df2bf96618dac71ddada02420f946be943b181108cac55a7a2dcd4"}, - {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:15928f824870535c85dbf949c09d6ae7d3d6ac2d6efec80f3227f73eefba741c"}, - {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:bdd0de2d64688ecae88dd8935012c4a72681e5df632af903a1dca8c5e7aa871a"}, - {file = "Pillow-9.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5b87da55a08acb586bad5c3aa3b86505f559b84f39035b233d5bf844b0834b1"}, - {file = "Pillow-9.2.0-cp311-cp311-win32.whl", hash = "sha256:b6d5e92df2b77665e07ddb2e4dbd6d644b78e4c0d2e9272a852627cdba0d75cf"}, - {file = "Pillow-9.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6bf088c1ce160f50ea40764f825ec9b72ed9da25346216b91361eef8ad1b8f8c"}, - {file = "Pillow-9.2.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:2c58b24e3a63efd22554c676d81b0e57f80e0a7d3a5874a7e14ce90ec40d3069"}, - {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eef7592281f7c174d3d6cbfbb7ee5984a671fcd77e3fc78e973d492e9bf0eb3f"}, - {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dcd7b9c7139dc8258d164b55696ecd16c04607f1cc33ba7af86613881ffe4ac8"}, - {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a138441e95562b3c078746a22f8fca8ff1c22c014f856278bdbdd89ca36cff1b"}, - {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:93689632949aff41199090eff5474f3990b6823404e45d66a5d44304e9cdc467"}, - {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:f3fac744f9b540148fa7715a435d2283b71f68bfb6d4aae24482a890aed18b59"}, - {file = "Pillow-9.2.0-cp37-cp37m-win32.whl", hash = "sha256:fa768eff5f9f958270b081bb33581b4b569faabf8774726b283edb06617101dc"}, - {file = "Pillow-9.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:69bd1a15d7ba3694631e00df8de65a8cb031911ca11f44929c97fe05eb9b6c1d"}, - {file = "Pillow-9.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:030e3460861488e249731c3e7ab59b07c7853838ff3b8e16aac9561bb345da14"}, - {file = "Pillow-9.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:74a04183e6e64930b667d321524e3c5361094bb4af9083db5c301db64cd341f3"}, - {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d33a11f601213dcd5718109c09a52c2a1c893e7461f0be2d6febc2879ec2402"}, - {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fd6f5e3c0e4697fa7eb45b6e93996299f3feee73a3175fa451f49a74d092b9f"}, - {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a647c0d4478b995c5e54615a2e5360ccedd2f85e70ab57fbe817ca613d5e63b8"}, - {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:4134d3f1ba5f15027ff5c04296f13328fecd46921424084516bdb1b2548e66ff"}, - {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:bc431b065722a5ad1dfb4df354fb9333b7a582a5ee39a90e6ffff688d72f27a1"}, - {file = "Pillow-9.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1536ad017a9f789430fb6b8be8bf99d2f214c76502becc196c6f2d9a75b01b76"}, - {file = "Pillow-9.2.0-cp38-cp38-win32.whl", hash = "sha256:2ad0d4df0f5ef2247e27fc790d5c9b5a0af8ade9ba340db4a73bb1a4a3e5fb4f"}, - {file = "Pillow-9.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:ec52c351b35ca269cb1f8069d610fc45c5bd38c3e91f9ab4cbbf0aebc136d9c8"}, - {file = "Pillow-9.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ed2c4ef2451de908c90436d6e8092e13a43992f1860275b4d8082667fbb2ffc"}, - {file = "Pillow-9.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ad2f835e0ad81d1689f1b7e3fbac7b01bb8777d5a985c8962bedee0cc6d43da"}, - {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea98f633d45f7e815db648fd7ff0f19e328302ac36427343e4432c84432e7ff4"}, - {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7761afe0126d046974a01e030ae7529ed0ca6a196de3ec6937c11df0df1bc91c"}, - {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a54614049a18a2d6fe156e68e188da02a046a4a93cf24f373bffd977e943421"}, - {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:5aed7dde98403cd91d86a1115c78d8145c83078e864c1de1064f52e6feb61b20"}, - {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:13b725463f32df1bfeacbf3dd197fb358ae8ebcd8c5548faa75126ea425ccb60"}, - {file = "Pillow-9.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:808add66ea764ed97d44dda1ac4f2cfec4c1867d9efb16a33d158be79f32b8a4"}, - {file = "Pillow-9.2.0-cp39-cp39-win32.whl", hash = "sha256:337a74fd2f291c607d220c793a8135273c4c2ab001b03e601c36766005f36885"}, - {file = "Pillow-9.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:fac2d65901fb0fdf20363fbd345c01958a742f2dc62a8dd4495af66e3ff502a4"}, - {file = "Pillow-9.2.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ad2277b185ebce47a63f4dc6302e30f05762b688f8dc3de55dbae4651872cdf3"}, - {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c7b502bc34f6e32ba022b4a209638f9e097d7a9098104ae420eb8186217ebbb"}, - {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d1f14f5f691f55e1b47f824ca4fdcb4b19b4323fe43cc7bb105988cad7496be"}, - {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:dfe4c1fedfde4e2fbc009d5ad420647f7730d719786388b7de0999bf32c0d9fd"}, - {file = "Pillow-9.2.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:f07f1f00e22b231dd3d9b9208692042e29792d6bd4f6639415d2f23158a80013"}, - {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1802f34298f5ba11d55e5bb09c31997dc0c6aed919658dfdf0198a2fe75d5490"}, - {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17d4cafe22f050b46d983b71c707162d63d796a1235cdf8b9d7a112e97b15bac"}, - {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:96b5e6874431df16aee0c1ba237574cb6dff1dcb173798faa6a9d8b399a05d0e"}, - {file = "Pillow-9.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:0030fdbd926fb85844b8b92e2f9449ba89607231d3dd597a21ae72dc7fe26927"}, - {file = "Pillow-9.2.0.tar.gz", hash = "sha256:75e636fd3e0fb872693f23ccb8a5ff2cd578801251f3a4f6854c6a5d437d3c04"}, -] -platformdirs = [ - {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, - {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, -] +pillow = [] +platformdirs = [] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, @@ -2640,22 +2159,7 @@ prefixed = [ {file = "prefixed-0.3.2-py2.py3-none-any.whl", hash = "sha256:5e107306462d63f2f03c529dbf11b0026fdfec621a9a008ca639d71de22995c3"}, {file = "prefixed-0.3.2.tar.gz", hash = "sha256:ca48277ba5fa8346dd4b760847da930c7b84416387c39e93affef086add2c029"}, ] -protobuf = [ - {file = "protobuf-4.21.5-cp310-abi3-win32.whl", hash = "sha256:5310cbe761e87f0c1decce019d23f2101521d4dfff46034f8a12a53546036ec7"}, - {file = "protobuf-4.21.5-cp310-abi3-win_amd64.whl", hash = "sha256:e5c5a2886ae48d22a9d32fbb9b6636a089af3cd26b706750258ce1ca96cc0116"}, - {file = "protobuf-4.21.5-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:ee04f5823ed98bb9a8c3b1dc503c49515e0172650875c3f76e225b223793a1f2"}, - {file = "protobuf-4.21.5-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:b04484d6f42f48c57dd2737a72692f4c6987529cdd148fb5b8e5f616862a2e37"}, - {file = "protobuf-4.21.5-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:5e0b272217aad8971763960238c1a1e6a65d50ef7824e23300da97569a251c55"}, - {file = "protobuf-4.21.5-cp37-cp37m-win32.whl", hash = "sha256:5eb0724615e90075f1d763983e708e1cef08e66b1891d8b8b6c33bc3b2f1a02b"}, - {file = "protobuf-4.21.5-cp37-cp37m-win_amd64.whl", hash = "sha256:011c0f267e85f5d73750b6c25f0155d5db1e9443cd3590ab669a6221dd8fcdb0"}, - {file = "protobuf-4.21.5-cp38-cp38-win32.whl", hash = "sha256:7b6f22463e2d1053d03058b7b4ceca6e4ed4c14f8c286c32824df751137bf8e7"}, - {file = "protobuf-4.21.5-cp38-cp38-win_amd64.whl", hash = "sha256:b52e7a522911a40445a5f588bd5b5e584291bfc5545e09b7060685e4b2ff814f"}, - {file = "protobuf-4.21.5-cp39-cp39-win32.whl", hash = "sha256:a7faa62b183d6a928e3daffd06af843b4287d16ef6e40f331575ecd236a7974d"}, - {file = "protobuf-4.21.5-cp39-cp39-win_amd64.whl", hash = "sha256:5e0ce02418ef03d7657a420ae8fd6fec4995ac713a3cb09164e95f694dbcf085"}, - {file = "protobuf-4.21.5-py2.py3-none-any.whl", hash = "sha256:bf711b451212dc5b0fa45ae7dada07d8e71a4b0ff0bc8e4783ee145f47ac4f82"}, - {file = "protobuf-4.21.5-py3-none-any.whl", hash = "sha256:3ec6f5b37935406bb9df9b277e79f8ed81d697146e07ef2ba8a5a272fb24b2c9"}, - {file = "protobuf-4.21.5.tar.gz", hash = "sha256:eb1106e87e095628e96884a877a51cdb90087106ee693925ec0a300468a9be3a"}, -] +protobuf = [] py = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, @@ -2714,14 +2218,8 @@ pyflakes = [ {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, ] -pygments = [ - {file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"}, - {file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"}, -] -pylint = [ - {file = "pylint-2.13.9-py3-none-any.whl", hash = "sha256:705c620d388035bdd9ff8b44c5bcdd235bfb49d276d488dd2c8ff1736aa42526"}, - {file = "pylint-2.13.9.tar.gz", hash = "sha256:095567c96e19e6f57b5b907e67d265ff535e588fe26b12b5ebe1fc5645b2c731"}, -] +pygments = [] +pylint = [] pymongo = [ {file = "pymongo-3.12.3-cp27-cp27m-macosx_10_14_intel.whl", hash = "sha256:c164eda0be9048f83c24b9b2656900041e069ddf72de81c17d874d0c32f6079f"}, {file = "pymongo-3.12.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:a055d29f1302892a9389a382bed10a3f77708bcf3e49bfb76f7712fa5f391cc6"}, @@ -2848,42 +2346,10 @@ pynput = [ {file = "pynput-1.7.6-py3.9.egg", hash = "sha256:264429fbe676e98e9050ad26a7017453bdd08768adb25cafb918347cf9f1eb4a"}, {file = "pynput-1.7.6.tar.gz", hash = "sha256:3a5726546da54116b687785d38b1db56997ce1d28e53e8d22fc656d8b92e533c"}, ] -pyobjc-core = [ - {file = "pyobjc-core-8.5.tar.gz", hash = "sha256:704c275439856c0d1287469f0d589a7d808d48b754a93d9ce5415d4eaf06d576"}, - {file = "pyobjc_core-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0c234143b48334443f5adcf26e668945a6d47bc1fa6223e80918c6c735a029d9"}, - {file = "pyobjc_core-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1486ee533f0d76f666804ce89723ada4db56bfde55e56151ba512d3f849857f8"}, - {file = "pyobjc_core-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:412de06dfa728301c04b3e46fd7453320a8ae8b862e85236e547cd797a73b490"}, - {file = "pyobjc_core-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b3e09cccb1be574a82cc9f929ae27fc4283eccc75496cb5d51534caa6bb83a3"}, - {file = "pyobjc_core-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:eeafe21f879666ab7f57efcc6b007c9f5f8733d367b7e380c925203ed83f000d"}, - {file = "pyobjc_core-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c0071686976d7ea8c14690950e504a13cb22b4ebb2bc7b5ec47c1c1c0f6eff41"}, -] -pyobjc-framework-applicationservices = [ - {file = "pyobjc-framework-ApplicationServices-8.5.tar.gz", hash = "sha256:fa3015ef8e3add90af3447d7fdcc7f8dd083cc2a1d58f99a569480a2df10d2b1"}, - {file = "pyobjc_framework_ApplicationServices-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:436b16ebe448a829a8312e10208eec81a2adcae1fff674dbcc3262e1bd76e0ca"}, - {file = "pyobjc_framework_ApplicationServices-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:408958d14aa7fcf46f2163754c211078bc63be1368934d86188202914dce077d"}, - {file = "pyobjc_framework_ApplicationServices-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1d6cd4ce192859a22e208da4d7177a1c3ceb1ef2f64c339fd881102b1210cadd"}, - {file = "pyobjc_framework_ApplicationServices-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0251d092adb1d2d116fd9f147ceef0e53b158a46c21245131c40b9d7b786d0db"}, - {file = "pyobjc_framework_ApplicationServices-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:9742e69fe6d4545d0e02b0ad0a7a2432bc9944569ee07d6e90ffa5ef614df9f7"}, - {file = "pyobjc_framework_ApplicationServices-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16f5677c14ea903c6aaca1dd121521825c39e816cae696d6ae32c0b287252ab2"}, -] -pyobjc-framework-cocoa = [ - {file = "pyobjc-framework-Cocoa-8.5.tar.gz", hash = "sha256:569bd3a020f64b536fb2d1c085b37553e50558c9f907e08b73ffc16ae68e1861"}, - {file = "pyobjc_framework_Cocoa-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7a7c160416696bf6035dfcdf0e603aaa52858d6afcddfcc5ab41733619ac2529"}, - {file = "pyobjc_framework_Cocoa-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6ceba444282030be8596b812260e8d28b671254a51052ad778d32da6e17db847"}, - {file = "pyobjc_framework_Cocoa-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f46b2b161b8dd40c7b9e00bc69636c3e6480b2704a69aee22ee0154befbe163a"}, - {file = "pyobjc_framework_Cocoa-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b31d425aee8698cbf62b187338f5ca59427fa4dca2153a73866f7cb410713119"}, - {file = "pyobjc_framework_Cocoa-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:898359ac1f76eedec8aa156847682378a8950824421c40edb89391286e607dc4"}, - {file = "pyobjc_framework_Cocoa-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:baa2947f76b119a3360973d74d57d6dada87ac527bab9a88f31596af392f123c"}, -] -pyobjc-framework-quartz = [ - {file = "pyobjc-framework-Quartz-8.5.tar.gz", hash = "sha256:d2bc5467a792ddc04814f12a1e9c2fcaf699a1c3ad3d4264cfdce6b9c7b10624"}, - {file = "pyobjc_framework_Quartz-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e9f0fb663f7872c9de94169031ac42b91ad01bd4cad49a9f1a0164be8f028426"}, - {file = "pyobjc_framework_Quartz-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:567eec91287cfe9a1b6433717192c585935de8f3daa28d82ce72fdd6c7ac00f6"}, - {file = "pyobjc_framework_Quartz-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f910ab41a712ffc7a8c3e3716a2d6f39ea4419004b26a2fd2d2f740ff5c262c"}, - {file = "pyobjc_framework_Quartz-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:29d07066781628278bf0e5278abcfc96ef6724c66c5629a0b4c214d319a82e55"}, - {file = "pyobjc_framework_Quartz-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:72abcde1a3d72be11f2c881c9b9872044c8f2de86d2047b67fe771713638b107"}, - {file = "pyobjc_framework_Quartz-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8809b9a2df2f461697bdb45b6d1b5a4f881f88f09450e3990858e64e3e26c530"}, -] +pyobjc-core = [] +pyobjc-framework-applicationservices = [] +pyobjc-framework-cocoa = [] +pyobjc-framework-quartz = [] pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, @@ -2907,14 +2373,8 @@ python-dateutil = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] -python-engineio = [ - {file = "python-engineio-3.14.2.tar.gz", hash = "sha256:eab4553f2804c1ce97054c8b22cf0d5a9ab23128075248b97e1a5b2f29553085"}, - {file = "python_engineio-3.14.2-py2.py3-none-any.whl", hash = "sha256:5a9e6086d192463b04a1428ff1f85b6ba631bbb19d453b144ffc04f530542b84"}, -] -python-socketio = [ - {file = "python-socketio-4.6.1.tar.gz", hash = "sha256:cd1f5aa492c1eb2be77838e837a495f117e17f686029ebc03d62c09e33f4fa10"}, - {file = "python_socketio-4.6.1-py2.py3-none-any.whl", hash = "sha256:5a21da53fdbdc6bb6c8071f40e13d100e0b279ad997681c2492478e06f370523"}, -] +python-engineio = [] +python-socketio = [] python-xlib = [ {file = "python-xlib-0.31.tar.gz", hash = "sha256:74d83a081f532bc07f6d7afcd6416ec38403d68f68b9b9dc9e1f28fbf2d799e9"}, {file = "python_xlib-0.31-py2.py3-none-any.whl", hash = "sha256:1ec6ce0de73d9e6592ead666779a5732b384e5b8fb1f1886bd0a81cafa477759"}, @@ -2922,10 +2382,7 @@ python-xlib = [ python3-xlib = [ {file = "python3-xlib-0.15.tar.gz", hash = "sha256:dc4245f3ae4aa5949c1d112ee4723901ade37a96721ba9645f2bfa56e5b383f8"}, ] -pytz = [ - {file = "pytz-2022.2-py2.py3-none-any.whl", hash = "sha256:d9b245e63af49c4e51afdec5402f56b99c0cb483a84a12bb8b7db980386baade"}, - {file = "pytz-2022.2.tar.gz", hash = "sha256:bc824559e43e8ab983426a49525079d186b25372ff63aa3430ccd527d95edc3a"}, -] +pytz = [] pywin32 = [ {file = "pywin32-301-cp35-cp35m-win32.whl", hash = "sha256:93367c96e3a76dfe5003d8291ae16454ca7d84bb24d721e0b74a07610b7be4a7"}, {file = "pywin32-301-cp35-cp35m-win_amd64.whl", hash = "sha256:9635df6998a70282bd36e7ac2a5cef9ead1627b0a63b17c731312c7a0daebb72"}, @@ -2942,10 +2399,7 @@ pywin32-ctypes = [ {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"}, {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, ] -"qt.py" = [ - {file = "Qt.py-1.3.7-py2.py3-none-any.whl", hash = "sha256:150099d1c6f64c9621a2c9d79d45102ec781c30ee30ee69fc082c6e9be7324fe"}, - {file = "Qt.py-1.3.7.tar.gz", hash = "sha256:803c7bdf4d6230f9a466be19d55934a173eabb61406d21cb91e80c2a3f773b1f"}, -] +"qt.py" = [] qtawesome = [ {file = "QtAwesome-0.7.3-py2.py3-none-any.whl", hash = "sha256:ddf4530b4af71cec13b24b88a4cdb56ec85b1e44c43c42d0698804c7137b09b0"}, {file = "QtAwesome-0.7.3.tar.gz", hash = "sha256:b98b9038d19190e83ab26d91c4d8fc3a36591ee2bc7f5016d4438b8240d097bd"}, @@ -2958,35 +2412,19 @@ recommonmark = [ {file = "recommonmark-0.7.1-py2.py3-none-any.whl", hash = "sha256:1b1db69af0231efce3fa21b94ff627ea33dee7079a01dd0a7f8482c3da148b3f"}, {file = "recommonmark-0.7.1.tar.gz", hash = "sha256:bdb4db649f2222dcd8d2d844f0006b958d627f732415d399791ee436a3686d67"}, ] -requests = [ - {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, - {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, -] -rsa = [ - {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, - {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, -] -secretstorage = [ - {file = "SecretStorage-3.3.2-py3-none-any.whl", hash = "sha256:755dc845b6ad76dcbcbc07ea3da75ae54bb1ea529eb72d15f83d26499a5df319"}, - {file = "SecretStorage-3.3.2.tar.gz", hash = "sha256:0a8eb9645b320881c222e827c26f4cfcf55363e8b374a021981ef886657a912f"}, -] +requests = [] +rsa = [] +secretstorage = [] semver = [ {file = "semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"}, {file = "semver-2.13.0.tar.gz", hash = "sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f"}, ] -setuptools = [ - {file = "setuptools-60.10.0-py3-none-any.whl", hash = "sha256:782ef48d58982ddb49920c11a0c5c9c0b02e7d7d1c2ad0aa44e1a1e133051c96"}, - {file = "setuptools-60.10.0.tar.gz", hash = "sha256:6599055eeb23bfef457d5605d33a4d68804266e6cb430b0fb12417c5efeae36c"}, -] shotgun-api3 = [] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] -slack-sdk = [ - {file = "slack_sdk-3.18.1-py2.py3-none-any.whl", hash = "sha256:63ce5e6253a31873d6c921c9feaa842a93a2f56e6e009cb7daf406f4bc4df798"}, - {file = "slack_sdk-3.18.1.tar.gz", hash = "sha256:a25d3d2bf0bf605d54d764d4a463fe7c0659ee24c13d75653e2bec247bd5998b"}, -] +slack-sdk = [] smmap = [ {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, @@ -2995,18 +2433,9 @@ snowballstemmer = [ {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] -speedcopy = [ - {file = "speedcopy-2.1.4-py3-none-any.whl", hash = "sha256:e09eb1de67ae0e0b51d5b99a28882009d565a37a3cb3c6bae121e3a5d3cccb17"}, - {file = "speedcopy-2.1.4.tar.gz", hash = "sha256:eff007a97e49ec1934df4fa8074f4bd1cf4a3b14c5499d914988785cff0c199a"}, -] -sphinx = [ - {file = "Sphinx-5.0.1-py3-none-any.whl", hash = "sha256:36aa2a3c2f6d5230be94585bc5d74badd5f9ed8f3388b8eedc1726fe45b1ad30"}, - {file = "Sphinx-5.0.1.tar.gz", hash = "sha256:f4da1187785a5bc7312cc271b0e867a93946c319d106363e102936a3d9857306"}, -] -sphinx-qt-documentation = [ - {file = "sphinx_qt_documentation-0.4-py3-none-any.whl", hash = "sha256:fa131093f75cd1bd48699cd132e18e4d46ba9eaadc070e6026867cea75ecdb7b"}, - {file = "sphinx_qt_documentation-0.4.tar.gz", hash = "sha256:f43ba17baa93e353fb94045027fb67f9d935ed158ce8662de93f08b88eec6774"}, -] +speedcopy = [] +sphinx = [] +sphinx-qt-documentation = [] sphinx-rtd-theme = [ {file = "sphinx_rtd_theme-1.0.0-py2.py3-none-any.whl", hash = "sha256:4d35a56f4508cfee4c4fb604373ede6feae2a306731d533f409ef5c3496fdbd8"}, {file = "sphinx_rtd_theme-1.0.0.tar.gz", hash = "sha256:eec6d497e4c2195fa0e8b2016b337532b8a699a68bcb22a512870e16925c6a5c"}, @@ -3055,44 +2484,13 @@ tomli = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] -typed-ast = [ - {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, - {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, - {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, - {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, - {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, - {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, - {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, - {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, - {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, - {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, - {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, - {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, - {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, - {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, - {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, - {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, - {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, - {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, - {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, - {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, - {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, - {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, - {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, - {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, -] -typing-extensions = [ - {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, - {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, -] +typed-ast = [] +typing-extensions = [] uritemplate = [ {file = "uritemplate-3.0.1-py2.py3-none-any.whl", hash = "sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f"}, {file = "uritemplate-3.0.1.tar.gz", hash = "sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae"}, ] -urllib3 = [ - {file = "urllib3-1.26.11-py2.py3-none-any.whl", hash = "sha256:c33ccba33c819596124764c23a97d25f32b28433ba0dedeb77d873a38722c9bc"}, - {file = "urllib3-1.26.11.tar.gz", hash = "sha256:ea6e8fb210b19d950fab93b60c9009226c63a28808bc8386e05301e25883ac0a"}, -] +urllib3 = [] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, @@ -3101,142 +2499,10 @@ websocket-client = [ {file = "websocket-client-0.59.0.tar.gz", hash = "sha256:d376bd60eace9d437ab6d7ee16f4ab4e821c9dae591e1b783c58ebd8aaf80c5c"}, {file = "websocket_client-0.59.0-py2.py3-none-any.whl", hash = "sha256:2e50d26ca593f70aba7b13a489435ef88b8fc3b5c5643c1ce8808ff9b40f0b32"}, ] -wheel = [ - {file = "wheel-0.37.1-py2.py3-none-any.whl", hash = "sha256:4bdcd7d840138086126cd09254dc6195fb4fc6f01c050a1d7236f2630db1d22a"}, - {file = "wheel-0.37.1.tar.gz", hash = "sha256:e9a504e793efbca1b8e0e9cb979a249cf4a0a7b5b8c9e8b65a5e39d49529c1c4"}, -] -wrapt = [ - {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"}, - {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"}, - {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"}, - {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"}, - {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"}, - {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"}, - {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"}, - {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"}, - {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"}, - {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"}, - {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"}, - {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"}, - {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"}, - {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"}, - {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"}, - {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"}, - {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"}, - {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"}, - {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"}, - {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"}, - {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"}, - {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"}, - {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"}, - {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"}, - {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"}, - {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"}, - {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"}, - {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"}, - {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"}, - {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"}, - {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"}, - {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"}, - {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"}, - {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"}, - {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"}, - {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"}, - {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"}, - {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"}, - {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"}, - {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"}, - {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"}, - {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"}, - {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"}, - {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"}, - {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"}, - {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"}, - {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"}, - {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"}, - {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"}, - {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"}, - {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"}, - {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"}, -] +wrapt = [] wsrpc-aiohttp = [ {file = "wsrpc-aiohttp-3.2.0.tar.gz", hash = "sha256:f467abc51bcdc760fc5aeb7041abdeef46eeca3928dc43dd6e7fa7a533563818"}, {file = "wsrpc_aiohttp-3.2.0-py3-none-any.whl", hash = "sha256:fa9b0bf5cb056898cb5c9f64cbc5eacb8a5dd18ab1b7f0cd4a2208b4a7fde282"}, ] -yarl = [ - {file = "yarl-1.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:abc06b97407868ef38f3d172762f4069323de52f2b70d133d096a48d72215d28"}, - {file = "yarl-1.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:07b21e274de4c637f3e3b7104694e53260b5fc10d51fb3ec5fed1da8e0f754e3"}, - {file = "yarl-1.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9de955d98e02fab288c7718662afb33aab64212ecb368c5dc866d9a57bf48880"}, - {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ec362167e2c9fd178f82f252b6d97669d7245695dc057ee182118042026da40"}, - {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:20df6ff4089bc86e4a66e3b1380460f864df3dd9dccaf88d6b3385d24405893b"}, - {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5999c4662631cb798496535afbd837a102859568adc67d75d2045e31ec3ac497"}, - {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed19b74e81b10b592084a5ad1e70f845f0aacb57577018d31de064e71ffa267a"}, - {file = "yarl-1.8.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e4808f996ca39a6463f45182e2af2fae55e2560be586d447ce8016f389f626f"}, - {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2d800b9c2eaf0684c08be5f50e52bfa2aa920e7163c2ea43f4f431e829b4f0fd"}, - {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6628d750041550c5d9da50bb40b5cf28a2e63b9388bac10fedd4f19236ef4957"}, - {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f5af52738e225fcc526ae64071b7e5342abe03f42e0e8918227b38c9aa711e28"}, - {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:76577f13333b4fe345c3704811ac7509b31499132ff0181f25ee26619de2c843"}, - {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0c03f456522d1ec815893d85fccb5def01ffaa74c1b16ff30f8aaa03eb21e453"}, - {file = "yarl-1.8.1-cp310-cp310-win32.whl", hash = "sha256:ea30a42dc94d42f2ba4d0f7c0ffb4f4f9baa1b23045910c0c32df9c9902cb272"}, - {file = "yarl-1.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:9130ddf1ae9978abe63808b6b60a897e41fccb834408cde79522feb37fb72fb0"}, - {file = "yarl-1.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0ab5a138211c1c366404d912824bdcf5545ccba5b3ff52c42c4af4cbdc2c5035"}, - {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0fb2cb4204ddb456a8e32381f9a90000429489a25f64e817e6ff94879d432fc"}, - {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:85cba594433915d5c9a0d14b24cfba0339f57a2fff203a5d4fd070e593307d0b"}, - {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ca7e596c55bd675432b11320b4eacc62310c2145d6801a1f8e9ad160685a231"}, - {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0f77539733e0ec2475ddcd4e26777d08996f8cd55d2aef82ec4d3896687abda"}, - {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29e256649f42771829974e742061c3501cc50cf16e63f91ed8d1bf98242e5507"}, - {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7fce6cbc6c170ede0221cc8c91b285f7f3c8b9fe28283b51885ff621bbe0f8ee"}, - {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:59ddd85a1214862ce7c7c66457f05543b6a275b70a65de366030d56159a979f0"}, - {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:12768232751689c1a89b0376a96a32bc7633c08da45ad985d0c49ede691f5c0d"}, - {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:b19255dde4b4f4c32e012038f2c169bb72e7f081552bea4641cab4d88bc409dd"}, - {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6c8148e0b52bf9535c40c48faebb00cb294ee577ca069d21bd5c48d302a83780"}, - {file = "yarl-1.8.1-cp37-cp37m-win32.whl", hash = "sha256:de839c3a1826a909fdbfe05f6fe2167c4ab033f1133757b5936efe2f84904c07"}, - {file = "yarl-1.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:dd032e8422a52e5a4860e062eb84ac94ea08861d334a4bcaf142a63ce8ad4802"}, - {file = "yarl-1.8.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:19cd801d6f983918a3f3a39f3a45b553c015c5aac92ccd1fac619bd74beece4a"}, - {file = "yarl-1.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6347f1a58e658b97b0a0d1ff7658a03cb79bdbda0331603bed24dd7054a6dea1"}, - {file = "yarl-1.8.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c0da7e44d0c9108d8b98469338705e07f4bb7dab96dbd8fa4e91b337db42548"}, - {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5587bba41399854703212b87071c6d8638fa6e61656385875f8c6dff92b2e461"}, - {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31a9a04ecccd6b03e2b0e12e82131f1488dea5555a13a4d32f064e22a6003cfe"}, - {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:205904cffd69ae972a1707a1bd3ea7cded594b1d773a0ce66714edf17833cdae"}, - {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea513a25976d21733bff523e0ca836ef1679630ef4ad22d46987d04b372d57fc"}, - {file = "yarl-1.8.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0b51530877d3ad7a8d47b2fff0c8df3b8f3b8deddf057379ba50b13df2a5eae"}, - {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d2b8f245dad9e331540c350285910b20dd913dc86d4ee410c11d48523c4fd546"}, - {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ab2a60d57ca88e1d4ca34a10e9fb4ab2ac5ad315543351de3a612bbb0560bead"}, - {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:449c957ffc6bc2309e1fbe67ab7d2c1efca89d3f4912baeb8ead207bb3cc1cd4"}, - {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a165442348c211b5dea67c0206fc61366212d7082ba8118c8c5c1c853ea4d82e"}, - {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b3ded839a5c5608eec8b6f9ae9a62cb22cd037ea97c627f38ae0841a48f09eae"}, - {file = "yarl-1.8.1-cp38-cp38-win32.whl", hash = "sha256:c1445a0c562ed561d06d8cbc5c8916c6008a31c60bc3655cdd2de1d3bf5174a0"}, - {file = "yarl-1.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:56c11efb0a89700987d05597b08a1efcd78d74c52febe530126785e1b1a285f4"}, - {file = "yarl-1.8.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e80ed5a9939ceb6fda42811542f31c8602be336b1fb977bccb012e83da7e4936"}, - {file = "yarl-1.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6afb336e23a793cd3b6476c30f030a0d4c7539cd81649683b5e0c1b0ab0bf350"}, - {file = "yarl-1.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4c322cbaa4ed78a8aac89b2174a6df398faf50e5fc12c4c191c40c59d5e28357"}, - {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fae37373155f5ef9b403ab48af5136ae9851151f7aacd9926251ab26b953118b"}, - {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5395da939ffa959974577eff2cbfc24b004a2fb6c346918f39966a5786874e54"}, - {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:076eede537ab978b605f41db79a56cad2e7efeea2aa6e0fa8f05a26c24a034fb"}, - {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d1a50e461615747dd93c099f297c1994d472b0f4d2db8a64e55b1edf704ec1c"}, - {file = "yarl-1.8.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7de89c8456525650ffa2bb56a3eee6af891e98f498babd43ae307bd42dca98f6"}, - {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4a88510731cd8d4befaba5fbd734a7dd914de5ab8132a5b3dde0bbd6c9476c64"}, - {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2d93a049d29df172f48bcb09acf9226318e712ce67374f893b460b42cc1380ae"}, - {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:21ac44b763e0eec15746a3d440f5e09ad2ecc8b5f6dcd3ea8cb4773d6d4703e3"}, - {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d0272228fabe78ce00a3365ffffd6f643f57a91043e119c289aaba202f4095b0"}, - {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:99449cd5366fe4608e7226c6cae80873296dfa0cde45d9b498fefa1de315a09e"}, - {file = "yarl-1.8.1-cp39-cp39-win32.whl", hash = "sha256:8b0af1cf36b93cee99a31a545fe91d08223e64390c5ecc5e94c39511832a4bb6"}, - {file = "yarl-1.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:de49d77e968de6626ba7ef4472323f9d2e5a56c1d85b7c0e2a190b2173d3b9be"}, - {file = "yarl-1.8.1.tar.gz", hash = "sha256:af887845b8c2e060eb5605ff72b6f2dd2aab7a761379373fd89d314f4752abbf"}, -] -zipp = [ - {file = "zipp-3.8.1-py3-none-any.whl", hash = "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009"}, - {file = "zipp-3.8.1.tar.gz", hash = "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2"}, -] +yarl = [] +zipp = [] diff --git a/pyproject.toml b/pyproject.toml index 2a0606a10c..0deb4f465b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,13 +70,12 @@ requests = "^2.25.1" pysftp = "^0.2.9" dropbox = "^11.20.0" aiohttp-middlewares = "^2.0.0" -cx-Freeze = "6.11.1" [tool.poetry.dev-dependencies] flake8 = "^3.7" autopep8 = "^1.4" coverage = "*" -cx_freeze = "^6.11.1" +cx_freeze = "~6.9" GitPython = "^3.1.17" jedi = "^0.13" Jinja2 = "^2.11" From 5079d05df103adf2690b161d1e726ebdb34038d9 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 2 Sep 2022 15:50:30 +0200 Subject: [PATCH 233/274] Hardcoded downgraded version of Poetry --- tools/create_env.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/create_env.ps1 b/tools/create_env.ps1 index 3f956e5c6a..cdb97d4942 100644 --- a/tools/create_env.ps1 +++ b/tools/create_env.ps1 @@ -68,6 +68,7 @@ function Install-Poetry() { } $env:POETRY_HOME="$openpype_root\.poetry" + $env:POETRY_VERSION="1.1.15" (Invoke-WebRequest -Uri https://install.python-poetry.org/ -UseBasicParsing).Content | & $($python) - } From 73309ffc324544726ecdbe5e43749d304637a91d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 2 Sep 2022 15:51:44 +0200 Subject: [PATCH 234/274] Revert "Fix - update cx-freeze" This reverts commit 4fc90655d7e5b6869650638e4b7ff064ebc2271e. --- poetry.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 726b248f8c..21b6bda880 100644 --- a/poetry.lock +++ b/poetry.lock @@ -339,7 +339,7 @@ test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", [[package]] name = "cx-freeze" -version = "6.11.1" +version = "6.9" description = "Create standalone executables from Python scripts" category = "dev" optional = false From d2240544ad2c00558114ef4122618ca2e73ad0e6 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 2 Sep 2022 15:55:58 +0200 Subject: [PATCH 235/274] Hardcoded downgraded version of Poetry Latest 1.2 version breaks build because cx-freeze. Latest cx-freeze works on Win, fails on Linux. --- tools/create_env.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/create_env.sh b/tools/create_env.sh index fba3942b87..1ecd960fe1 100755 --- a/tools/create_env.sh +++ b/tools/create_env.sh @@ -109,6 +109,7 @@ detect_python () { install_poetry () { echo -e "${BIGreen}>>>${RST} Installing Poetry ..." export POETRY_HOME="$openpype_root/.poetry" + export POETRY_VERSION="1.1.15" 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 - } From f0dc54f78e230c1d39c0c33b9360b97876d6ce22 Mon Sep 17 00:00:00 2001 From: Hayley GUILLOT Date: Fri, 2 Sep 2022 16:32:45 +0200 Subject: [PATCH 236/274] Update credentials.py Implementation got moved from emit_on_kitsu_login to the login function --- openpype/modules/kitsu/utils/credentials.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/openpype/modules/kitsu/utils/credentials.py b/openpype/modules/kitsu/utils/credentials.py index d853d5b437..013a9966c5 100644 --- a/openpype/modules/kitsu/utils/credentials.py +++ b/openpype/modules/kitsu/utils/credentials.py @@ -33,7 +33,10 @@ def validate_credentials( except gazu.exception.AuthFailedException: return False - emit_on_kitsu_login(login) + emit_event("kitsu.user.logged", + data={"username": login}, + source="kitsu") + return True @@ -103,15 +106,4 @@ def set_credentials_envs(login: str, password: str): password (str): Kitsu user password """ os.environ["KITSU_LOGIN"] = login - os.environ["KITSU_PWD"] = password - - -def emit_on_kitsu_login(login: str): - """Notifies listeners that Kitsu module succesfully connected, - and passes them data - - Args: - login (str): Kitsu username - """ - event_data = {"username": login} - emit_event("kitsu.user.logged", data=event_data, source="kitsu") + os.environ["KITSU_PWD"] = password \ No newline at end of file From 58fd5a1b097bd7d7e71f004b3fff8529296b1102 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Albert?= Date: Fri, 2 Sep 2022 16:32:52 +0200 Subject: [PATCH 237/274] Make to Optional Arguments --- openpype/modules/kitsu/utils/update_op_with_zou.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index f013251bb1..199c59053b 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -318,7 +318,7 @@ def write_project_to_op(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: ) -def sync_all_projects(login: str, password: str, ignore_projects: list = []): +def sync_all_projects(login: str, password: str, ignore_projects=[]): """Update all OP projects in DB with Zou data. Args: From a72b84a58695b11b6128a5985014c7a0ebea15dd Mon Sep 17 00:00:00 2001 From: Hayley GUILLOT Date: Fri, 2 Sep 2022 16:33:34 +0200 Subject: [PATCH 238/274] Forgot to lint --- openpype/modules/kitsu/utils/credentials.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/openpype/modules/kitsu/utils/credentials.py b/openpype/modules/kitsu/utils/credentials.py index 013a9966c5..adcfb07cd5 100644 --- a/openpype/modules/kitsu/utils/credentials.py +++ b/openpype/modules/kitsu/utils/credentials.py @@ -33,10 +33,8 @@ def validate_credentials( except gazu.exception.AuthFailedException: return False - emit_event("kitsu.user.logged", - data={"username": login}, - source="kitsu") - + emit_event("kitsu.user.logged", data={"username": login}, source="kitsu") + return True @@ -106,4 +104,4 @@ def set_credentials_envs(login: str, password: str): password (str): Kitsu user password """ os.environ["KITSU_LOGIN"] = login - os.environ["KITSU_PWD"] = password \ No newline at end of file + os.environ["KITSU_PWD"] = password From 88e4798b535c47cefce3dc2a1ed9aacf60dd0f68 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 2 Sep 2022 19:50:56 +0200 Subject: [PATCH 239/274] Remove old type hint --- .../modules/deadline/plugins/publish/submit_maya_deadline.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index bb48fe6902..68d55fef5d 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -641,7 +641,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): return result def _patch_workfile(self): - # type: (str, dict) -> [str, None] """Patch Maya scene. This will take list of patches (lines to add) and apply them to From 153f2c2e95526f0ace683249486316cfd5bc213e Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sat, 3 Sep 2022 04:15:14 +0000 Subject: [PATCH 240/274] [Automated] Bump version --- CHANGELOG.md | 46 ++++++++++++++++++++++----------------------- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c8834dd49..b35e89e96e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,23 +1,42 @@ # Changelog -## [3.14.2-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.14.2-nightly.2](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.1...HEAD) **🆕 New features** +- Nuke: Build workfile by template [\#3763](https://github.com/pypeclub/OpenPype/pull/3763) - Houdini: Publishing workfiles [\#3697](https://github.com/pypeclub/OpenPype/pull/3697) +**🚀 Enhancements** + +- SyncServer: Added cli commands for sync server [\#3765](https://github.com/pypeclub/OpenPype/pull/3765) +- Maya: move set render settings menu entry [\#3669](https://github.com/pypeclub/OpenPype/pull/3669) +- Scene Inventory: Maya add actions to select from or to scene [\#3659](https://github.com/pypeclub/OpenPype/pull/3659) + **🐛 Bug fixes** +- Fix - changed format of version string in pyproject.toml [\#3777](https://github.com/pypeclub/OpenPype/pull/3777) - Ftrack status fix typo prgoress -\> progress [\#3761](https://github.com/pypeclub/OpenPype/pull/3761) +- Fix version resolution [\#3757](https://github.com/pypeclub/OpenPype/pull/3757) +- Maya: `containerise` dont skip empty values [\#3674](https://github.com/pypeclub/OpenPype/pull/3674) **🔀 Refactored code** +- AfterEffects: Use new Extractor location [\#3784](https://github.com/pypeclub/OpenPype/pull/3784) +- General: Remove unused teshost [\#3773](https://github.com/pypeclub/OpenPype/pull/3773) +- General: Copied 'Extractor' plugin to publish pipeline [\#3771](https://github.com/pypeclub/OpenPype/pull/3771) +- General: Create project function moved to client code [\#3766](https://github.com/pypeclub/OpenPype/pull/3766) - General: Move hostdirname functionality into host [\#3749](https://github.com/pypeclub/OpenPype/pull/3749) -- Webpublisher: Webpublisher is used as addon [\#3740](https://github.com/pypeclub/OpenPype/pull/3740) +- General: Move publish utils to pipeline [\#3745](https://github.com/pypeclub/OpenPype/pull/3745) - Houdini: Define houdini as addon [\#3735](https://github.com/pypeclub/OpenPype/pull/3735) - Flame: Defined flame as addon [\#3732](https://github.com/pypeclub/OpenPype/pull/3732) +- Resolve: Define resolve as addon [\#3727](https://github.com/pypeclub/OpenPype/pull/3727) + +**Merged pull requests:** + +- Standalone Publisher: Ignore empty labels, then still use name like other asset models [\#3779](https://github.com/pypeclub/OpenPype/pull/3779) ## [3.14.1](https://github.com/pypeclub/OpenPype/tree/3.14.1) (2022-08-30) @@ -63,6 +82,7 @@ - General: Move delivery logic to pipeline [\#3751](https://github.com/pypeclub/OpenPype/pull/3751) - General: Host addons cleanup [\#3744](https://github.com/pypeclub/OpenPype/pull/3744) +- Webpublisher: Webpublisher is used as addon [\#3740](https://github.com/pypeclub/OpenPype/pull/3740) - Photoshop: Defined photoshop as addon [\#3736](https://github.com/pypeclub/OpenPype/pull/3736) - Harmony: Defined harmony as addon [\#3734](https://github.com/pypeclub/OpenPype/pull/3734) - General: Module interfaces cleanup [\#3731](https://github.com/pypeclub/OpenPype/pull/3731) @@ -93,6 +113,7 @@ **🚀 Enhancements** - Ftrack: Addiotional component metadata [\#3685](https://github.com/pypeclub/OpenPype/pull/3685) +- Ftrack: Set task status on farm publishing [\#3680](https://github.com/pypeclub/OpenPype/pull/3680) - Ftrack: Set task status on task creation in integrate hierarchy [\#3675](https://github.com/pypeclub/OpenPype/pull/3675) - Maya: Disable rendering of all lights for render instances submitted through Deadline. [\#3661](https://github.com/pypeclub/OpenPype/pull/3661) - General: Optimized OCIO configs [\#3650](https://github.com/pypeclub/OpenPype/pull/3650) @@ -103,43 +124,22 @@ - General: Fix finding of last version [\#3656](https://github.com/pypeclub/OpenPype/pull/3656) - General: Extract Review can scale with pixel aspect ratio [\#3644](https://github.com/pypeclub/OpenPype/pull/3644) - Maya: Refactor moved usage of CreateRender settings [\#3643](https://github.com/pypeclub/OpenPype/pull/3643) -- General: Hero version representations have full context [\#3638](https://github.com/pypeclub/OpenPype/pull/3638) -- Nuke: color settings for render write node is working now [\#3632](https://github.com/pypeclub/OpenPype/pull/3632) -- Maya: FBX support for update in reference loader [\#3631](https://github.com/pypeclub/OpenPype/pull/3631) **🔀 Refactored code** - General: Use client projects getter [\#3673](https://github.com/pypeclub/OpenPype/pull/3673) - Resolve: Match folder structure to other hosts [\#3653](https://github.com/pypeclub/OpenPype/pull/3653) - Maya: Hosts as modules [\#3647](https://github.com/pypeclub/OpenPype/pull/3647) -- TimersManager: Plugins are in timers manager module [\#3639](https://github.com/pypeclub/OpenPype/pull/3639) -- General: Move workfiles functions into pipeline [\#3637](https://github.com/pypeclub/OpenPype/pull/3637) **Merged pull requests:** - Deadline: Global job pre load is not Pype 2 compatible [\#3666](https://github.com/pypeclub/OpenPype/pull/3666) - Maya: Remove unused get current renderer logic [\#3645](https://github.com/pypeclub/OpenPype/pull/3645) -- Kitsu|Fix: Movie project type fails & first loop children names [\#3636](https://github.com/pypeclub/OpenPype/pull/3636) -- fix the bug of failing to extract look when UDIMs format used in AiImage [\#3628](https://github.com/pypeclub/OpenPype/pull/3628) ## [3.13.0](https://github.com/pypeclub/OpenPype/tree/3.13.0) (2022-08-09) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.13.0-nightly.1...3.13.0) -**🚀 Enhancements** - -- Editorial: Mix audio use side file for ffmpeg filters [\#3630](https://github.com/pypeclub/OpenPype/pull/3630) - -**🐛 Bug fixes** - -- Maya: fix aov separator in Redshift [\#3625](https://github.com/pypeclub/OpenPype/pull/3625) -- Fix for multi-version build on Mac [\#3622](https://github.com/pypeclub/OpenPype/pull/3622) -- Ftrack: Sync hierarchical attributes can handle new created entities [\#3621](https://github.com/pypeclub/OpenPype/pull/3621) - -**🔀 Refactored code** - -- General: Plugin settings handled by plugins [\#3623](https://github.com/pypeclub/OpenPype/pull/3623) - ## [3.12.2](https://github.com/pypeclub/OpenPype/tree/3.12.2) (2022-07-27) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.12.2-nightly.4...3.12.2) diff --git a/openpype/version.py b/openpype/version.py index 0c114b6060..26b03c37e5 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.14.2-nightly.1" +__version__ = "3.14.2-nightly.2" diff --git a/pyproject.toml b/pyproject.toml index 0deb4f465b..f74f40c561 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.14.2+nightly.1" # OpenPype +version = "3.14.2-nightly.2" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 5645bcb353b13b1711ba67e0a3b394b273e7cef3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 3 Sep 2022 13:17:10 +0200 Subject: [PATCH 241/274] Use custom plugin info per type of plugin submission --- .../plugins/publish/submit_maya_deadline.py | 127 +++++++++--------- 1 file changed, 66 insertions(+), 61 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 68d55fef5d..2a41d92efd 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -49,6 +49,30 @@ class MayaPluginInfo: RenderSetupIncludeLights = attr.ib(default=None) # Include all lights flag +@attr.s +class PythonPluginInfo: + ScriptFile = attr.ib() + Version = attr.ib(default="3.6") + Arguments = attr.ib(default=None) + SingleFrameOnly = attr.ib(default=None) + + +@attr.s +class VRayPluginInfo: + InputFilename = attr.ib(default=None) # Input + SeparateFilesPerFrame = attr.ib(default=None) + VRayEngine = attr.ib(default="V-Ray") + Width = attr.ib(default=None) + Height = attr.ib(default=None) # Mandatory for Deadline + OutputFilePath = attr.ib(default=True) + OutputFileName = attr.ib(default=None) # Render only this layer + + +@attr.s +class ArnoldPluginInfo: + ArnoldFile = attr.ib(default=None) + + class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): label = "Submit Render to Deadline" @@ -479,26 +503,19 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): def _get_vray_export_payload(self, data): job_info = copy.deepcopy(self.job_info) - job_info.Name = self._job_info_label("Export") # Get V-Ray settings info to compute output path - vray_settings = cmds.ls(type="VRaySettingsNode") - node = vray_settings[0] - template = cmds.getAttr("{}.vrscene_filename".format(node)) - scene, _ = os.path.splitext(data["filename"]) - first_file = self.format_vray_output_filename(scene, template) - first_file = "{}/{}".format(data["workspace"], first_file) - output = os.path.dirname(first_file) + vray_scene = self.format_vray_output_filename() plugin_info = { "Renderer": "vray", "SkipExistingFrames": True, "UseLegacyRenderLayers": True, - "OutputFilePath": output + "OutputFilePath": os.path.dirname(vray_scene) } - return job_info, plugin_info + return job_info, attr.asdict(plugin_info) def _get_arnold_export_payload(self, data): @@ -515,8 +532,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): script = os.path.normpath(module_path) job_info = copy.deepcopy(self.job_info) - plugin_info = copy.deepcopy(self.plugin_info) - job_info.Name = self._job_info_label("Export") # Force a single frame Python job @@ -540,14 +555,14 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): continue job_info.EnvironmentKeyValue[key] = value - plugin_info.update({ - "Version": "3.6", - "ScriptFile": script, - "Arguments": "", - "SingleFrameOnly": "True", - }) + plugin_info = PythonPluginInfo( + ScriptFile=script, + Version="3.6", + Arguments="", + SingleFrameOnly="True" + ) - return job_info, plugin_info + return job_info, attr.asdict(plugin_info) def _get_vray_render_payload(self, data): @@ -558,27 +573,17 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): job_info.OverrideTaskExtraInfoNames = False # Plugin Info - vray_settings = cmds.ls(type="VRaySettingsNode") - node = vray_settings[0] - template = cmds.getAttr("{}.vrscene_filename".format(node)) - # "vrayscene//_/" + plugin_info = VRayPluginInfo( + InputFilename=self.format_vray_output_filename(), + SeparateFilesPerFrame=False, + VRayEngine="V-Ray", + Width=self._instance.data["resolutionWidth"], + Height=self._instance.data["resolutionHeight"], + OutputFilePath=job_info.OutputDirectory[0], + OutputFileName=job_info.OutputFilename[0] + ) - scene, _ = os.path.splitext(self.scene_path) - first_file = self.format_vray_output_filename(scene, template) - first_file = "{}/{}".format(data["workspace"], first_file) - - plugin_info = { - "InputFilename": first_file, - "SeparateFilesPerFrame": True, - "VRayEngine": "V-Ray", - - "Width": self._instance.data["resolutionWidth"], - "Height": self._instance.data["resolutionHeight"], - "OutputFilePath": job_info.OutputDirectory[0], - "OutputFileName": job_info.OutputFilename[0] - } - - return job_info, plugin_info + return job_info, attr.asdict(plugin_info) def _get_arnold_render_payload(self, data): @@ -590,55 +595,55 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # Plugin Info ass_file, _ = os.path.splitext(data["output_filename_0"]) - first_file = ass_file + ".ass" - plugin_info = { - "ArnoldFile": first_file, - } + ass_filepath = ass_file + ".ass" - return job_info, plugin_info + plugin_info = ArnoldPluginInfo( + ArnoldFile=ass_filepath + ) - def format_vray_output_filename(self, filename, template, dir=False): + return job_info, attr.asdict(plugin_info) + + def format_vray_output_filename(self): """Format the expected output file of the Export job. Example: /_/ - "shot010_v006/shot010_v006_CHARS/CHARS" - - Args: - instance: - filename(str): - dir(bool): - + "shot010_v006/shot010_v006_CHARS/CHARS_0001.vrscene" Returns: str """ + + # "vrayscene//_/" + vray_settings = cmds.ls(type="VRaySettingsNode") + node = vray_settings[0] + template = cmds.getAttr("{}.vrscene_filename".format(node)) + scene, _ = os.path.splitext(self.scene_path) + def smart_replace(string, key_values): new_string = string for key, value in key_values.items(): new_string = new_string.replace(key, value) return new_string - # Ensure filename has no extension - file_name, _ = os.path.splitext(filename) + # Get workfile scene path without extension to format vrscene_filename + scene_filename = os.path.basename(self.scene_path) + scene_filename_no_ext, _ = os.path.splitext(scene_filename) layer = self._instance.data['setMembers'] # Reformat without tokens output_path = smart_replace( template, - {"": file_name, + {"": scene_filename_no_ext, "": layer}) - if dir: - return output_path.replace("\\", "/") - start_frame = int(self._instance.data["frameStartHandle"]) + workspace = self._instance.context.data["workspace"] filename_zero = "{}_{:04d}.vrscene".format(output_path, start_frame) + filepath_zero = os.path.join(workspace, filename_zero) - result = filename_zero.replace("\\", "/") - - return result + return filepath_zero.replace("\\", "/") def _patch_workfile(self): """Patch Maya scene. From 507dac4aa9f50e8978a841067262ce33e77cf5e0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 3 Sep 2022 15:06:24 +0200 Subject: [PATCH 242/274] Ensure integer math for _format_tiles See #3758 --- .../plugins/publish/submit_maya_deadline.py | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 2a41d92efd..7c486b7c34 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -754,14 +754,21 @@ def _format_tiles( used for assembler configuration. """ - tile = 0 + # Math used requires integers for correct output - as such + # we ensure our inputs are correct. + assert type(tiles_x) is int, "tiles_x must be an integer" + assert type(tiles_y) is int, "tiles_y must be an integer" + assert type(width) is int, "width must be an integer" + assert type(height) is int, "height must be an integer" + out = {"JobInfo": {}, "PluginInfo": {}} cfg = OrderedDict() - w_space = width / tiles_x - h_space = height / tiles_y + w_space = width // tiles_x + h_space = height // tiles_y cfg["TilesCropped"] = "False" + tile = 0 for tile_x in range(1, tiles_x + 1): for tile_y in reversed(range(1, tiles_y + 1)): tile_prefix = "_tile_{}x{}_{}x{}_".format( @@ -769,10 +776,10 @@ def _format_tiles( tiles_x, tiles_y ) - top = int(height - (tile_y * h_space)) - bottom = int(height - ((tile_y - 1) * h_space) - 1) - left = int((tile_x - 1) * w_space) - right = int((tile_x * w_space) - 1) + top = height - (tile_y * h_space) + bottom = height - ((tile_y - 1) * h_space) - 1 + left = (tile_x - 1) * w_space + right = (tile_x * w_space) - 1 # Job Info new_filename = "{}/{}{}".format( From f54f4cf99c824e927e5cbe24c7a02ec0e4a4fc62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Albert?= Date: Sun, 4 Sep 2022 22:43:25 +0200 Subject: [PATCH 243/274] Kitsu : Modification default value for Ignore_projects --- openpype/modules/kitsu/utils/update_op_with_zou.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 199c59053b..26cd125e15 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -318,7 +318,7 @@ def write_project_to_op(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: ) -def sync_all_projects(login: str, password: str, ignore_projects=[]): +def sync_all_projects(login: str, password: str, ignore_projects: list = None): """Update all OP projects in DB with Zou data. Args: @@ -340,8 +340,9 @@ def sync_all_projects(login: str, password: str, ignore_projects=[]): dbcon.install() all_projects = gazu.project.all_open_projects() for project in all_projects: - if project["name"] not in ignore_projects: - sync_project_from_kitsu(dbcon, project) + if ignore_projects and project["name"] in ignore_projects: + continue + sync_project_from_kitsu(dbcon, project) def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): From fb48faf386c8d53c737ac9a6a00287730c934217 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Albert?= Date: Sun, 4 Sep 2022 22:49:07 +0200 Subject: [PATCH 244/274] Kitsu : Ignore_projects - minor fix - indent --- openpype/modules/kitsu/utils/update_op_with_zou.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 26cd125e15..55a7bdc51d 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -341,7 +341,7 @@ def sync_all_projects(login: str, password: str, ignore_projects: list = None): all_projects = gazu.project.all_open_projects() for project in all_projects: if ignore_projects and project["name"] in ignore_projects: - continue + continue sync_project_from_kitsu(dbcon, project) From 81d8a53dbad5313fc435ca5669500e868f4c1134 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 5 Sep 2022 12:20:16 +0200 Subject: [PATCH 245/274] fix function name --- openpype/client/__init__.py | 4 ++-- openpype/lib/avalon_context.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index cf3ce1ae46..7831afd8ad 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -48,7 +48,7 @@ from .entities import ( from .entity_links import ( get_linked_asset_ids, get_linked_assets, - get_linked_representation_ids, + get_linked_representation_id, ) from .operations import ( @@ -102,7 +102,7 @@ __all__ = ( "get_linked_asset_ids", "get_linked_assets", - "get_linked_representation_ids", + "get_linked_representation_id", "create_project", ) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 470c40d0d7..c890e08d3e 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -1061,14 +1061,14 @@ def get_linked_ids_for_representations( Function will be removed after release version 3.16.* """ - from openpype.client import get_linked_representation_ids + from openpype.client import get_linked_representation_id if not isinstance(repre_ids, list): repre_ids = [repre_ids] output = [] for repre_id in repre_ids: - output.extend(get_linked_representation_ids( + output.extend(get_linked_representation_id( project_name, repre_id=repre_id, link_type=link_type, From 88ab0462b5b8861f99ef0eb069d8f1e2ff4ccfd3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 5 Sep 2022 12:21:19 +0200 Subject: [PATCH 246/274] fix new import path --- openpype/lib/avalon_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index c890e08d3e..12f4a5198b 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -1039,7 +1039,7 @@ def get_last_workfile( ) -@deprecated("openpype.client.get_linked_ids_for_representations") +@deprecated("openpype.client.get_linked_representation_id") def get_linked_ids_for_representations( project_name, repre_ids, dbcon=None, link_type=None, max_depth=0 ): From 469ba24879956e5c1a83d6d042f6503d3e254593 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 5 Sep 2022 12:27:23 +0200 Subject: [PATCH 247/274] Fix - updated to not use deprecated function --- openpype/plugins/load/add_site.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/load/add_site.py b/openpype/plugins/load/add_site.py index 388a871e9d..ac931e41db 100644 --- a/openpype/plugins/load/add_site.py +++ b/openpype/plugins/load/add_site.py @@ -1,4 +1,4 @@ -from openpype.client import get_linked_ids_for_representations +from openpype.client import get_linked_representation_id from openpype.modules import ModulesManager from openpype.pipeline import load from openpype.modules.sync_server.utils import SiteAlreadyPresentError @@ -45,7 +45,7 @@ class AddSyncSite(load.LoaderPlugin): force=True) if family == "workfile": - links = get_linked_ids_for_representations( + links = get_linked_representation_id( project_name, repre_id=repre_id, link_type="reference" From 5908f49b685a981d08d7ea1ff841d567018a7e76 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 6 Sep 2022 10:56:01 +0200 Subject: [PATCH 248/274] updating README file --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b8c04f8b49..a2f442b640 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ It can be built and ran on all common platforms. We develop and test on the foll - **Linux** - **Ubuntu** 20.04 LTS - **Centos** 7 -- **Mac OSX** +- **Mac OSX** - **10.15** Catalina - **11.1** Big Sur (using Rosetta2) @@ -287,6 +287,14 @@ To run tests, execute `.\tools\run_tests(.ps1|.sh)`. **Note that it needs existing virtual environment.** + +Developer tools +------------- + +In case you wish to add your own tools to `.\tools` folder without git tracking, it is possible by adding it with `dev_*` suffix (`dev_clear_pyc(.ps1|.sh)`). + + + ## Contributors ✨ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): From a0e241b02fbfdee18b8ded65af89eedb343793d2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 6 Sep 2022 10:58:22 +0200 Subject: [PATCH 249/274] README fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a2f442b640..a3d3cf1dbb 100644 --- a/README.md +++ b/README.md @@ -291,7 +291,7 @@ To run tests, execute `.\tools\run_tests(.ps1|.sh)`. Developer tools ------------- -In case you wish to add your own tools to `.\tools` folder without git tracking, it is possible by adding it with `dev_*` suffix (`dev_clear_pyc(.ps1|.sh)`). +In case you wish to add your own tools to `.\tools` folder without git tracking, it is possible by adding it with `dev_*` suffix (example: `dev_clear_pyc(.ps1|.sh)`). From 70a17a8876541de84d98bd8f502421827fd78751 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 6 Sep 2022 11:39:58 +0200 Subject: [PATCH 250/274] hiero: instances detection - timeline no need to retime --- openpype/hosts/hiero/plugins/publish/precollect_instances.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/hiero/plugins/publish/precollect_instances.py b/openpype/hosts/hiero/plugins/publish/precollect_instances.py index 0c7dbc1f22..84f2927fc7 100644 --- a/openpype/hosts/hiero/plugins/publish/precollect_instances.py +++ b/openpype/hosts/hiero/plugins/publish/precollect_instances.py @@ -318,10 +318,9 @@ class PrecollectInstances(pyblish.api.ContextPlugin): @staticmethod def create_otio_time_range_from_timeline_item_data(track_item): - speed = track_item.playbackSpeed() timeline = phiero.get_current_sequence() frame_start = int(track_item.timelineIn()) - frame_duration = int((track_item.duration() - 1) / speed) + frame_duration = int(track_item.duration()) fps = timeline.framerate().toFloat() return hiero_export.create_otio_time_range( From 1ed5ef7dcdf9cd2183f009c541e4c52f5374075d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 6 Sep 2022 13:16:01 +0200 Subject: [PATCH 251/274] OP-3863 - attempt to speed up ExtractImage If workfile has a large number of layers (hundreds or thousands), ExtractImage wasn't too efficient. It was hiding/showing layers too many times. Current logic is to hide all, show only publishable layers of instance, save to image, hide them again. get_layers replaced by argument if possible. --- openpype/hosts/photoshop/api/lib.py | 11 +- openpype/hosts/photoshop/api/ws_stub.py | 19 ++- .../plugins/publish/extract_image.py | 120 +++++++++++------- 3 files changed, 97 insertions(+), 53 deletions(-) diff --git a/openpype/hosts/photoshop/api/lib.py b/openpype/hosts/photoshop/api/lib.py index 73a546604f..221b4314e6 100644 --- a/openpype/hosts/photoshop/api/lib.py +++ b/openpype/hosts/photoshop/api/lib.py @@ -64,10 +64,15 @@ def maintained_selection(): @contextlib.contextmanager -def maintained_visibility(): - """Maintain visibility during context.""" +def maintained_visibility(layers=None): + """Maintain visibility during context. + + Args: + layers (list) of PSItem (used for caching) + """ visibility = {} - layers = stub().get_layers() + if not layers: + layers = stub().get_layers() for layer in layers: visibility[layer.id] = layer.visible try: diff --git a/openpype/hosts/photoshop/api/ws_stub.py b/openpype/hosts/photoshop/api/ws_stub.py index b49bf1c73f..2c4d0ad5fc 100644 --- a/openpype/hosts/photoshop/api/ws_stub.py +++ b/openpype/hosts/photoshop/api/ws_stub.py @@ -229,10 +229,11 @@ class PhotoshopServerStub: return self._get_layers_in_layers(parent_ids) - def get_layers_in_layers_ids(self, layers_ids): + def get_layers_in_layers_ids(self, layers_ids, layers=None): """Return all layers that belong to layers (might be groups). Args: + layers_ids layers : Returns: @@ -240,10 +241,13 @@ class PhotoshopServerStub: """ parent_ids = set(layers_ids) - return self._get_layers_in_layers(parent_ids) + return self._get_layers_in_layers(parent_ids, layers) - def _get_layers_in_layers(self, parent_ids): - all_layers = self.get_layers() + def _get_layers_in_layers(self, parent_ids, layers=None): + if not layers: + layers = self.get_layers() + + all_layers = layers ret = [] for layer in all_layers: @@ -394,14 +398,17 @@ class PhotoshopServerStub: self.hide_all_others_layers_ids(extract_ids) - def hide_all_others_layers_ids(self, extract_ids): + def hide_all_others_layers_ids(self, extract_ids, layers=None): """hides all layers that are not part of the list or that are not children of this list Args: extract_ids (list): list of integer that should be visible + layers (list) of PSItem (used for caching) """ - for layer in self.get_layers(): + if not layers: + layers = self.get_layers() + for layer in layers: if layer.visible and layer.id not in extract_ids: self.set_visible(layer.id, False) diff --git a/openpype/hosts/photoshop/plugins/publish/extract_image.py b/openpype/hosts/photoshop/plugins/publish/extract_image.py index a133e33409..7543af95bd 100644 --- a/openpype/hosts/photoshop/plugins/publish/extract_image.py +++ b/openpype/hosts/photoshop/plugins/publish/extract_image.py @@ -1,61 +1,93 @@ import os -import openpype.api +import pyblish.api + +from openpype.pipeline import publish from openpype.hosts.photoshop import api as photoshop -class ExtractImage(openpype.api.Extractor): - """Produce a flattened image file from instance - - This plug-in takes into account only the layers in the group. - """ +class ExtractImage(pyblish.api.ContextPlugin): + """Save scene before extraction.""" + order = publish.Extractor.order - 0.48 label = "Extract Image" hosts = ["photoshop"] + families = ["image", "background"] formats = ["png", "jpg"] - def process(self, instance): - staging_dir = self.staging_dir(instance) - self.log.info("Outputting image to {}".format(staging_dir)) - - # Perform extraction + def process(self, context): stub = photoshop.stub() - files = {} + hidden_layer_ids = set() + + all_layers = stub.get_layers() + for layer in all_layers: + if not layer.visible: + hidden_layer_ids.add(layer.id) + stub.hide_all_others_layers_ids([], layers=all_layers) + with photoshop.maintained_selection(): - self.log.info("Extracting %s" % str(list(instance))) - with photoshop.maintained_visibility(): - ids = set() - layer = instance.data.get("layer") - if layer: - ids.add(layer.id) - add_ids = instance.data.pop("ids", None) - if add_ids: - ids.update(set(add_ids)) - extract_ids = set([ll.id for ll in stub. - get_layers_in_layers_ids(ids)]) - stub.hide_all_others_layers_ids(extract_ids) + # self.log.info("Extracting %s" % str(list(instance))) + with photoshop.maintained_visibility(layers=all_layers): + for instance in context: + if instance.data["family"] not in self.families: + continue - file_basename = os.path.splitext( - stub.get_active_document_name() - )[0] - for extension in self.formats: - _filename = "{}.{}".format(file_basename, extension) - files[extension] = _filename + staging_dir = self.staging_dir(instance) + self.log.info("Outputting image to {}".format(staging_dir)) - full_filename = os.path.join(staging_dir, _filename) - stub.saveAs(full_filename, extension, True) - self.log.info(f"Extracted: {extension}") + # Perform extraction + files = {} + ids = set() + layer = instance.data.get("layer") + if layer: + ids.add(layer.id) + add_ids = instance.data.pop("ids", None) + if add_ids: + ids.update(set(add_ids)) + extract_ids = set([ll.id for ll in stub. + get_layers_in_layers_ids(ids, all_layers) + if ll.id not in hidden_layer_ids]) - representations = [] - for extension, filename in files.items(): - representations.append({ - "name": extension, - "ext": extension, - "files": filename, - "stagingDir": staging_dir - }) - instance.data["representations"] = representations - instance.data["stagingDir"] = staging_dir + for extracted_id in extract_ids: + stub.set_visible(extracted_id, True) - self.log.info(f"Extracted {instance} to {staging_dir}") + file_basename = os.path.splitext( + stub.get_active_document_name() + )[0] + for extension in self.formats: + _filename = "{}.{}".format(file_basename, + extension) + files[extension] = _filename + + full_filename = os.path.join(staging_dir, + _filename) + stub.saveAs(full_filename, extension, True) + self.log.info(f"Extracted: {extension}") + + representations = [] + for extension, filename in files.items(): + representations.append({ + "name": extension, + "ext": extension, + "files": filename, + "stagingDir": staging_dir + }) + instance.data["representations"] = representations + instance.data["stagingDir"] = staging_dir + + self.log.info(f"Extracted {instance} to {staging_dir}") + + for extracted_id in extract_ids: + stub.set_visible(extracted_id, False) + + def staging_dir(self, instance): + """Provide a temporary directory in which to store extracted files + + Upon calling this method the staging directory is stored inside + the instance.data['stagingDir'] + """ + + from openpype.pipeline.publish import get_instance_staging_dir + + return get_instance_staging_dir(instance) From d00eb29ca1bd8fcda97a40a14f8ae03d171c4853 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 6 Sep 2022 13:20:48 +0200 Subject: [PATCH 252/274] Remove instance._log legacy code that has no use anymore --- .../modules/deadline/plugins/publish/submit_publish_job.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 2647dcf0cb..c9d1daffd1 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -700,9 +700,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): self.context = context self.anatomy = instance.context.data["anatomy"] - if hasattr(instance, "_log"): - data['_log'] = instance._log - asset = data.get("asset") or legacy_io.Session["AVALON_ASSET"] subset = data.get("subset") From 96f8c3b7b3a1cf524c2ed1a815092ef2f55a2e9b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 6 Sep 2022 13:23:41 +0200 Subject: [PATCH 253/274] OP-3863 - fixed doc string --- .../hosts/photoshop/plugins/publish/extract_image.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/photoshop/plugins/publish/extract_image.py b/openpype/hosts/photoshop/plugins/publish/extract_image.py index 7543af95bd..5bdb3ef681 100644 --- a/openpype/hosts/photoshop/plugins/publish/extract_image.py +++ b/openpype/hosts/photoshop/plugins/publish/extract_image.py @@ -7,7 +7,15 @@ from openpype.hosts.photoshop import api as photoshop class ExtractImage(pyblish.api.ContextPlugin): - """Save scene before extraction.""" + """Extract all layers (groups) marked for publish. + + Usually publishable instance is created as a wrapper of layer(s). For each + publishable instance so many images as there is 'formats' is created. + + Logic tries to hide/unhide layers minimum times. + + Called once for all publishable instances. + """ order = publish.Extractor.order - 0.48 label = "Extract Image" @@ -27,7 +35,6 @@ class ExtractImage(pyblish.api.ContextPlugin): stub.hide_all_others_layers_ids([], layers=all_layers) with photoshop.maintained_selection(): - # self.log.info("Extracting %s" % str(list(instance))) with photoshop.maintained_visibility(layers=all_layers): for instance in context: if instance.data["family"] not in self.families: From c8efd0d67e5600a093d024786dd16717c9f3d16c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 6 Sep 2022 17:07:46 +0200 Subject: [PATCH 254/274] removed f-string formatting for py2 compatibility --- openpype/hosts/resolve/utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/resolve/utils.py b/openpype/hosts/resolve/utils.py index 382a7cf344..aa8ad3008b 100644 --- a/openpype/hosts/resolve/utils.py +++ b/openpype/hosts/resolve/utils.py @@ -17,7 +17,7 @@ def setup(env): # collect script dirs if us_env: - log.info(f"Utility Scripts Env: `{us_env}`") + log.info("Utility Scripts Env: `{}`".format(us_env)) us_paths = us_env.split( os.pathsep) + us_paths @@ -25,8 +25,8 @@ def setup(env): for path in us_paths: scripts.update({path: os.listdir(path)}) - log.info(f"Utility Scripts Dir: `{us_paths}`") - log.info(f"Utility Scripts: `{scripts}`") + log.info("Utility Scripts Dir: `{}`".format(us_paths)) + log.info("Utility Scripts: `{}`".format(scripts)) # make sure no script file is in folder for s in os.listdir(us_dir): @@ -44,7 +44,7 @@ def setup(env): # script in script list src = os.path.join(d, s) dst = os.path.join(us_dir, s) - log.info(f"Copying `{src}` to `{dst}`...") + log.info("Copying `{}` to `{}`...".format(src, dst)) if os.path.isdir(src): shutil.copytree( src, dst, symlinks=False, From de52a7d61b970b6df353130295568a2242b6b902 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 6 Sep 2022 17:21:32 +0200 Subject: [PATCH 255/274] last f-string removement --- openpype/hosts/resolve/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/resolve/utils.py b/openpype/hosts/resolve/utils.py index aa8ad3008b..d5c133bbf5 100644 --- a/openpype/hosts/resolve/utils.py +++ b/openpype/hosts/resolve/utils.py @@ -31,7 +31,7 @@ def setup(env): # make sure no script file is in folder for s in os.listdir(us_dir): path = os.path.join(us_dir, s) - log.info(f"Removing `{path}`...") + log.info("Removing `{}`...".format(path)) if os.path.isdir(path): shutil.rmtree(path, onerror=None) else: From 915cba631c42a822be8f71517d081a43a09cc177 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Wed, 7 Sep 2022 04:24:55 +0000 Subject: [PATCH 256/274] [Automated] Bump version --- CHANGELOG.md | 18 +++++++++--------- openpype/version.py | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b35e89e96e..6754f1e2e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,22 +1,25 @@ # Changelog -## [3.14.2-nightly.2](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.14.2-nightly.3](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.1...HEAD) **🆕 New features** - Nuke: Build workfile by template [\#3763](https://github.com/pypeclub/OpenPype/pull/3763) -- Houdini: Publishing workfiles [\#3697](https://github.com/pypeclub/OpenPype/pull/3697) **🚀 Enhancements** +- Photoshop: attempt to speed up ExtractImage [\#3793](https://github.com/pypeclub/OpenPype/pull/3793) - SyncServer: Added cli commands for sync server [\#3765](https://github.com/pypeclub/OpenPype/pull/3765) +- Blender: Publisher collect workfile representation [\#3670](https://github.com/pypeclub/OpenPype/pull/3670) - Maya: move set render settings menu entry [\#3669](https://github.com/pypeclub/OpenPype/pull/3669) - Scene Inventory: Maya add actions to select from or to scene [\#3659](https://github.com/pypeclub/OpenPype/pull/3659) **🐛 Bug fixes** +- Resolve: Addon import is Python 2 compatible [\#3798](https://github.com/pypeclub/OpenPype/pull/3798) +- nuke: validate write node is not failing due wrong type [\#3780](https://github.com/pypeclub/OpenPype/pull/3780) - Fix - changed format of version string in pyproject.toml [\#3777](https://github.com/pypeclub/OpenPype/pull/3777) - Ftrack status fix typo prgoress -\> progress [\#3761](https://github.com/pypeclub/OpenPype/pull/3761) - Fix version resolution [\#3757](https://github.com/pypeclub/OpenPype/pull/3757) @@ -24,10 +27,14 @@ **🔀 Refactored code** +- Photoshop: Use new Extractor location [\#3789](https://github.com/pypeclub/OpenPype/pull/3789) +- Blender: Use new Extractor location [\#3787](https://github.com/pypeclub/OpenPype/pull/3787) - AfterEffects: Use new Extractor location [\#3784](https://github.com/pypeclub/OpenPype/pull/3784) - General: Remove unused teshost [\#3773](https://github.com/pypeclub/OpenPype/pull/3773) - General: Copied 'Extractor' plugin to publish pipeline [\#3771](https://github.com/pypeclub/OpenPype/pull/3771) +- General: Move queries of asset and representation links [\#3770](https://github.com/pypeclub/OpenPype/pull/3770) - General: Create project function moved to client code [\#3766](https://github.com/pypeclub/OpenPype/pull/3766) +- General: Move delivery logic to pipeline [\#3751](https://github.com/pypeclub/OpenPype/pull/3751) - General: Move hostdirname functionality into host [\#3749](https://github.com/pypeclub/OpenPype/pull/3749) - General: Move publish utils to pipeline [\#3745](https://github.com/pypeclub/OpenPype/pull/3745) - Houdini: Define houdini as addon [\#3735](https://github.com/pypeclub/OpenPype/pull/3735) @@ -80,7 +87,6 @@ **🔀 Refactored code** -- General: Move delivery logic to pipeline [\#3751](https://github.com/pypeclub/OpenPype/pull/3751) - General: Host addons cleanup [\#3744](https://github.com/pypeclub/OpenPype/pull/3744) - Webpublisher: Webpublisher is used as addon [\#3740](https://github.com/pypeclub/OpenPype/pull/3740) - Photoshop: Defined photoshop as addon [\#3736](https://github.com/pypeclub/OpenPype/pull/3736) @@ -116,25 +122,19 @@ - Ftrack: Set task status on farm publishing [\#3680](https://github.com/pypeclub/OpenPype/pull/3680) - Ftrack: Set task status on task creation in integrate hierarchy [\#3675](https://github.com/pypeclub/OpenPype/pull/3675) - Maya: Disable rendering of all lights for render instances submitted through Deadline. [\#3661](https://github.com/pypeclub/OpenPype/pull/3661) -- General: Optimized OCIO configs [\#3650](https://github.com/pypeclub/OpenPype/pull/3650) **🐛 Bug fixes** - General: Switch from hero version to versioned works [\#3691](https://github.com/pypeclub/OpenPype/pull/3691) - General: Fix finding of last version [\#3656](https://github.com/pypeclub/OpenPype/pull/3656) -- General: Extract Review can scale with pixel aspect ratio [\#3644](https://github.com/pypeclub/OpenPype/pull/3644) -- Maya: Refactor moved usage of CreateRender settings [\#3643](https://github.com/pypeclub/OpenPype/pull/3643) **🔀 Refactored code** - General: Use client projects getter [\#3673](https://github.com/pypeclub/OpenPype/pull/3673) -- Resolve: Match folder structure to other hosts [\#3653](https://github.com/pypeclub/OpenPype/pull/3653) -- Maya: Hosts as modules [\#3647](https://github.com/pypeclub/OpenPype/pull/3647) **Merged pull requests:** - Deadline: Global job pre load is not Pype 2 compatible [\#3666](https://github.com/pypeclub/OpenPype/pull/3666) -- Maya: Remove unused get current renderer logic [\#3645](https://github.com/pypeclub/OpenPype/pull/3645) ## [3.13.0](https://github.com/pypeclub/OpenPype/tree/3.13.0) (2022-08-09) diff --git a/openpype/version.py b/openpype/version.py index 26b03c37e5..c042ca2625 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.14.2-nightly.2" +__version__ = "3.14.2-nightly.3" From e1c17c71d30a38bcfda292393e425651c4c5a6d4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 7 Sep 2022 12:11:06 +0200 Subject: [PATCH 257/274] fix variable name --- openpype/pipeline/template_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/template_data.py b/openpype/pipeline/template_data.py index bab46a627d..627eba5c3d 100644 --- a/openpype/pipeline/template_data.py +++ b/openpype/pipeline/template_data.py @@ -53,7 +53,7 @@ def get_project_template_data(project_doc=None, project_name=None): project_name = project_doc["name"] if not project_doc: - project_code = get_project(project_name, fields=["data.code"]) + project_doc = get_project(project_name, fields=["data.code"]) project_code = project_doc.get("data", {}).get("code") return { From 509c209093d6fd971ec3631e662a8e34a31c5717 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 7 Sep 2022 12:40:34 +0200 Subject: [PATCH 258/274] fix status handling --- igniter/install_dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/igniter/install_dialog.py b/igniter/install_dialog.py index b09529f5c5..c7e9ef74c5 100644 --- a/igniter/install_dialog.py +++ b/igniter/install_dialog.py @@ -389,7 +389,7 @@ class InstallDialog(QtWidgets.QDialog): def _installation_finished(self): status = self._install_thread.result() - if status >= 0: + if status is not None and status >= 0: self._update_progress(100) QtWidgets.QApplication.processEvents() self.done(3) From d6ab41887a37e1bfb4e7f9a1f430daef93182faa Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 7 Sep 2022 13:41:30 +0200 Subject: [PATCH 259/274] added comment to code --- igniter/install_dialog.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/igniter/install_dialog.py b/igniter/install_dialog.py index c7e9ef74c5..65ddd58735 100644 --- a/igniter/install_dialog.py +++ b/igniter/install_dialog.py @@ -388,6 +388,9 @@ class InstallDialog(QtWidgets.QDialog): install_thread.start() def _installation_finished(self): + # TODO we should find out why status can be set to 'None'? + # - 'InstallThread.run' should handle all cases so not sure where + # that come from status = self._install_thread.result() if status is not None and status >= 0: self._update_progress(100) From 57aa1e6659ab9552c1980dbebcd8b64535469f39 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 7 Sep 2022 14:49:55 +0200 Subject: [PATCH 260/274] implementing not retimed working frame range and retimed handles switch --- openpype/hosts/flame/api/plugin.py | 10 ++++++ .../flame/plugins/create/create_shot_clip.py | 16 ++++++++++ .../publish/collect_timeline_instances.py | 4 +++ .../publish/extract_subset_resources.py | 32 +++++++++++++------ .../publish/collect_otio_frame_ranges.py | 6 ++++ 5 files changed, 58 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index efbabb6a55..145b1f0921 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -361,6 +361,8 @@ class PublishableClip: index_from_segment_default = False use_shot_name_default = False include_handles_default = False + retimed_handles_default = True + retimed_framerange_default = True def __init__(self, segment, **kwargs): self.rename_index = kwargs["rename_index"] @@ -496,6 +498,14 @@ class PublishableClip: "audio", {}).get("value") or False self.include_handles = self.ui_inputs.get( "includeHandles", {}).get("value") or self.include_handles_default + self.retimed_handles = ( + self.ui_inputs.get("retimedHandles", {}).get("value") + or self.retimed_handles_default + ) + self.retimed_framerange = ( + self.ui_inputs.get("retimedFramerange", {}).get("value") + or self.retimed_framerange_default + ) # build subset name from layer name if self.subset_name == "[ track name ]": diff --git a/openpype/hosts/flame/plugins/create/create_shot_clip.py b/openpype/hosts/flame/plugins/create/create_shot_clip.py index fa239ea420..b03a39a7ca 100644 --- a/openpype/hosts/flame/plugins/create/create_shot_clip.py +++ b/openpype/hosts/flame/plugins/create/create_shot_clip.py @@ -276,6 +276,22 @@ class CreateShotClip(opfapi.Creator): "target": "tag", "toolTip": "By default handles are excluded", # noqa "order": 3 + }, + "retimedHandles": { + "value": True, + "type": "QCheckBox", + "label": "Retimed handles", + "target": "tag", + "toolTip": "By default handles are retimed.", # noqa + "order": 4 + }, + "retimedFramerange": { + "value": True, + "type": "QCheckBox", + "label": "Retimed framerange", + "target": "tag", + "toolTip": "By default framerange is retimed.", # noqa + "order": 5 } } } diff --git a/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py b/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py index 992db62c75..d6ff13b059 100644 --- a/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py +++ b/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py @@ -131,6 +131,10 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin): "fps": self.fps, "workfileFrameStart": workfile_start, "sourceFirstFrame": int(first_frame), + "notRetimedHandles": ( + not marker_data.get("retimedHandles")), + "notRetimedFramerange": ( + not marker_data.get("retimedFramerange")), "path": file_path, "flameAddTasks": self.add_tasks, "tasks": { diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index 3e1e8db986..1af6b00654 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -90,26 +90,38 @@ class ExtractSubsetResources(openpype.api.Extractor): handle_end = instance.data["handleEnd"] handles = max(handle_start, handle_end) include_handles = instance.data.get("includeHandles") + retimed_handles = instance.data.get("retimedHandles") # get media source range with handles source_start_handles = instance.data["sourceStartH"] source_end_handles = instance.data["sourceEndH"] - # retime if needed + + # retime if needed if r_speed != 1.0: - source_start_handles = ( - instance.data["sourceStart"] - r_handle_start) - source_end_handles = ( - source_start_handles - + (r_source_dur - 1) - + r_handle_start - + r_handle_end - ) + if retimed_handles: + # handles are retimed + source_start_handles = ( + instance.data["sourceStart"] - r_handle_start) + source_end_handles = ( + source_start_handles + + (r_source_dur - 1) + + r_handle_start + + r_handle_end + ) + else: + # handles are not retimed + source_end_handles = ( + source_start_handles + + (r_source_dur - 1) + + handle_start + + handle_end + ) # get frame range with handles for representation range frame_start_handle = frame_start - handle_start repre_frame_start = frame_start_handle if include_handles: - if r_speed == 1.0: + if r_speed == 1.0 or not retimed_handles: frame_start_handle = frame_start else: frame_start_handle = ( diff --git a/openpype/plugins/publish/collect_otio_frame_ranges.py b/openpype/plugins/publish/collect_otio_frame_ranges.py index 40e89e29bc..40a3fa6978 100644 --- a/openpype/plugins/publish/collect_otio_frame_ranges.py +++ b/openpype/plugins/publish/collect_otio_frame_ranges.py @@ -29,6 +29,7 @@ class CollectOtioFrameRanges(pyblish.api.InstancePlugin): # get basic variables otio_clip = instance.data["otioClip"] workfile_start = instance.data["workfileFrameStart"] + not_retime_framerange = instance.data.get("notRetimedFramerange") # get ranges otio_tl_range = otio_clip.range_in_parent() @@ -54,6 +55,11 @@ class CollectOtioFrameRanges(pyblish.api.InstancePlugin): frame_end = frame_start + otio.opentime.to_frames( otio_tl_range.duration, otio_tl_range.duration.rate) - 1 + # in case of retimed clip and frame range should not be retimed + if not_retime_framerange: + frame_end = frame_start + otio.opentime.to_frames( + otio_src_range.duration, otio_src_range.duration.rate) - 1 + data = { "frameStart": frame_start, "frameEnd": frame_end, From d2b3c80fb93acf26f9d61a6164dadf2941cd7930 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 7 Sep 2022 16:02:38 +0200 Subject: [PATCH 261/274] improving variable name --- openpype/plugins/publish/collect_otio_frame_ranges.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/collect_otio_frame_ranges.py b/openpype/plugins/publish/collect_otio_frame_ranges.py index 40a3fa6978..cfb0318950 100644 --- a/openpype/plugins/publish/collect_otio_frame_ranges.py +++ b/openpype/plugins/publish/collect_otio_frame_ranges.py @@ -29,7 +29,7 @@ class CollectOtioFrameRanges(pyblish.api.InstancePlugin): # get basic variables otio_clip = instance.data["otioClip"] workfile_start = instance.data["workfileFrameStart"] - not_retime_framerange = instance.data.get("notRetimedFramerange") + workfile_source_duration = instance.data.get("notRetimedFramerange") # get ranges otio_tl_range = otio_clip.range_in_parent() @@ -56,7 +56,7 @@ class CollectOtioFrameRanges(pyblish.api.InstancePlugin): otio_tl_range.duration, otio_tl_range.duration.rate) - 1 # in case of retimed clip and frame range should not be retimed - if not_retime_framerange: + if workfile_source_duration: frame_end = frame_start + otio.opentime.to_frames( otio_src_range.duration, otio_src_range.duration.rate) - 1 From 5627c8ec627e4157e45eaeeb5a58e12216979f7c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 8 Sep 2022 11:23:56 +0200 Subject: [PATCH 262/274] enabled pixmap scaling in tray --- openpype/tools/tray/pype_tray.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpype/tools/tray/pype_tray.py b/openpype/tools/tray/pype_tray.py index 85bc00ead6..c32a074fd1 100644 --- a/openpype/tools/tray/pype_tray.py +++ b/openpype/tools/tray/pype_tray.py @@ -778,6 +778,14 @@ def main(): if not app: app = QtWidgets.QApplication([]) + for attr_name in ( + "AA_EnableHighDpiScaling", + "AA_UseHighDpiPixmaps" + ): + attr = getattr(QtCore.Qt, attr_name, None) + if attr is not None: + app.setAttribute(attr) + starter = PypeTrayStarter(app) # TODO remove when pype.exe will have an icon From 4f3accee1ac84991543493a32768c1aa99672035 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 8 Sep 2022 11:34:33 +0200 Subject: [PATCH 263/274] change "hierarchy" key to "nodesHierarchy" in maya --- openpype/hosts/maya/plugins/publish/collect_assembly.py | 2 +- openpype/hosts/maya/plugins/publish/extract_assembly.py | 2 +- .../hosts/maya/plugins/publish/validate_assembly_transforms.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_assembly.py b/openpype/hosts/maya/plugins/publish/collect_assembly.py index 1a65bf1fde..2aef9ab908 100644 --- a/openpype/hosts/maya/plugins/publish/collect_assembly.py +++ b/openpype/hosts/maya/plugins/publish/collect_assembly.py @@ -70,7 +70,7 @@ class CollectAssembly(pyblish.api.InstancePlugin): data[representation_id].append(instance_data) instance.data["scenedata"] = dict(data) - instance.data["hierarchy"] = list(set(hierarchy_nodes)) + instance.data["nodesHierarchy"] = list(set(hierarchy_nodes)) def get_file_rule(self, rule): return mel.eval('workspace -query -fileRuleEntry "{}"'.format(rule)) diff --git a/openpype/hosts/maya/plugins/publish/extract_assembly.py b/openpype/hosts/maya/plugins/publish/extract_assembly.py index 482930b76e..120805894e 100644 --- a/openpype/hosts/maya/plugins/publish/extract_assembly.py +++ b/openpype/hosts/maya/plugins/publish/extract_assembly.py @@ -33,7 +33,7 @@ class ExtractAssembly(openpype.api.Extractor): json.dump(instance.data["scenedata"], filepath, ensure_ascii=False) self.log.info("Extracting point cache ..") - cmds.select(instance.data["hierarchy"]) + cmds.select(instance.data["nodesHierarchy"]) # Run basic alembic exporter extract_alembic(file=hierarchy_path, diff --git a/openpype/hosts/maya/plugins/publish/validate_assembly_transforms.py b/openpype/hosts/maya/plugins/publish/validate_assembly_transforms.py index f793846555..fb25b617be 100644 --- a/openpype/hosts/maya/plugins/publish/validate_assembly_transforms.py +++ b/openpype/hosts/maya/plugins/publish/validate_assembly_transforms.py @@ -48,7 +48,7 @@ class ValidateAssemblyModelTransforms(pyblish.api.InstancePlugin): from openpype.hosts.maya.api import lib # Get all transforms in the loaded containers - container_roots = cmds.listRelatives(instance.data["hierarchy"], + container_roots = cmds.listRelatives(instance.data["nodesHierarchy"], children=True, type="transform", fullPath=True) From 01d3b0b0b0e2ed72b4ff509d4e0685f44086ef85 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 8 Sep 2022 13:30:27 +0200 Subject: [PATCH 264/274] add logs about missing attributes --- openpype/tools/tray/pype_tray.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/openpype/tools/tray/pype_tray.py b/openpype/tools/tray/pype_tray.py index c32a074fd1..348573a191 100644 --- a/openpype/tools/tray/pype_tray.py +++ b/openpype/tools/tray/pype_tray.py @@ -9,11 +9,11 @@ import platform from Qt import QtCore, QtGui, QtWidgets import openpype.version -from openpype.api import ( - resources, - get_system_settings +from openpype import resources, style +from openpype.lib import ( + get_openpype_execute_args, + Logger, ) -from openpype.lib import get_openpype_execute_args, Logger from openpype.lib.openpype_version import ( op_version_control_available, get_expected_version, @@ -25,8 +25,8 @@ from openpype.lib.openpype_version import ( get_openpype_version, ) from openpype.modules import TrayModulesManager -from openpype import style from openpype.settings import ( + get_system_settings, SystemSettings, ProjectSettings, DefaultsNotDefined @@ -774,6 +774,7 @@ class PypeTrayStarter(QtCore.QObject): def main(): + log = Logger.get_logger(__name__) app = QtWidgets.QApplication.instance() if not app: app = QtWidgets.QApplication([]) @@ -783,7 +784,12 @@ def main(): "AA_UseHighDpiPixmaps" ): attr = getattr(QtCore.Qt, attr_name, None) - if attr is not None: + if attr is None: + log.debug(( + "Missing QtCore.Qt attribute \"{}\"." + " UI quality may be affected." + ).format(attr_name)) + else: app.setAttribute(attr) starter = PypeTrayStarter(app) From d7a768f718944c9322ae30e9167ea64b655be5e5 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 8 Sep 2022 15:51:24 +0200 Subject: [PATCH 265/274] turn plugin off by default --- openpype/settings/defaults/project_settings/global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 9258343440..99a2e16a7c 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -4,7 +4,7 @@ "follow_workfile_version": false }, "CollectAudio": { - "enabled": true, + "enabled": false, "audio_subset_name": "audioMain" }, "CollectSceneVersion": { From 49dff63f08207eea0218cf37e4824795d08e3895 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 8 Sep 2022 17:50:55 +0200 Subject: [PATCH 266/274] Fix detection of workfile instance --- openpype/modules/deadline/abstract_submit_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index f698b7688e..512ff800ee 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -519,7 +519,7 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin): instance = self._instance workfile_instance = self._get_workfile_instance(instance.context) - if not workfile_instance: + if workfile_instance is None: return # determine published path from Anatomy. From 3943d74f3ea3b72d08f2d2e114a30b4a5e00a515 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 Sep 2022 11:55:05 +0200 Subject: [PATCH 267/274] flame: adding batch action hook --- openpype/hosts/flame/api/__init__.py | 4 +- openpype/hosts/flame/api/menu.py | 50 +++++++++++++++++++ .../hosts/flame/startup/openpype_in_flame.py | 13 +++++ 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/flame/api/__init__.py b/openpype/hosts/flame/api/__init__.py index 76c1c93379..7da91d41e4 100644 --- a/openpype/hosts/flame/api/__init__.py +++ b/openpype/hosts/flame/api/__init__.py @@ -51,7 +51,8 @@ from .pipeline import ( ) from .menu import ( FlameMenuProjectConnect, - FlameMenuTimeline + FlameMenuTimeline, + FlameMenuBatch ) from .plugin import ( Creator, @@ -131,6 +132,7 @@ __all__ = [ # menu "FlameMenuProjectConnect", "FlameMenuTimeline", + "FlameMenuBatch", # plugin "Creator", diff --git a/openpype/hosts/flame/api/menu.py b/openpype/hosts/flame/api/menu.py index 7f1a6a24e2..a822059930 100644 --- a/openpype/hosts/flame/api/menu.py +++ b/openpype/hosts/flame/api/menu.py @@ -201,3 +201,53 @@ class FlameMenuTimeline(_FlameMenuApp): if self.flame: self.flame.execute_shortcut('Rescan Python Hooks') self.log.info('Rescan Python Hooks') + + +class FlameMenuBatch(_FlameMenuApp): + + # flameMenuProjectconnect app takes care of the preferences dialog as well + + def __init__(self, framework): + _FlameMenuApp.__init__(self, framework) + + def __getattr__(self, name): + def method(*args, **kwargs): + project = self.dynamic_menu_data.get(name) + if project: + self.link_project(project) + return method + + def build_menu(self): + if not self.flame: + return [] + + menu = deepcopy(self.menu) + + menu['actions'].append({ + "name": "Load...", + "execute": lambda x: self.tools_helper.show_loader() + }) + menu['actions'].append({ + "name": "Manage...", + "execute": lambda x: self.tools_helper.show_scene_inventory() + }) + menu['actions'].append({ + "name": "Library...", + "execute": lambda x: self.tools_helper.show_library_loader() + }) + return menu + + def refresh(self, *args, **kwargs): + self.rescan() + + def rescan(self, *args, **kwargs): + if not self.flame: + try: + import flame + self.flame = flame + except ImportError: + self.flame = None + + if self.flame: + self.flame.execute_shortcut('Rescan Python Hooks') + self.log.info('Rescan Python Hooks') diff --git a/openpype/hosts/flame/startup/openpype_in_flame.py b/openpype/hosts/flame/startup/openpype_in_flame.py index f2ac23b19e..60f6612b7f 100644 --- a/openpype/hosts/flame/startup/openpype_in_flame.py +++ b/openpype/hosts/flame/startup/openpype_in_flame.py @@ -73,6 +73,8 @@ def load_apps(): opfapi.FlameMenuProjectConnect(opfapi.CTX.app_framework)) opfapi.CTX.flame_apps.append( opfapi.FlameMenuTimeline(opfapi.CTX.app_framework)) + opfapi.CTX.flame_apps.append( + opfapi.FlameMenuBatch(opfapi.CTX.app_framework)) opfapi.CTX.app_framework.log.info("Apps are loaded") @@ -191,3 +193,14 @@ def get_timeline_custom_ui_actions(): openpype_install() return _build_app_menu("FlameMenuTimeline") + +def get_batch_custom_ui_actions(): + """Hook to create submenu in batch + + Returns: + list: menu object + """ + # install openpype and the host + openpype_install() + + return _build_app_menu("FlameMenuBatch") \ No newline at end of file From c622eb7a59986ebe003205d66cd2ae101e1b23eb Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 Sep 2022 13:54:33 +0200 Subject: [PATCH 268/274] flame: add ui to project media panel --- openpype/hosts/flame/api/__init__.py | 4 ++-- openpype/hosts/flame/api/menu.py | 2 +- openpype/hosts/flame/startup/openpype_in_flame.py | 15 ++++++++++++++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/flame/api/__init__.py b/openpype/hosts/flame/api/__init__.py index 7da91d41e4..c00ee958b6 100644 --- a/openpype/hosts/flame/api/__init__.py +++ b/openpype/hosts/flame/api/__init__.py @@ -52,7 +52,7 @@ from .pipeline import ( from .menu import ( FlameMenuProjectConnect, FlameMenuTimeline, - FlameMenuBatch + FlameMenuUniversal ) from .plugin import ( Creator, @@ -132,7 +132,7 @@ __all__ = [ # menu "FlameMenuProjectConnect", "FlameMenuTimeline", - "FlameMenuBatch", + "FlameMenuUniversal", # plugin "Creator", diff --git a/openpype/hosts/flame/api/menu.py b/openpype/hosts/flame/api/menu.py index a822059930..f72a352bba 100644 --- a/openpype/hosts/flame/api/menu.py +++ b/openpype/hosts/flame/api/menu.py @@ -203,7 +203,7 @@ class FlameMenuTimeline(_FlameMenuApp): self.log.info('Rescan Python Hooks') -class FlameMenuBatch(_FlameMenuApp): +class FlameMenuUniversal(_FlameMenuApp): # flameMenuProjectconnect app takes care of the preferences dialog as well diff --git a/openpype/hosts/flame/startup/openpype_in_flame.py b/openpype/hosts/flame/startup/openpype_in_flame.py index 60f6612b7f..9fdc30db5d 100644 --- a/openpype/hosts/flame/startup/openpype_in_flame.py +++ b/openpype/hosts/flame/startup/openpype_in_flame.py @@ -194,6 +194,7 @@ def get_timeline_custom_ui_actions(): return _build_app_menu("FlameMenuTimeline") + def get_batch_custom_ui_actions(): """Hook to create submenu in batch @@ -203,4 +204,16 @@ def get_batch_custom_ui_actions(): # install openpype and the host openpype_install() - return _build_app_menu("FlameMenuBatch") \ No newline at end of file + return _build_app_menu("FlameMenuUniversal") + + +def get_media_panel_custom_ui_actions(): + """Hook to create submenu in desktop + + Returns: + list: menu object + """ + # install openpype and the host + openpype_install() + + return _build_app_menu("FlameMenuUniversal") From 7b78e09eaec1c7e243782c13079ff70ca6c06d23 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 Sep 2022 13:59:15 +0200 Subject: [PATCH 269/274] fixing name of class --- openpype/hosts/flame/startup/openpype_in_flame.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/flame/startup/openpype_in_flame.py b/openpype/hosts/flame/startup/openpype_in_flame.py index 9fdc30db5d..d07aaa6b7d 100644 --- a/openpype/hosts/flame/startup/openpype_in_flame.py +++ b/openpype/hosts/flame/startup/openpype_in_flame.py @@ -74,7 +74,7 @@ def load_apps(): opfapi.CTX.flame_apps.append( opfapi.FlameMenuTimeline(opfapi.CTX.app_framework)) opfapi.CTX.flame_apps.append( - opfapi.FlameMenuBatch(opfapi.CTX.app_framework)) + opfapi.FlameMenuUniversal(opfapi.CTX.app_framework)) opfapi.CTX.app_framework.log.info("Apps are loaded") From 42f575fca94722a25ae462a5112a843799d9aad1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 14:23:36 +0200 Subject: [PATCH 270/274] actions are expected as list so each application in group is stored if force not open workfile is enabled --- openpype/tools/launcher/models.py | 46 +++++++++++++++++++------------ 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/openpype/tools/launcher/models.py b/openpype/tools/launcher/models.py index 6d40d21f96..6e3b531018 100644 --- a/openpype/tools/launcher/models.py +++ b/openpype/tools/launcher/models.py @@ -281,18 +281,25 @@ class ActionModel(QtGui.QStandardItemModel): if not action_item: return - action = action_item.data(ACTION_ROLE) - actual_data = self._prepare_compare_data(action) + actions = action_item.data(ACTION_ROLE) + if not isinstance(actions, list): + actions = [actions] + + action_actions_data = [ + self._prepare_compare_data(action) + for action in actions + ] stored = self.launcher_registry.get_item("force_not_open_workfile") - if is_checked: - stored.append(actual_data) - else: - final_values = [] - for config in stored: - if config != actual_data: - final_values.append(config) - stored = final_values + for actual_data in action_actions_data: + if is_checked: + stored.append(actual_data) + else: + final_values = [] + for config in stored: + if config != actual_data: + final_values.append(config) + stored = final_values self.launcher_registry.set_item("force_not_open_workfile", stored) self.launcher_registry._get_item.cache_clear() @@ -329,21 +336,24 @@ class ActionModel(QtGui.QStandardItemModel): item (QStandardItem) stored (list) of dict """ - action = item.data(ACTION_ROLE) - if not self.is_application_action(action): + + actions = item.data(ACTION_ROLE) + if not isinstance(actions, list): + actions = [actions] + + if not self.is_application_action(actions[0]): return False - actual_data = self._prepare_compare_data(action) + action_actions_data = [ + self._prepare_compare_data(action) + for action in actions + ] for config in stored: - if config == actual_data: + if config in action_actions_data: return True - return False def _prepare_compare_data(self, action): - if isinstance(action, list) and action: - action = action[0] - compare_data = {} if action and action.label: compare_data = { From 98c065cb8b3c8f75ae0479da1a3287ebbe0b22d6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 14:24:02 +0200 Subject: [PATCH 271/274] change "start_last_workfile" when triggered from group --- openpype/tools/launcher/widgets.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/openpype/tools/launcher/widgets.py b/openpype/tools/launcher/widgets.py index 62599664fe..774ceb659d 100644 --- a/openpype/tools/launcher/widgets.py +++ b/openpype/tools/launcher/widgets.py @@ -312,11 +312,12 @@ class ActionBar(QtWidgets.QWidget): is_group = index.data(GROUP_ROLE) is_variant_group = index.data(VARIANT_GROUP_ROLE) + force_not_open_workfile = index.data(FORCE_NOT_OPEN_WORKFILE_ROLE) if not is_group and not is_variant_group: action = index.data(ACTION_ROLE) # Change data of application action if issubclass(action, ApplicationAction): - if index.data(FORCE_NOT_OPEN_WORKFILE_ROLE): + if force_not_open_workfile: action.data["start_last_workfile"] = False else: action.data.pop("start_last_workfile", None) @@ -385,10 +386,18 @@ class ActionBar(QtWidgets.QWidget): menu.addMenu(sub_menu) result = menu.exec_(QtGui.QCursor.pos()) - if result: - action = actions_mapping[result] - self._start_animation(index) - self.action_clicked.emit(action) + if not result: + return + + action = actions_mapping[result] + if issubclass(action, ApplicationAction): + if force_not_open_workfile: + action.data["start_last_workfile"] = False + else: + action.data.pop("start_last_workfile", None) + + self._start_animation(index) + self.action_clicked.emit(action) class ActionHistory(QtWidgets.QPushButton): From 113210a781ed5affd6e92f72d075a9522a07c0f0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 17:21:53 +0200 Subject: [PATCH 272/274] firx access to 'Pattern' attribute --- openpype/client/entities.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index a9112ac581..43afccf2f1 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -14,6 +14,8 @@ from bson.objectid import ObjectId from .mongo import get_project_database, get_project_connection +PatternType = type(re.compile("")) + def _prepare_fields(fields, required_fields=None): if not fields: @@ -1054,11 +1056,11 @@ def _regex_filters(filters): for key, value in filters.items(): regexes = [] a_values = [] - if isinstance(value, re.Pattern): + if isinstance(value, PatternType): regexes.append(value) elif isinstance(value, (list, tuple, set)): for item in value: - if isinstance(item, re.Pattern): + if isinstance(item, PatternType): regexes.append(item) else: a_values.append(item) @@ -1194,7 +1196,7 @@ def get_representations( as filter. Filter ignored if 'None' is passed. version_ids (Iterable[str]): Subset ids used as parent filter. Filter ignored if 'None' is passed. - context_filters (Dict[str, List[str, re.Pattern]]): Filter by + context_filters (Dict[str, List[str, PatternType]]): Filter by representation context fields. names_by_version_ids (dict[ObjectId, list[str]]): Complex filtering using version ids and list of names under the version. @@ -1240,7 +1242,7 @@ def get_archived_representations( as filter. Filter ignored if 'None' is passed. version_ids (Iterable[str]): Subset ids used as parent filter. Filter ignored if 'None' is passed. - context_filters (Dict[str, List[str, re.Pattern]]): Filter by + context_filters (Dict[str, List[str, PatternType]]): Filter by representation context fields. names_by_version_ids (dict[ObjectId, List[str]]): Complex filtering using version ids and list of names under the version. From c5ae7e5d6cb704794e444addf93bb9e903795dc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Fri, 9 Sep 2022 17:45:44 +0200 Subject: [PATCH 273/274] Update openpype/hosts/flame/plugins/publish/extract_subset_resources.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../hosts/flame/plugins/publish/extract_subset_resources.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index 1af6b00654..1d42330e23 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -96,7 +96,7 @@ class ExtractSubsetResources(openpype.api.Extractor): source_start_handles = instance.data["sourceStartH"] source_end_handles = instance.data["sourceEndH"] - # retime if needed + # retime if needed if r_speed != 1.0: if retimed_handles: # handles are retimed From 87cb5e25b82d3ea352b17794e231924bdbb097ed Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sat, 10 Sep 2022 04:17:04 +0000 Subject: [PATCH 274/274] [Automated] Bump version --- CHANGELOG.md | 45 ++++++++++++++++++--------------------------- openpype/version.py | 2 +- 2 files changed, 19 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6754f1e2e3..0ffb6a996b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,29 +1,36 @@ # Changelog -## [3.14.2-nightly.3](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.14.2-nightly.4](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.1...HEAD) **🆕 New features** - Nuke: Build workfile by template [\#3763](https://github.com/pypeclub/OpenPype/pull/3763) +- Houdini: Publishing workfiles [\#3697](https://github.com/pypeclub/OpenPype/pull/3697) +- Global: making collect audio plugin global [\#3679](https://github.com/pypeclub/OpenPype/pull/3679) **🚀 Enhancements** +- Flame: Adding Creator's retimed shot and handles switch [\#3826](https://github.com/pypeclub/OpenPype/pull/3826) +- Flame: OpenPype submenu to batch and media manager [\#3825](https://github.com/pypeclub/OpenPype/pull/3825) +- General: Better pixmap scaling [\#3809](https://github.com/pypeclub/OpenPype/pull/3809) - Photoshop: attempt to speed up ExtractImage [\#3793](https://github.com/pypeclub/OpenPype/pull/3793) - SyncServer: Added cli commands for sync server [\#3765](https://github.com/pypeclub/OpenPype/pull/3765) -- Blender: Publisher collect workfile representation [\#3670](https://github.com/pypeclub/OpenPype/pull/3670) -- Maya: move set render settings menu entry [\#3669](https://github.com/pypeclub/OpenPype/pull/3669) -- Scene Inventory: Maya add actions to select from or to scene [\#3659](https://github.com/pypeclub/OpenPype/pull/3659) +- Kitsu: Drop 'entities root' setting. [\#3739](https://github.com/pypeclub/OpenPype/pull/3739) **🐛 Bug fixes** +- General: Fix Pattern access in client code [\#3828](https://github.com/pypeclub/OpenPype/pull/3828) +- Launcher: Skip opening last work file works for groups [\#3822](https://github.com/pypeclub/OpenPype/pull/3822) +- Maya: Publishing data key change [\#3811](https://github.com/pypeclub/OpenPype/pull/3811) +- Igniter: Fix status handling when version is already installed [\#3804](https://github.com/pypeclub/OpenPype/pull/3804) - Resolve: Addon import is Python 2 compatible [\#3798](https://github.com/pypeclub/OpenPype/pull/3798) +- Hiero: retimed clip publishing is working [\#3792](https://github.com/pypeclub/OpenPype/pull/3792) - nuke: validate write node is not failing due wrong type [\#3780](https://github.com/pypeclub/OpenPype/pull/3780) - Fix - changed format of version string in pyproject.toml [\#3777](https://github.com/pypeclub/OpenPype/pull/3777) - Ftrack status fix typo prgoress -\> progress [\#3761](https://github.com/pypeclub/OpenPype/pull/3761) - Fix version resolution [\#3757](https://github.com/pypeclub/OpenPype/pull/3757) -- Maya: `containerise` dont skip empty values [\#3674](https://github.com/pypeclub/OpenPype/pull/3674) **🔀 Refactored code** @@ -33,17 +40,19 @@ - General: Remove unused teshost [\#3773](https://github.com/pypeclub/OpenPype/pull/3773) - General: Copied 'Extractor' plugin to publish pipeline [\#3771](https://github.com/pypeclub/OpenPype/pull/3771) - General: Move queries of asset and representation links [\#3770](https://github.com/pypeclub/OpenPype/pull/3770) +- General: Move create project folders to pipeline [\#3768](https://github.com/pypeclub/OpenPype/pull/3768) - General: Create project function moved to client code [\#3766](https://github.com/pypeclub/OpenPype/pull/3766) -- General: Move delivery logic to pipeline [\#3751](https://github.com/pypeclub/OpenPype/pull/3751) - General: Move hostdirname functionality into host [\#3749](https://github.com/pypeclub/OpenPype/pull/3749) - General: Move publish utils to pipeline [\#3745](https://github.com/pypeclub/OpenPype/pull/3745) - Houdini: Define houdini as addon [\#3735](https://github.com/pypeclub/OpenPype/pull/3735) +- Fusion: Defined fusion as addon [\#3733](https://github.com/pypeclub/OpenPype/pull/3733) - Flame: Defined flame as addon [\#3732](https://github.com/pypeclub/OpenPype/pull/3732) - Resolve: Define resolve as addon [\#3727](https://github.com/pypeclub/OpenPype/pull/3727) **Merged pull requests:** - Standalone Publisher: Ignore empty labels, then still use name like other asset models [\#3779](https://github.com/pypeclub/OpenPype/pull/3779) +- Kitsu - sync\_all\_project - add list ignore\_projects [\#3776](https://github.com/pypeclub/OpenPype/pull/3776) ## [3.14.1](https://github.com/pypeclub/OpenPype/tree/3.14.1) (2022-08-30) @@ -52,23 +61,16 @@ ### 📖 Documentation - Documentation: Few updates [\#3698](https://github.com/pypeclub/OpenPype/pull/3698) -- Documentation: Settings development [\#3660](https://github.com/pypeclub/OpenPype/pull/3660) - -**🆕 New features** - -- Webpublisher:change create flatten image into tri state [\#3678](https://github.com/pypeclub/OpenPype/pull/3678) -- Blender: validators code correction with settings and defaults [\#3662](https://github.com/pypeclub/OpenPype/pull/3662) **🚀 Enhancements** - General: Thumbnail can use project roots [\#3750](https://github.com/pypeclub/OpenPype/pull/3750) +- git: update gitignore [\#3722](https://github.com/pypeclub/OpenPype/pull/3722) - Settings: Remove settings lock on tray exit [\#3720](https://github.com/pypeclub/OpenPype/pull/3720) - General: Added helper getters to modules manager [\#3712](https://github.com/pypeclub/OpenPype/pull/3712) - Unreal: Define unreal as module and use host class [\#3701](https://github.com/pypeclub/OpenPype/pull/3701) - Settings: Lock settings UI session [\#3700](https://github.com/pypeclub/OpenPype/pull/3700) - General: Benevolent context label collector [\#3686](https://github.com/pypeclub/OpenPype/pull/3686) -- Ftrack: Store ftrack entities on hierarchy integration to instances [\#3677](https://github.com/pypeclub/OpenPype/pull/3677) -- Blender: ops refresh manager after process events [\#3663](https://github.com/pypeclub/OpenPype/pull/3663) **🐛 Bug fixes** @@ -82,11 +84,11 @@ - Settings: Fix project overrides save [\#3708](https://github.com/pypeclub/OpenPype/pull/3708) - Workfiles tool: Fix published workfile filtering [\#3704](https://github.com/pypeclub/OpenPype/pull/3704) - PS, AE: Provide default variant value for workfile subset [\#3703](https://github.com/pypeclub/OpenPype/pull/3703) -- Flame: retime is working on clip publishing [\#3684](https://github.com/pypeclub/OpenPype/pull/3684) - Webpublisher: added check for empty context [\#3682](https://github.com/pypeclub/OpenPype/pull/3682) **🔀 Refactored code** +- General: Move delivery logic to pipeline [\#3751](https://github.com/pypeclub/OpenPype/pull/3751) - General: Host addons cleanup [\#3744](https://github.com/pypeclub/OpenPype/pull/3744) - Webpublisher: Webpublisher is used as addon [\#3740](https://github.com/pypeclub/OpenPype/pull/3740) - Photoshop: Defined photoshop as addon [\#3736](https://github.com/pypeclub/OpenPype/pull/3736) @@ -110,7 +112,6 @@ - Hiero: Define hiero as module [\#3717](https://github.com/pypeclub/OpenPype/pull/3717) - Deadline: better logging for DL webservice failures [\#3694](https://github.com/pypeclub/OpenPype/pull/3694) -- Photoshop: resize saved images in ExtractReview for ffmpeg [\#3676](https://github.com/pypeclub/OpenPype/pull/3676) ## [3.14.0](https://github.com/pypeclub/OpenPype/tree/3.14.0) (2022-08-18) @@ -120,21 +121,11 @@ - Ftrack: Addiotional component metadata [\#3685](https://github.com/pypeclub/OpenPype/pull/3685) - Ftrack: Set task status on farm publishing [\#3680](https://github.com/pypeclub/OpenPype/pull/3680) -- Ftrack: Set task status on task creation in integrate hierarchy [\#3675](https://github.com/pypeclub/OpenPype/pull/3675) -- Maya: Disable rendering of all lights for render instances submitted through Deadline. [\#3661](https://github.com/pypeclub/OpenPype/pull/3661) **🐛 Bug fixes** - General: Switch from hero version to versioned works [\#3691](https://github.com/pypeclub/OpenPype/pull/3691) -- General: Fix finding of last version [\#3656](https://github.com/pypeclub/OpenPype/pull/3656) - -**🔀 Refactored code** - -- General: Use client projects getter [\#3673](https://github.com/pypeclub/OpenPype/pull/3673) - -**Merged pull requests:** - -- Deadline: Global job pre load is not Pype 2 compatible [\#3666](https://github.com/pypeclub/OpenPype/pull/3666) +- Flame: retime is working on clip publishing [\#3684](https://github.com/pypeclub/OpenPype/pull/3684) ## [3.13.0](https://github.com/pypeclub/OpenPype/tree/3.13.0) (2022-08-09) diff --git a/openpype/version.py b/openpype/version.py index c042ca2625..142bd51a30 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.14.2-nightly.3" +__version__ = "3.14.2-nightly.4"