From 6d3d52c05c630b9f559ff9a86f0e8cc574007fc7 Mon Sep 17 00:00:00 2001 From: Kaa Maurice Date: Thu, 11 Aug 2022 14:46:43 +0200 Subject: [PATCH 01/96] 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 02/96] 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 03/96] 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 04/96] 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 cf0ac3f8b56c5d0f63ab6fa00966fcfe6b76ee08 Mon Sep 17 00:00:00 2001 From: Kaa Maurice Date: Thu, 11 Aug 2022 17:10:02 +0200 Subject: [PATCH 05/96] blender ops refresh manager after process events --- openpype/hosts/blender/api/lib.py | 2 +- openpype/hosts/blender/api/ops.py | 17 ++++++++++++----- .../hosts/blender/blender_addon/startup/init.py | 8 +++++++- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/blender/api/lib.py b/openpype/hosts/blender/api/lib.py index 20098c0fe8..9cd1ace821 100644 --- a/openpype/hosts/blender/api/lib.py +++ b/openpype/hosts/blender/api/lib.py @@ -234,7 +234,7 @@ def lsattrs(attrs: Dict) -> List: def read(node: bpy.types.bpy_struct_meta_idprop): """Return user-defined attributes from `node`""" - data = dict(node.get(pipeline.AVALON_PROPERTY)) + data = dict(node.get(pipeline.AVALON_PROPERTY, {})) # Ignore hidden/internal data data = { diff --git a/openpype/hosts/blender/api/ops.py b/openpype/hosts/blender/api/ops.py index 4f8410da74..e0e09277df 100644 --- a/openpype/hosts/blender/api/ops.py +++ b/openpype/hosts/blender/api/ops.py @@ -26,7 +26,7 @@ PREVIEW_COLLECTIONS: Dict = dict() # This seems like a good value to keep the Qt app responsive and doesn't slow # down Blender. At least on macOS I the interace of Blender gets very laggy if # you make it smaller. -TIMER_INTERVAL: float = 0.01 +TIMER_INTERVAL: float = 0.01 if platform.system() == "Windows" else 0.1 class BlenderApplication(QtWidgets.QApplication): @@ -164,6 +164,12 @@ def _process_app_events() -> Optional[float]: dialog.setDetailedText(detail) dialog.exec_() + # Refresh Manager + if GlobalClass.app: + manager = GlobalClass.app.get_window("WM_OT_avalon_manager") + if manager: + manager.refresh() + if not GlobalClass.is_windows: if OpenFileCacher.opening_file: return TIMER_INTERVAL @@ -192,10 +198,11 @@ class LaunchQtApp(bpy.types.Operator): self._app = BlenderApplication.get_app() GlobalClass.app = self._app - bpy.app.timers.register( - _process_app_events, - persistent=True - ) + if not bpy.app.timers.is_registered(_process_app_events): + bpy.app.timers.register( + _process_app_events, + persistent=True + ) def execute(self, context): """Execute the operator. diff --git a/openpype/hosts/blender/blender_addon/startup/init.py b/openpype/hosts/blender/blender_addon/startup/init.py index 13a4b8a7a1..8dbff8a91d 100644 --- a/openpype/hosts/blender/blender_addon/startup/init.py +++ b/openpype/hosts/blender/blender_addon/startup/init.py @@ -1,4 +1,10 @@ from openpype.pipeline import install_host from openpype.hosts.blender import api -install_host(api) + +def register(): + install_host(api) + + +def unregister(): + pass From b1f29676227726f4367c4f6aa4de9defd305d41e Mon Sep 17 00:00:00 2001 From: Kaa Maurice Date: Mon, 15 Aug 2022 11:00:10 +0200 Subject: [PATCH 06/96] 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 07/96] 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 08/96] 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 46726e5afedc88ef22b31f515e70b08308643acd Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 16 Aug 2022 14:31:18 +0200 Subject: [PATCH 09/96] OP-3713 - changed type to tri-state Customer wants to have more granularity, they want to create flatten 'image', but not separate 'image' per layer. --- .../settings/defaults/project_settings/photoshop.json | 2 +- .../projects_schema/schema_project_photoshop.json | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/openpype/settings/defaults/project_settings/photoshop.json b/openpype/settings/defaults/project_settings/photoshop.json index d9b7a8083f..b08e73f1ee 100644 --- a/openpype/settings/defaults/project_settings/photoshop.json +++ b/openpype/settings/defaults/project_settings/photoshop.json @@ -8,7 +8,7 @@ }, "publish": { "CollectColorCodedInstances": { - "create_flatten_image": false, + "create_flatten_image": "no", "flatten_subset_template": "", "color_code_mapping": [] }, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json index badf94229b..6935ec8e5e 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json @@ -45,9 +45,15 @@ "label": "Set color for publishable layers, set its resulting family and template for subset name. \nCan create flatten image from published instances.(Applicable only for remote publishing!)" }, { - "type": "boolean", "key": "create_flatten_image", - "label": "Create flatten image" + "label": "Create flatten image", + "type": "enum", + "multiselection": false, + "enum_items": [ + { "yes": "Yes" }, + { "no": "No" }, + { "only": "Only flatten" } + ] }, { "type": "text", From 9bfc1447b7945cd11d11414df3ffad42f6014292 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 16 Aug 2022 14:32:47 +0200 Subject: [PATCH 10/96] OP-3713 - implement tri-state logic for create_flatten_image Customer wants to have more granularity, they want to create flatten 'image', but not separate 'image' per layer. --- .../publish/collect_color_coded_instances.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/photoshop/plugins/publish/collect_color_coded_instances.py b/openpype/hosts/photoshop/plugins/publish/collect_color_coded_instances.py index 71bd2cd854..9adc16d0fd 100644 --- a/openpype/hosts/photoshop/plugins/publish/collect_color_coded_instances.py +++ b/openpype/hosts/photoshop/plugins/publish/collect_color_coded_instances.py @@ -32,7 +32,7 @@ class CollectColorCodedInstances(pyblish.api.ContextPlugin): # TODO check if could be set globally, probably doesn't make sense when # flattened template cannot subset_template_name = "" - create_flatten_image = False + create_flatten_image = "no" # probably not possible to configure this globally flatten_subset_template = "" @@ -98,13 +98,16 @@ class CollectColorCodedInstances(pyblish.api.ContextPlugin): "Subset {} already created, skipping.".format(subset)) continue - instance = self._create_instance(context, layer, resolved_family, - asset_name, subset, task_name) + if self.create_flatten_image != "only": + instance = self._create_instance(context, layer, + resolved_family, + asset_name, subset, task_name) + created_instances.append(instance) + existing_subset_names.append(subset) publishable_layers.append(layer) - created_instances.append(instance) - if self.create_flatten_image and publishable_layers: + if self.create_flatten_image != "no" and publishable_layers: self.log.debug("create_flatten_image") if not self.flatten_subset_template: self.log.warning("No template for flatten image") @@ -116,7 +119,7 @@ class CollectColorCodedInstances(pyblish.api.ContextPlugin): first_layer = publishable_layers[0] # dummy layer first_layer.name = subset - family = created_instances[0].data["family"] # inherit family + family = resolved_family # inherit family instance = self._create_instance(context, first_layer, family, asset_name, subset, task_name) 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 11/96] :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 141b275fc614aa1456c97bbe16706497524cb0f0 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 16 Aug 2022 17:02:37 +0200 Subject: [PATCH 12/96] OP-3713 - fix missing family Resulted in failure in integrate --- .../plugins/publish/collect_color_coded_instances.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/photoshop/plugins/publish/collect_color_coded_instances.py b/openpype/hosts/photoshop/plugins/publish/collect_color_coded_instances.py index 9adc16d0fd..7d78140c5b 100644 --- a/openpype/hosts/photoshop/plugins/publish/collect_color_coded_instances.py +++ b/openpype/hosts/photoshop/plugins/publish/collect_color_coded_instances.py @@ -62,6 +62,7 @@ class CollectColorCodedInstances(pyblish.api.ContextPlugin): publishable_layers = [] created_instances = [] + family_from_settings = None for layer in layers: self.log.debug("Layer:: {}".format(layer)) if layer.parents: @@ -80,6 +81,9 @@ class CollectColorCodedInstances(pyblish.api.ContextPlugin): self.log.debug("!!! Not found family or template, skip") continue + if not family_from_settings: + family_from_settings = resolved_family + fill_pairs = { "variant": variant, "family": resolved_family, @@ -119,7 +123,7 @@ class CollectColorCodedInstances(pyblish.api.ContextPlugin): first_layer = publishable_layers[0] # dummy layer first_layer.name = subset - family = resolved_family # inherit family + family = family_from_settings # inherit family instance = self._create_instance(context, first_layer, family, asset_name, subset, task_name) From 45368a7ba83f0bdb59e9b4e591e6a1fee0736fff Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 16 Aug 2022 17:15:09 +0200 Subject: [PATCH 13/96] OP-3713 - added more documentation --- .../publish/collect_color_coded_instances.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/photoshop/plugins/publish/collect_color_coded_instances.py b/openpype/hosts/photoshop/plugins/publish/collect_color_coded_instances.py index 7d78140c5b..f93ba51574 100644 --- a/openpype/hosts/photoshop/plugins/publish/collect_color_coded_instances.py +++ b/openpype/hosts/photoshop/plugins/publish/collect_color_coded_instances.py @@ -9,14 +9,22 @@ from openpype.settings import get_project_settings class CollectColorCodedInstances(pyblish.api.ContextPlugin): - """Creates instances for configured color code of a layer. + """Creates instances for layers marked by configurable color. Used in remote publishing when artists marks publishable layers by color- - coding. + coding. Top level layers (group) must be marked by specific color to be + published as an instance of 'image' family. Can add group for all publishable layers to allow creation of flattened image. (Cannot contain special background layer as it cannot be grouped!) + Based on value `create_flatten_image` from Settings: + - "yes": create flattened 'image' subset of all publishable layers + create + 'image' subset per publishable layer + - "only": create ONLY flattened 'image' subset of all publishable layers + - "no": do not create flattened 'image' subset at all, + only separate subsets per marked layer. + Identifier: id (str): "pyblish.avalon.instance" """ @@ -33,7 +41,6 @@ class CollectColorCodedInstances(pyblish.api.ContextPlugin): # flattened template cannot subset_template_name = "" create_flatten_image = "no" - # probably not possible to configure this globally flatten_subset_template = "" def process(self, context): 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 14/96] :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 4dbca722bac4a918fa992a0860361460338ef970 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 17 Aug 2022 11:14:29 +0200 Subject: [PATCH 15/96] OP-3713 - refactored keys and labels --- .../plugins/publish/collect_color_coded_instances.py | 2 +- .../schemas/projects_schema/schema_project_photoshop.json | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/photoshop/plugins/publish/collect_color_coded_instances.py b/openpype/hosts/photoshop/plugins/publish/collect_color_coded_instances.py index f93ba51574..c157c932fd 100644 --- a/openpype/hosts/photoshop/plugins/publish/collect_color_coded_instances.py +++ b/openpype/hosts/photoshop/plugins/publish/collect_color_coded_instances.py @@ -109,7 +109,7 @@ class CollectColorCodedInstances(pyblish.api.ContextPlugin): "Subset {} already created, skipping.".format(subset)) continue - if self.create_flatten_image != "only": + if self.create_flatten_image != "flatten_only": instance = self._create_instance(context, layer, resolved_family, asset_name, subset, task_name) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json index 6935ec8e5e..db06147a51 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json @@ -50,9 +50,9 @@ "type": "enum", "multiselection": false, "enum_items": [ - { "yes": "Yes" }, - { "no": "No" }, - { "only": "Only flatten" } + { "flatten_with_images": "Flatten with images" }, + { "flatten_only": "Flatten only" }, + { "no": "No" } ] }, { From 5484c083230cbc3090db1b2dba6d582d21a1f849 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 17 Aug 2022 16:58:41 +0200 Subject: [PATCH 16/96] context label collector does not require 'currentFile' to be filled --- .../plugins/publish/collect_context_label.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/openpype/plugins/publish/collect_context_label.py b/openpype/plugins/publish/collect_context_label.py index 8cf71882aa..0ca19b28c1 100644 --- a/openpype/plugins/publish/collect_context_label.py +++ b/openpype/plugins/publish/collect_context_label.py @@ -1,5 +1,6 @@ """ -Requires: +Optional: + context -> hostName (str) context -> currentFile (str) Provides: context -> label (str) @@ -16,16 +17,16 @@ class CollectContextLabel(pyblish.api.ContextPlugin): label = "Context Label" def process(self, context): + host_name = context.data.get("hostName") + if not host_name: + host_name = pyblish.api.registered_hosts()[-1] + # Use host name as base for label + label = host_name.title() - # Get last registered host - host = pyblish.api.registered_hosts()[-1] - - # Get scene name from "currentFile" - path = context.data.get("currentFile") or "" - base = os.path.basename(path) + # Get scene name from "currentFile" and use basename as ending of label + path = context.data.get("currentFile") + if path: + label += " - {}".format(os.path.basename(path)) # Set label - label = "{host} - {scene}".format(host=host.title(), scene=base) - if host == "standalonepublisher": - label = host.title() context.data["label"] = label From e3c43d22159b78428c7ee7f75d89f793dc86dba7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 17 Aug 2022 16:59:12 +0200 Subject: [PATCH 17/96] it is possible to have set custom context label and in that case the plugin is skipped --- openpype/plugins/publish/collect_context_label.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/plugins/publish/collect_context_label.py b/openpype/plugins/publish/collect_context_label.py index 0ca19b28c1..1dec0b380b 100644 --- a/openpype/plugins/publish/collect_context_label.py +++ b/openpype/plugins/publish/collect_context_label.py @@ -17,6 +17,12 @@ class CollectContextLabel(pyblish.api.ContextPlugin): label = "Context Label" def process(self, context): + # Add ability to use custom context label + context_label = context.data.get("contextLabel") + if context_label: + context.data["label"] = context_label + return + host_name = context.data.get("hostName") if not host_name: host_name = pyblish.api.registered_hosts()[-1] From fe278d7135998a368db562eabeb5a636ce56e0ca Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 17 Aug 2022 18:05:42 +0200 Subject: [PATCH 18/96] Don't force to have label on all instances and in context. --- openpype/tools/pyblish_pype/control.py | 1 - openpype/tools/pyblish_pype/model.py | 19 ++++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/openpype/tools/pyblish_pype/control.py b/openpype/tools/pyblish_pype/control.py index f657936b79..05e53a989a 100644 --- a/openpype/tools/pyblish_pype/control.py +++ b/openpype/tools/pyblish_pype/control.py @@ -244,7 +244,6 @@ class Controller(QtCore.QObject): self.context.optional = False self.context.data["publish"] = True - self.context.data["label"] = "Context" self.context.data["name"] = "context" self.context.data["host"] = reversed(pyblish.api.registered_hosts()) diff --git a/openpype/tools/pyblish_pype/model.py b/openpype/tools/pyblish_pype/model.py index 31aa63677e..309126a884 100644 --- a/openpype/tools/pyblish_pype/model.py +++ b/openpype/tools/pyblish_pype/model.py @@ -596,11 +596,6 @@ class InstanceItem(QtGui.QStandardItem): instance._logs = [] instance.optional = getattr(instance, "optional", True) instance.data["publish"] = instance.data.get("publish", True) - instance.data["label"] = ( - instance.data.get("label") - or getattr(instance, "label", None) - or instance.data["name"] - ) family = self.data(Roles.FamiliesRole)[0] self.setData( @@ -616,9 +611,19 @@ class InstanceItem(QtGui.QStandardItem): def data(self, role=QtCore.Qt.DisplayRole): if role == QtCore.Qt.DisplayRole: + label = None if settings.UseLabel: - return self.instance.data["label"] - return self.instance.data["name"] + label = ( + self.instance.data.get("label") + or getattr(self.instance, "label", None) + ) + + if not label: + if self.is_context: + label = "Context" + else: + label = self.instance.data["name"] + return label if role == QtCore.Qt.DecorationRole: icon_name = self.instance.data.get("icon") or "file" From 55bf1bea91bda6c01a7d47f7c2437b80a4ccfc58 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 17 Aug 2022 18:06:05 +0200 Subject: [PATCH 19/96] change label access in report --- openpype/tools/publisher/publish_report_viewer/report_items.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/publisher/publish_report_viewer/report_items.py b/openpype/tools/publisher/publish_report_viewer/report_items.py index 8a01569723..206f999bac 100644 --- a/openpype/tools/publisher/publish_report_viewer/report_items.py +++ b/openpype/tools/publisher/publish_report_viewer/report_items.py @@ -79,7 +79,7 @@ class PublishReport: context_data = data["context"] context_data["name"] = "context" - context_data["label"] = context_data["label"] or "Context" + context_data["label"] = context_data.get("label") or "Context" logs = [] plugins_items_by_id = {} From 6761aa7d68016ad0e319ddae56c956d656c8bd44 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 17 Aug 2022 18:07:30 +0200 Subject: [PATCH 20/96] Change the check of "label" key --- openpype/plugins/publish/collect_context_label.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/collect_context_label.py b/openpype/plugins/publish/collect_context_label.py index 1dec0b380b..6cdeba8418 100644 --- a/openpype/plugins/publish/collect_context_label.py +++ b/openpype/plugins/publish/collect_context_label.py @@ -18,9 +18,11 @@ class CollectContextLabel(pyblish.api.ContextPlugin): def process(self, context): # Add ability to use custom context label - context_label = context.data.get("contextLabel") - if context_label: - context.data["label"] = context_label + label = context.data.get("label") + if label: + self.log.debug("Context label is already set to \"{}\"".format( + label + )) return host_name = context.data.get("hostName") @@ -36,3 +38,6 @@ class CollectContextLabel(pyblish.api.ContextPlugin): # Set label context.data["label"] = label + self.log.debug("Context label is changed to \"{}\"".format( + label + )) 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 21/96] :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 22/96] :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 23/96] :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 88a11e86f4a710444acb5d025f672834b9aa2404 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 23 Aug 2022 18:56:01 +0200 Subject: [PATCH 24/96] copied code to openpype/pipeline/create content --- openpype/pipeline/create/constants.py | 2 + openpype/pipeline/create/subset_name.py | 143 ++++++++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 openpype/pipeline/create/subset_name.py diff --git a/openpype/pipeline/create/constants.py b/openpype/pipeline/create/constants.py index bfbbccfd12..3af9651947 100644 --- a/openpype/pipeline/create/constants.py +++ b/openpype/pipeline/create/constants.py @@ -1,6 +1,8 @@ SUBSET_NAME_ALLOWED_SYMBOLS = "a-zA-Z0-9_." +DEFAULT_SUBSET_TEMPLATE = "{family}{Variant}" __all__ = ( "SUBSET_NAME_ALLOWED_SYMBOLS", + "DEFAULT_SUBSET_TEMPLATE", ) diff --git a/openpype/pipeline/create/subset_name.py b/openpype/pipeline/create/subset_name.py new file mode 100644 index 0000000000..d5dcf44c04 --- /dev/null +++ b/openpype/pipeline/create/subset_name.py @@ -0,0 +1,143 @@ +import os + +from openpype.client import get_asset_by_id +from openpype.settings import get_project_settings +from openpype.lib import filter_profiles, prepare_template_data +from openpype.pipeline import legacy_io + +from .constants import DEFAULT_SUBSET_TEMPLATE + + +class TaskNotSetError(KeyError): + def __init__(self, msg=None): + if not msg: + msg = "Creator's subset name template requires task name." + super(TaskNotSetError, self).__init__(msg) + + +def get_subset_name_with_asset_doc( + family, + variant, + task_name, + asset_doc, + project_name=None, + host_name=None, + default_template=None, + dynamic_data=None +): + """Calculate subset name based on passed context and OpenPype settings. + + Subst name templates are defined in `project_settings/global/tools/creator + /subset_name_profiles` where are profiles with host name, family, task name + and task type filters. If context does not match any profile then + `DEFAULT_SUBSET_TEMPLATE` is used as default template. + + That's main reason why so many arguments are required to calculate subset + name. + + Args: + family (str): Instance family. + variant (str): In most of cases it is user input during creation. + task_name (str): Task name on which context is instance created. + asset_doc (dict): Queried asset document with it's tasks in data. + Used to get task type. + project_name (str): Name of project on which is instance created. + Important for project settings that are loaded. + host_name (str): One of filtering criteria for template profile + filters. + default_template (str): Default template if any profile does not match + passed context. Constant 'DEFAULT_SUBSET_TEMPLATE' is used if + is not passed. + dynamic_data (dict): Dynamic data specific for a creator which creates + instance. + dbcon (AvalonMongoDB): Mongo connection to be able query asset document + if 'asset_doc' is not passed. + """ + + if not family: + return "" + + if not host_name: + host_name = os.environ["AVALON_APP"] + + # Use only last part of class family value split by dot (`.`) + family = family.rsplit(".", 1)[-1] + + if project_name is None: + project_name = legacy_io.Session["AVALON_PROJECT"] + + asset_tasks = asset_doc.get("data", {}).get("tasks") or {} + task_info = asset_tasks.get(task_name) or {} + task_type = task_info.get("type") + + # Get settings + tools_settings = get_project_settings(project_name)["global"]["tools"] + profiles = tools_settings["creator"]["subset_name_profiles"] + filtering_criteria = { + "families": family, + "hosts": host_name, + "tasks": task_name, + "task_types": task_type + } + + matching_profile = filter_profiles(profiles, filtering_criteria) + template = None + if matching_profile: + template = matching_profile["template"] + + # Make sure template is set (matching may have empty string) + if not template: + template = default_template or DEFAULT_SUBSET_TEMPLATE + + # Simple check of task name existence for template with {task} in + # - missing task should be possible only in Standalone publisher + if not task_name and "{task" in template.lower(): + raise TaskNotSetError() + + fill_pairs = { + "variant": variant, + "family": family, + "task": task_name + } + if dynamic_data: + # Dynamic data may override default values + for key, value in dynamic_data.items(): + fill_pairs[key] = value + + return template.format(**prepare_template_data(fill_pairs)) + + +def get_subset_name( + family, + variant, + task_name, + asset_id, + project_name=None, + host_name=None, + default_template=None, + dynamic_data=None, + dbcon=None +): + """Calculate subset name using OpenPype settings. + + This variant of function expects asset id as argument. + + This is legacy function should be replaced with + `get_subset_name_with_asset_doc` where asset document is expected. + """ + + if project_name is None: + project_name = dbcon.project_name + + asset_doc = get_asset_by_id(project_name, asset_id, fields=["data.tasks"]) + + return get_subset_name_with_asset_doc( + family, + variant, + task_name, + asset_doc or {}, + project_name, + host_name, + default_template, + dynamic_data + ) From 65b3a9a5a399bcd5fc633b96250623cf0f287292 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 23 Aug 2022 18:57:07 +0200 Subject: [PATCH 25/96] added ability to pass project settings --- openpype/pipeline/create/subset_name.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/openpype/pipeline/create/subset_name.py b/openpype/pipeline/create/subset_name.py index d5dcf44c04..b6028d6427 100644 --- a/openpype/pipeline/create/subset_name.py +++ b/openpype/pipeline/create/subset_name.py @@ -23,7 +23,8 @@ def get_subset_name_with_asset_doc( project_name=None, host_name=None, default_template=None, - dynamic_data=None + dynamic_data=None, + project_settings=None ): """Calculate subset name based on passed context and OpenPype settings. @@ -71,7 +72,9 @@ def get_subset_name_with_asset_doc( task_type = task_info.get("type") # Get settings - tools_settings = get_project_settings(project_name)["global"]["tools"] + if not project_settings: + project_settings = get_project_settings(project_name) + tools_settings = project_settings["global"]["tools"] profiles = tools_settings["creator"]["subset_name_profiles"] filtering_criteria = { "families": family, @@ -116,7 +119,7 @@ def get_subset_name( host_name=None, default_template=None, dynamic_data=None, - dbcon=None + project_settings=None ): """Calculate subset name using OpenPype settings. @@ -127,7 +130,7 @@ def get_subset_name( """ if project_name is None: - project_name = dbcon.project_name + project_name = legacy_io.Session["AVALON_PROJECT"] asset_doc = get_asset_by_id(project_name, asset_id, fields=["data.tasks"]) @@ -139,5 +142,6 @@ def get_subset_name( project_name, host_name, default_template, - dynamic_data + dynamic_data, + project_settings ) From daea5fd45e52770dd59057c9d836bf8dd23643b9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 23 Aug 2022 18:58:59 +0200 Subject: [PATCH 26/96] import content to create level --- openpype/pipeline/create/__init__.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/create/__init__.py b/openpype/pipeline/create/__init__.py index bd196ccfd1..4f3d2c03e5 100644 --- a/openpype/pipeline/create/__init__.py +++ b/openpype/pipeline/create/__init__.py @@ -1,6 +1,14 @@ from .constants import ( - SUBSET_NAME_ALLOWED_SYMBOLS + SUBSET_NAME_ALLOWED_SYMBOLS, + DEFAULT_SUBSET_TEMPLATE, ) + +from .subset_name import ( + TaskNotSetError, + get_subset_name, + get_subset_name_with_asset_doc, +) + from .creator_plugins import ( CreatorError, @@ -30,6 +38,11 @@ from .legacy_create import ( __all__ = ( "SUBSET_NAME_ALLOWED_SYMBOLS", + "DEFAULT_SUBSET_TEMPLATE", + + "TaskNotSetError", + "get_subset_name", + "get_subset_name_with_asset_doc", "CreatorError", From 476153e81c31e5b755159618368eccbfb1d68b1d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 23 Aug 2022 19:01:56 +0200 Subject: [PATCH 27/96] changed imports of task not set error --- .../traypublisher/plugins/create/create_movie_batch.py | 6 ++++-- openpype/tools/publisher/widgets/create_dialog.py | 4 ++-- openpype/tools/publisher/widgets/widgets.py | 6 ++++-- openpype/tools/standalonepublish/widgets/widget_family.py | 8 +++++--- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_movie_batch.py b/openpype/hosts/traypublisher/plugins/create/create_movie_batch.py index c5f0d6b75e..5d0fe4b177 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_movie_batch.py +++ b/openpype/hosts/traypublisher/plugins/create/create_movie_batch.py @@ -6,13 +6,15 @@ from openpype.client import get_assets, get_asset_by_name from openpype.lib import ( FileDef, BoolDef, - get_subset_name_with_asset_doc, - TaskNotSetError, ) from openpype.pipeline import ( CreatedInstance, CreatorError ) +from openpype.pipeline.create import ( + get_subset_name_with_asset_doc, + TaskNotSetError, +) from openpype.hosts.traypublisher.api.plugin import TrayPublishCreator diff --git a/openpype/tools/publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py index d4740b2493..173df7d5c8 100644 --- a/openpype/tools/publisher/widgets/create_dialog.py +++ b/openpype/tools/publisher/widgets/create_dialog.py @@ -11,10 +11,10 @@ except Exception: from Qt import QtWidgets, QtCore, QtGui from openpype.client import get_asset_by_name, get_subsets -from openpype.lib import TaskNotSetError from openpype.pipeline.create import ( CreatorError, - SUBSET_NAME_ALLOWED_SYMBOLS + SUBSET_NAME_ALLOWED_SYMBOLS, + TaskNotSetError, ) from openpype.tools.utils import ( ErrorMessageBox, diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index 5a5f8c4c37..aa7e3be687 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -6,7 +6,6 @@ import collections from Qt import QtWidgets, QtCore, QtGui import qtawesome -from openpype.lib import TaskNotSetError from openpype.widgets.attribute_defs import create_widget_for_attr_def from openpype.tools import resources from openpype.tools.flickcharm import FlickCharm @@ -17,7 +16,10 @@ from openpype.tools.utils import ( BaseClickableFrame, set_style_property, ) -from openpype.pipeline.create import SUBSET_NAME_ALLOWED_SYMBOLS +from openpype.pipeline.create import ( + SUBSET_NAME_ALLOWED_SYMBOLS, + TaskNotSetError, +) from .assets_widget import AssetsDialog from .tasks_widget import TasksModel from .icons import ( diff --git a/openpype/tools/standalonepublish/widgets/widget_family.py b/openpype/tools/standalonepublish/widgets/widget_family.py index 1736be84ab..eab66d75b3 100644 --- a/openpype/tools/standalonepublish/widgets/widget_family.py +++ b/openpype/tools/standalonepublish/widgets/widget_family.py @@ -8,10 +8,12 @@ from openpype.client import ( get_subsets, get_last_version_by_subset_id, ) -from openpype.api import get_project_settings +from openpype.settings import get_project_settings from openpype.pipeline import LegacyCreator -from openpype.lib import TaskNotSetError -from openpype.pipeline.create import SUBSET_NAME_ALLOWED_SYMBOLS +from openpype.pipeline.create import ( + SUBSET_NAME_ALLOWED_SYMBOLS, + TaskNotSetError, +) from . import HelpRole, FamilyRole, ExistsRole, PluginRole, PluginKeyRole from . import FamilyDescriptionWidget From 7e59a577a66f857ecd28920ed457915e14c1f0b3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 23 Aug 2022 19:12:28 +0200 Subject: [PATCH 28/96] use new import of 'get_subset_name_with_asset_doc' --- .../hosts/aftereffects/plugins/publish/collect_workfile.py | 2 +- .../hosts/flame/plugins/publish/collect_timeline_otio.py | 6 +++--- openpype/hosts/harmony/plugins/publish/collect_workfile.py | 4 ++-- openpype/hosts/photoshop/plugins/publish/collect_review.py | 2 +- .../hosts/photoshop/plugins/publish/collect_workfile.py | 2 +- .../plugins/publish/collect_bulk_mov_instances.py | 2 +- openpype/hosts/tvpaint/plugins/publish/collect_instances.py | 2 +- .../hosts/tvpaint/plugins/publish/collect_scene_render.py | 2 +- openpype/hosts/tvpaint/plugins/publish/collect_workfile.py | 2 +- .../webpublisher/plugins/publish/collect_published_files.py | 6 ++---- .../plugins/publish/collect_tvpaint_instances.py | 2 +- 11 files changed, 15 insertions(+), 17 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/publish/collect_workfile.py b/openpype/hosts/aftereffects/plugins/publish/collect_workfile.py index fef5448a4c..b1f40113a4 100644 --- a/openpype/hosts/aftereffects/plugins/publish/collect_workfile.py +++ b/openpype/hosts/aftereffects/plugins/publish/collect_workfile.py @@ -1,8 +1,8 @@ import os import pyblish.api -from openpype.lib import get_subset_name_with_asset_doc from openpype.pipeline import legacy_io +from openpype.pipeline.create import get_subset_name_with_asset_doc class CollectWorkfile(pyblish.api.ContextPlugin): diff --git a/openpype/hosts/flame/plugins/publish/collect_timeline_otio.py b/openpype/hosts/flame/plugins/publish/collect_timeline_otio.py index 0a9b0db334..c0c7eee7f2 100644 --- a/openpype/hosts/flame/plugins/publish/collect_timeline_otio.py +++ b/openpype/hosts/flame/plugins/publish/collect_timeline_otio.py @@ -1,9 +1,9 @@ import pyblish.api -import openpype.lib as oplib -from openpype.pipeline import legacy_io import openpype.hosts.flame.api as opfapi from openpype.hosts.flame.otio import flame_export +from openpype.pipeline import legacy_io +from openpype.pipeline.create import get_subset_name_with_asset_doc class CollecTimelineOTIO(pyblish.api.ContextPlugin): @@ -24,7 +24,7 @@ class CollecTimelineOTIO(pyblish.api.ContextPlugin): sequence = opfapi.get_current_sequence(opfapi.CTX.selection) # create subset name - subset_name = oplib.get_subset_name_with_asset_doc( + subset_name = get_subset_name_with_asset_doc( family, variant, task_name, diff --git a/openpype/hosts/harmony/plugins/publish/collect_workfile.py b/openpype/hosts/harmony/plugins/publish/collect_workfile.py index c0493315a4..924661d310 100644 --- a/openpype/hosts/harmony/plugins/publish/collect_workfile.py +++ b/openpype/hosts/harmony/plugins/publish/collect_workfile.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- """Collect current workfile from Harmony.""" -import pyblish.api import os +import pyblish.api -from openpype.lib import get_subset_name_with_asset_doc +from openpype.pipeline.create import get_subset_name_with_asset_doc class CollectWorkfile(pyblish.api.ContextPlugin): diff --git a/openpype/hosts/photoshop/plugins/publish/collect_review.py b/openpype/hosts/photoshop/plugins/publish/collect_review.py index 2ea5503f3f..ce475524a7 100644 --- a/openpype/hosts/photoshop/plugins/publish/collect_review.py +++ b/openpype/hosts/photoshop/plugins/publish/collect_review.py @@ -10,7 +10,7 @@ import os import pyblish.api -from openpype.lib import get_subset_name_with_asset_doc +from openpype.pipeline.create import get_subset_name_with_asset_doc class CollectReview(pyblish.api.ContextPlugin): diff --git a/openpype/hosts/photoshop/plugins/publish/collect_workfile.py b/openpype/hosts/photoshop/plugins/publish/collect_workfile.py index 9cf6d5227e..5e673bebb1 100644 --- a/openpype/hosts/photoshop/plugins/publish/collect_workfile.py +++ b/openpype/hosts/photoshop/plugins/publish/collect_workfile.py @@ -1,7 +1,7 @@ import os import pyblish.api -from openpype.lib import get_subset_name_with_asset_doc +from openpype.pipeline.create import get_subset_name_with_asset_doc class CollectWorkfile(pyblish.api.ContextPlugin): diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py index 052a97af7d..7a66026e1c 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py @@ -2,8 +2,8 @@ import copy import json import pyblish.api -from openpype.lib import get_subset_name_with_asset_doc from openpype.client import get_asset_by_name +from openpype.pipeline.create import get_subset_name_with_asset_doc class CollectBulkMovInstances(pyblish.api.InstancePlugin): diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_instances.py b/openpype/hosts/tvpaint/plugins/publish/collect_instances.py index 9b6d5c4879..68bfa8ef6a 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_instances.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_instances.py @@ -3,8 +3,8 @@ import copy import pyblish.api from openpype.client import get_asset_by_name -from openpype.lib import get_subset_name_with_asset_doc from openpype.pipeline import legacy_io +from openpype.pipeline.create import get_subset_name_with_asset_doc class CollectInstances(pyblish.api.ContextPlugin): diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_scene_render.py b/openpype/hosts/tvpaint/plugins/publish/collect_scene_render.py index 20c5bb586a..a7bc2f3c76 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_scene_render.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_scene_render.py @@ -3,7 +3,7 @@ import copy import pyblish.api from openpype.client import get_asset_by_name -from openpype.lib import get_subset_name_with_asset_doc +from openpype.pipeline.create import get_subset_name_with_asset_doc class CollectRenderScene(pyblish.api.ContextPlugin): diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_workfile.py b/openpype/hosts/tvpaint/plugins/publish/collect_workfile.py index 88c5f4dbc7..f88b32b980 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_workfile.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_workfile.py @@ -3,8 +3,8 @@ import json import pyblish.api from openpype.client import get_asset_by_name -from openpype.lib import get_subset_name_with_asset_doc from openpype.pipeline import legacy_io +from openpype.pipeline.create import get_subset_name_with_asset_doc class CollectWorkfile(pyblish.api.ContextPlugin): diff --git a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py index 20e277d794..5b0a4a6910 100644 --- a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py +++ b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py @@ -23,10 +23,8 @@ from openpype.lib import ( get_ffprobe_streams, convert_ffprobe_fps_value, ) -from openpype.lib.plugin_tools import ( - parse_json, - get_subset_name_with_asset_doc -) +from openpype.lib.plugin_tools import parse_json +from openpype.pipeline.create import get_subset_name_with_asset_doc class CollectPublishedFiles(pyblish.api.ContextPlugin): diff --git a/openpype/hosts/webpublisher/plugins/publish/collect_tvpaint_instances.py b/openpype/hosts/webpublisher/plugins/publish/collect_tvpaint_instances.py index 92f581be5f..3a9f8eb8f2 100644 --- a/openpype/hosts/webpublisher/plugins/publish/collect_tvpaint_instances.py +++ b/openpype/hosts/webpublisher/plugins/publish/collect_tvpaint_instances.py @@ -10,7 +10,7 @@ import re import copy import pyblish.api -from openpype.lib import get_subset_name_with_asset_doc +from openpype.pipeline.create import get_subset_name_with_asset_doc class CollectTVPaintInstances(pyblish.api.ContextPlugin): From ce31b9a47706f0c71f56fc9625d560e9cc5185a0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 23 Aug 2022 19:13:23 +0200 Subject: [PATCH 29/96] provide more data as arguments during publishing --- .../aftereffects/plugins/publish/collect_workfile.py | 3 ++- .../flame/plugins/publish/collect_timeline_otio.py | 3 +++ .../harmony/plugins/publish/collect_workfile.py | 3 ++- .../photoshop/plugins/publish/collect_review.py | 3 ++- .../photoshop/plugins/publish/collect_workfile.py | 3 ++- .../plugins/publish/collect_bulk_mov_instances.py | 4 +++- .../tvpaint/plugins/publish/collect_instances.py | 3 ++- .../tvpaint/plugins/publish/collect_scene_render.py | 3 ++- .../tvpaint/plugins/publish/collect_workfile.py | 3 ++- .../plugins/publish/collect_published_files.py | 9 +++++++-- .../plugins/publish/collect_tvpaint_instances.py | 12 ++++++++---- 11 files changed, 35 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/publish/collect_workfile.py b/openpype/hosts/aftereffects/plugins/publish/collect_workfile.py index b1f40113a4..bd52f569a3 100644 --- a/openpype/hosts/aftereffects/plugins/publish/collect_workfile.py +++ b/openpype/hosts/aftereffects/plugins/publish/collect_workfile.py @@ -77,7 +77,8 @@ class CollectWorkfile(pyblish.api.ContextPlugin): context.data["anatomyData"]["task"]["name"], context.data["assetEntity"], context.data["anatomyData"]["project"]["name"], - host_name=context.data["hostName"] + host_name=context.data["hostName"], + project_settings=context.data["project_settings"] ) # Create instance instance = context.create_instance(subset) diff --git a/openpype/hosts/flame/plugins/publish/collect_timeline_otio.py b/openpype/hosts/flame/plugins/publish/collect_timeline_otio.py index c0c7eee7f2..e57ef270b8 100644 --- a/openpype/hosts/flame/plugins/publish/collect_timeline_otio.py +++ b/openpype/hosts/flame/plugins/publish/collect_timeline_otio.py @@ -29,6 +29,9 @@ class CollecTimelineOTIO(pyblish.api.ContextPlugin): variant, task_name, asset_doc, + context.data["projectName"], + context.data["hostName"], + project_settings=context.data["project_settings"] ) # adding otio timeline to context diff --git a/openpype/hosts/harmony/plugins/publish/collect_workfile.py b/openpype/hosts/harmony/plugins/publish/collect_workfile.py index 924661d310..3d1d2f03c2 100644 --- a/openpype/hosts/harmony/plugins/publish/collect_workfile.py +++ b/openpype/hosts/harmony/plugins/publish/collect_workfile.py @@ -23,7 +23,8 @@ class CollectWorkfile(pyblish.api.ContextPlugin): context.data["anatomyData"]["task"]["name"], context.data["assetEntity"], context.data["anatomyData"]["project"]["name"], - host_name=context.data["hostName"] + host_name=context.data["hostName"], + project_settings=context.data["project_settings"] ) # Create instance diff --git a/openpype/hosts/photoshop/plugins/publish/collect_review.py b/openpype/hosts/photoshop/plugins/publish/collect_review.py index ce475524a7..eb2ad644e5 100644 --- a/openpype/hosts/photoshop/plugins/publish/collect_review.py +++ b/openpype/hosts/photoshop/plugins/publish/collect_review.py @@ -33,7 +33,8 @@ class CollectReview(pyblish.api.ContextPlugin): context.data["anatomyData"]["task"]["name"], context.data["assetEntity"], context.data["anatomyData"]["project"]["name"], - host_name=context.data["hostName"] + host_name=context.data["hostName"], + project_settings=context.data["project_settings"] ) instance = context.create_instance(subset) diff --git a/openpype/hosts/photoshop/plugins/publish/collect_workfile.py b/openpype/hosts/photoshop/plugins/publish/collect_workfile.py index 5e673bebb1..21ec914910 100644 --- a/openpype/hosts/photoshop/plugins/publish/collect_workfile.py +++ b/openpype/hosts/photoshop/plugins/publish/collect_workfile.py @@ -30,7 +30,8 @@ class CollectWorkfile(pyblish.api.ContextPlugin): context.data["anatomyData"]["task"]["name"], context.data["assetEntity"], context.data["anatomyData"]["project"]["name"], - host_name=context.data["hostName"] + host_name=context.data["hostName"], + project_settings=context.data["project_settings"] ) file_path = context.data["currentFile"] diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py index 7a66026e1c..fa99a8c7a7 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py @@ -49,7 +49,9 @@ class CollectBulkMovInstances(pyblish.api.InstancePlugin): self.subset_name_variant, task_name, asset_doc, - project_name + project_name, + host_name=context.data["hostName"], + project_settings=context.data["project_settings"] ) instance_name = f"{asset_name}_{subset_name}" diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_instances.py b/openpype/hosts/tvpaint/plugins/publish/collect_instances.py index 68bfa8ef6a..cd7eccc067 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_instances.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_instances.py @@ -113,7 +113,8 @@ class CollectInstances(pyblish.api.ContextPlugin): task_name, asset_doc, project_name, - host_name + host_name, + project_settings=context.data["project_settings"] ) instance_data["subset"] = new_subset_name diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_scene_render.py b/openpype/hosts/tvpaint/plugins/publish/collect_scene_render.py index a7bc2f3c76..d909317274 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_scene_render.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_scene_render.py @@ -82,7 +82,8 @@ class CollectRenderScene(pyblish.api.ContextPlugin): asset_doc, project_name, host_name, - dynamic_data=dynamic_data + dynamic_data=dynamic_data, + project_settings=context.data["project_settings"] ) instance_data = { diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_workfile.py b/openpype/hosts/tvpaint/plugins/publish/collect_workfile.py index f88b32b980..ef67ae8003 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_workfile.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_workfile.py @@ -45,7 +45,8 @@ class CollectWorkfile(pyblish.api.ContextPlugin): task_name, asset_doc, project_name, - host_name + host_name, + project_settings=context.data["project_settings"] ) # Create Workfile instance diff --git a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py index 5b0a4a6910..4a497a9514 100644 --- a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py +++ b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py @@ -79,8 +79,13 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin): extension.replace(".", '')) subset_name = get_subset_name_with_asset_doc( - family, variant, task_name, asset_doc, - project_name=project_name, host_name="webpublisher" + family, + variant, + task_name, + asset_doc, + project_name=project_name, + host_name="webpublisher", + project_settings=context.data["project_settings"] ) version = self._get_next_version( project_name, asset_doc, subset_name diff --git a/openpype/hosts/webpublisher/plugins/publish/collect_tvpaint_instances.py b/openpype/hosts/webpublisher/plugins/publish/collect_tvpaint_instances.py index 3a9f8eb8f2..bdacdbdc26 100644 --- a/openpype/hosts/webpublisher/plugins/publish/collect_tvpaint_instances.py +++ b/openpype/hosts/webpublisher/plugins/publish/collect_tvpaint_instances.py @@ -53,7 +53,8 @@ class CollectTVPaintInstances(pyblish.api.ContextPlugin): task_name, asset_doc, project_name, - host_name + host_name, + project_settings=context.data["project_settings"] ) workfile_instance = self._create_workfile_instance( context, workfile_subset_name @@ -67,7 +68,8 @@ class CollectTVPaintInstances(pyblish.api.ContextPlugin): task_name, asset_doc, project_name, - host_name + host_name, + project_settings=context.data["project_settings"] ) review_instance = self._create_review_instance( context, review_subset_name @@ -121,7 +123,8 @@ class CollectTVPaintInstances(pyblish.api.ContextPlugin): asset_doc, project_name, host_name, - dynamic_data=dynamic_data + dynamic_data=dynamic_data, + project_settings=context.data["project_settings"] ) instance = self._create_render_pass_instance( @@ -144,7 +147,8 @@ class CollectTVPaintInstances(pyblish.api.ContextPlugin): asset_doc, project_name, host_name, - dynamic_data=dynamic_data + dynamic_data=dynamic_data, + project_settings=context.data["project_settings"] ) instance = self._create_render_layer_instance( context, layers, subset_name From df0565222c0f0061ca34472a02f5aa1747faf32e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 23 Aug 2022 19:15:54 +0200 Subject: [PATCH 30/96] marked functions in openpype.lib as deprecated --- openpype/lib/plugin_tools.py | 94 +++++++++++------------------------- 1 file changed, 28 insertions(+), 66 deletions(-) diff --git a/openpype/lib/plugin_tools.py b/openpype/lib/plugin_tools.py index 060db94ae0..6534e7355f 100644 --- a/openpype/lib/plugin_tools.py +++ b/openpype/lib/plugin_tools.py @@ -8,16 +8,10 @@ import json import warnings import functools -from openpype.client import get_asset_by_id from openpype.settings import get_project_settings -from .profiles_filtering import filter_profiles - log = logging.getLogger(__name__) -# Subset name template used when plugin does not have defined any -DEFAULT_SUBSET_TEMPLATE = "{family}{Variant}" - class PluginToolsDeprecatedWarning(DeprecationWarning): pass @@ -64,13 +58,14 @@ def deprecated(new_destination): return _decorator(func) -class TaskNotSetError(KeyError): - def __init__(self, msg=None): - if not msg: - msg = "Creator's subset name template requires task name." - super(TaskNotSetError, self).__init__(msg) +@deprecated("openpype.pipeline.create.TaskNotSetError") +def TaskNotSetError(*args, **kwargs): + from openpype.pipeline.create import TaskNotSetError + + return TaskNotSetError(*args, **kwargs) +@deprecated("openpype.pipeline.create.get_subset_name_with_asset_doc") def get_subset_name_with_asset_doc( family, variant, @@ -109,61 +104,22 @@ def get_subset_name_with_asset_doc( dbcon (AvalonMongoDB): Mongo connection to be able query asset document if 'asset_doc' is not passed. """ - if not family: - return "" - if not host_name: - host_name = os.environ["AVALON_APP"] + from openpype.pipeline.create import get_subset_name_with_asset_doc - # Use only last part of class family value split by dot (`.`) - family = family.rsplit(".", 1)[-1] - - if project_name is None: - from openpype.pipeline import legacy_io - - project_name = legacy_io.Session["AVALON_PROJECT"] - - asset_tasks = asset_doc.get("data", {}).get("tasks") or {} - task_info = asset_tasks.get(task_name) or {} - task_type = task_info.get("type") - - # Get settings - tools_settings = get_project_settings(project_name)["global"]["tools"] - profiles = tools_settings["creator"]["subset_name_profiles"] - filtering_criteria = { - "families": family, - "hosts": host_name, - "tasks": task_name, - "task_types": task_type - } - - matching_profile = filter_profiles(profiles, filtering_criteria) - template = None - if matching_profile: - template = matching_profile["template"] - - # Make sure template is set (matching may have empty string) - if not template: - template = default_template or DEFAULT_SUBSET_TEMPLATE - - # Simple check of task name existence for template with {task} in - # - missing task should be possible only in Standalone publisher - if not task_name and "{task" in template.lower(): - raise TaskNotSetError() - - fill_pairs = { - "variant": variant, - "family": family, - "task": task_name - } - if dynamic_data: - # Dynamic data may override default values - for key, value in dynamic_data.items(): - fill_pairs[key] = value - - return template.format(**prepare_template_data(fill_pairs)) + return get_subset_name_with_asset_doc( + family, + variant, + task_name, + asset_doc, + project_name, + host_name, + default_template, + dynamic_data + ) +@deprecated("openpype.pipeline.create.get_subset_name") def get_subset_name( family, variant, @@ -183,16 +139,16 @@ def get_subset_name( `get_subset_name_with_asset_doc` where asset document is expected. """ + from openpype.pipeline.create import get_subset_name + if project_name is None: project_name = dbcon.project_name - asset_doc = get_asset_by_id(project_name, asset_id, fields=["data.tasks"]) - - return get_subset_name_with_asset_doc( + return get_subset_name( family, variant, task_name, - asset_doc or {}, + asset_id, project_name, host_name, default_template, @@ -254,6 +210,9 @@ def filter_pyblish_plugins(plugins): Args: plugins (dict): Dictionary of plugins produced by :mod:`pyblish-base` `discover()` method. + + Deprecated: + Function will be removed after release version 3.15.* """ from openpype.pipeline.publish.lib import filter_pyblish_plugins @@ -277,6 +236,9 @@ def set_plugin_attributes_from_settings( Value from environment `AVALON_APP` is used if not entered. project_name (str): Name of project for which settings will be loaded. Value from environment `AVALON_PROJECT` is used if not entered. + + Deprecated: + Function will be removed after release version 3.15.* """ # Function is not used anymore From 7a4cd9c1faca8c4ca3d7f2fea871c241c38b1320 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 23 Aug 2022 19:20:04 +0200 Subject: [PATCH 31/96] removed 'get_subset_name' and renamed 'get_subset_name_with_asset_doc' to 'get_subset_name' --- .../plugins/publish/collect_workfile.py | 4 +- .../plugins/publish/collect_timeline_otio.py | 4 +- .../plugins/publish/collect_workfile.py | 4 +- .../plugins/publish/collect_review.py | 4 +- .../plugins/publish/collect_workfile.py | 4 +- .../publish/collect_bulk_mov_instances.py | 4 +- .../plugins/create/create_movie_batch.py | 6 +-- .../plugins/publish/collect_instances.py | 4 +- .../plugins/publish/collect_scene_render.py | 4 +- .../plugins/publish/collect_workfile.py | 4 +- .../publish/collect_published_files.py | 4 +- .../publish/collect_tvpaint_instances.py | 10 ++--- openpype/lib/plugin_tools.py | 13 +++--- openpype/pipeline/create/__init__.py | 2 - openpype/pipeline/create/subset_name.py | 40 +------------------ 15 files changed, 37 insertions(+), 74 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/publish/collect_workfile.py b/openpype/hosts/aftereffects/plugins/publish/collect_workfile.py index bd52f569a3..3c5013b3bd 100644 --- a/openpype/hosts/aftereffects/plugins/publish/collect_workfile.py +++ b/openpype/hosts/aftereffects/plugins/publish/collect_workfile.py @@ -2,7 +2,7 @@ import os import pyblish.api from openpype.pipeline import legacy_io -from openpype.pipeline.create import get_subset_name_with_asset_doc +from openpype.pipeline.create import get_subset_name class CollectWorkfile(pyblish.api.ContextPlugin): @@ -71,7 +71,7 @@ class CollectWorkfile(pyblish.api.ContextPlugin): # workfile instance family = "workfile" - subset = get_subset_name_with_asset_doc( + subset = get_subset_name( family, self.default_variant, context.data["anatomyData"]["task"]["name"], diff --git a/openpype/hosts/flame/plugins/publish/collect_timeline_otio.py b/openpype/hosts/flame/plugins/publish/collect_timeline_otio.py index e57ef270b8..917041e053 100644 --- a/openpype/hosts/flame/plugins/publish/collect_timeline_otio.py +++ b/openpype/hosts/flame/plugins/publish/collect_timeline_otio.py @@ -3,7 +3,7 @@ import pyblish.api import openpype.hosts.flame.api as opfapi from openpype.hosts.flame.otio import flame_export from openpype.pipeline import legacy_io -from openpype.pipeline.create import get_subset_name_with_asset_doc +from openpype.pipeline.create import get_subset_name class CollecTimelineOTIO(pyblish.api.ContextPlugin): @@ -24,7 +24,7 @@ class CollecTimelineOTIO(pyblish.api.ContextPlugin): sequence = opfapi.get_current_sequence(opfapi.CTX.selection) # create subset name - subset_name = get_subset_name_with_asset_doc( + subset_name = get_subset_name( family, variant, task_name, diff --git a/openpype/hosts/harmony/plugins/publish/collect_workfile.py b/openpype/hosts/harmony/plugins/publish/collect_workfile.py index 3d1d2f03c2..3624147435 100644 --- a/openpype/hosts/harmony/plugins/publish/collect_workfile.py +++ b/openpype/hosts/harmony/plugins/publish/collect_workfile.py @@ -3,7 +3,7 @@ import os import pyblish.api -from openpype.pipeline.create import get_subset_name_with_asset_doc +from openpype.pipeline.create import get_subset_name class CollectWorkfile(pyblish.api.ContextPlugin): @@ -17,7 +17,7 @@ class CollectWorkfile(pyblish.api.ContextPlugin): """Plugin entry point.""" family = "workfile" basename = os.path.basename(context.data["currentFile"]) - subset = get_subset_name_with_asset_doc( + subset = get_subset_name( family, "", context.data["anatomyData"]["task"]["name"], diff --git a/openpype/hosts/photoshop/plugins/publish/collect_review.py b/openpype/hosts/photoshop/plugins/publish/collect_review.py index eb2ad644e5..7f395b46d7 100644 --- a/openpype/hosts/photoshop/plugins/publish/collect_review.py +++ b/openpype/hosts/photoshop/plugins/publish/collect_review.py @@ -10,7 +10,7 @@ import os import pyblish.api -from openpype.pipeline.create import get_subset_name_with_asset_doc +from openpype.pipeline.create import get_subset_name class CollectReview(pyblish.api.ContextPlugin): @@ -27,7 +27,7 @@ class CollectReview(pyblish.api.ContextPlugin): def process(self, context): family = "review" - subset = get_subset_name_with_asset_doc( + subset = get_subset_name( family, context.data.get("variant", ''), context.data["anatomyData"]["task"]["name"], diff --git a/openpype/hosts/photoshop/plugins/publish/collect_workfile.py b/openpype/hosts/photoshop/plugins/publish/collect_workfile.py index 21ec914910..9a5aad5569 100644 --- a/openpype/hosts/photoshop/plugins/publish/collect_workfile.py +++ b/openpype/hosts/photoshop/plugins/publish/collect_workfile.py @@ -1,7 +1,7 @@ import os import pyblish.api -from openpype.pipeline.create import get_subset_name_with_asset_doc +from openpype.pipeline.create import get_subset_name class CollectWorkfile(pyblish.api.ContextPlugin): @@ -24,7 +24,7 @@ class CollectWorkfile(pyblish.api.ContextPlugin): family = "workfile" # context.data["variant"] might come only from collect_batch_data variant = context.data.get("variant") or self.default_variant - subset = get_subset_name_with_asset_doc( + subset = get_subset_name( family, variant, context.data["anatomyData"]["task"]["name"], diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py index fa99a8c7a7..7925b0ecf3 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py @@ -3,7 +3,7 @@ import json import pyblish.api from openpype.client import get_asset_by_name -from openpype.pipeline.create import get_subset_name_with_asset_doc +from openpype.pipeline.create import get_subset_name class CollectBulkMovInstances(pyblish.api.InstancePlugin): @@ -44,7 +44,7 @@ class CollectBulkMovInstances(pyblish.api.InstancePlugin): task_name = available_task_names[_task_name_low] break - subset_name = get_subset_name_with_asset_doc( + subset_name = get_subset_name( self.new_instance_family, self.subset_name_variant, task_name, diff --git a/openpype/hosts/traypublisher/plugins/create/create_movie_batch.py b/openpype/hosts/traypublisher/plugins/create/create_movie_batch.py index 5d0fe4b177..abe29d7473 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_movie_batch.py +++ b/openpype/hosts/traypublisher/plugins/create/create_movie_batch.py @@ -12,7 +12,7 @@ from openpype.pipeline import ( CreatorError ) from openpype.pipeline.create import ( - get_subset_name_with_asset_doc, + get_subset_name, TaskNotSetError, ) @@ -132,7 +132,7 @@ class BatchMovieCreator(TrayPublishCreator): task_name = self._get_task_name(asset_doc) try: - subset_name = get_subset_name_with_asset_doc( + subset_name = get_subset_name( self.family, variant, task_name, @@ -145,7 +145,7 @@ class BatchMovieCreator(TrayPublishCreator): # but user have ability to change it # NOTE: This expect that there is not task 'Undefined' on asset task_name = "Undefined" - subset_name = get_subset_name_with_asset_doc( + subset_name = get_subset_name( self.family, variant, task_name, diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_instances.py b/openpype/hosts/tvpaint/plugins/publish/collect_instances.py index cd7eccc067..ae1326a5bd 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_instances.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_instances.py @@ -4,7 +4,7 @@ import pyblish.api from openpype.client import get_asset_by_name from openpype.pipeline import legacy_io -from openpype.pipeline.create import get_subset_name_with_asset_doc +from openpype.pipeline.create import get_subset_name class CollectInstances(pyblish.api.ContextPlugin): @@ -107,7 +107,7 @@ class CollectInstances(pyblish.api.ContextPlugin): # Use empty variant value variant = "" task_name = legacy_io.Session["AVALON_TASK"] - new_subset_name = get_subset_name_with_asset_doc( + new_subset_name = get_subset_name( family, variant, task_name, diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_scene_render.py b/openpype/hosts/tvpaint/plugins/publish/collect_scene_render.py index d909317274..92a2815ba0 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_scene_render.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_scene_render.py @@ -3,7 +3,7 @@ import copy import pyblish.api from openpype.client import get_asset_by_name -from openpype.pipeline.create import get_subset_name_with_asset_doc +from openpype.pipeline.create import get_subset_name class CollectRenderScene(pyblish.api.ContextPlugin): @@ -75,7 +75,7 @@ class CollectRenderScene(pyblish.api.ContextPlugin): dynamic_data["render_pass"] = dynamic_data["renderpass"] task_name = workfile_context["task"] - subset_name = get_subset_name_with_asset_doc( + subset_name = get_subset_name( "render", variant, task_name, diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_workfile.py b/openpype/hosts/tvpaint/plugins/publish/collect_workfile.py index ef67ae8003..8c7c8c3899 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_workfile.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_workfile.py @@ -4,7 +4,7 @@ import pyblish.api from openpype.client import get_asset_by_name from openpype.pipeline import legacy_io -from openpype.pipeline.create import get_subset_name_with_asset_doc +from openpype.pipeline.create import get_subset_name class CollectWorkfile(pyblish.api.ContextPlugin): @@ -39,7 +39,7 @@ class CollectWorkfile(pyblish.api.ContextPlugin): # Use empty variant value variant = "" task_name = legacy_io.Session["AVALON_TASK"] - subset_name = get_subset_name_with_asset_doc( + subset_name = get_subset_name( family, variant, task_name, diff --git a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py index 4a497a9514..f2d1d19609 100644 --- a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py +++ b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py @@ -24,7 +24,7 @@ from openpype.lib import ( convert_ffprobe_fps_value, ) from openpype.lib.plugin_tools import parse_json -from openpype.pipeline.create import get_subset_name_with_asset_doc +from openpype.pipeline.create import get_subset_name class CollectPublishedFiles(pyblish.api.ContextPlugin): @@ -78,7 +78,7 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin): is_sequence, extension.replace(".", '')) - subset_name = get_subset_name_with_asset_doc( + subset_name = get_subset_name( family, variant, task_name, diff --git a/openpype/hosts/webpublisher/plugins/publish/collect_tvpaint_instances.py b/openpype/hosts/webpublisher/plugins/publish/collect_tvpaint_instances.py index bdacdbdc26..948e86c23e 100644 --- a/openpype/hosts/webpublisher/plugins/publish/collect_tvpaint_instances.py +++ b/openpype/hosts/webpublisher/plugins/publish/collect_tvpaint_instances.py @@ -10,7 +10,7 @@ import re import copy import pyblish.api -from openpype.pipeline.create import get_subset_name_with_asset_doc +from openpype.pipeline.create import get_subset_name class CollectTVPaintInstances(pyblish.api.ContextPlugin): @@ -47,7 +47,7 @@ class CollectTVPaintInstances(pyblish.api.ContextPlugin): new_instances = [] # Workfile instance - workfile_subset_name = get_subset_name_with_asset_doc( + workfile_subset_name = get_subset_name( self.workfile_family, self.workfile_variant, task_name, @@ -62,7 +62,7 @@ class CollectTVPaintInstances(pyblish.api.ContextPlugin): new_instances.append(workfile_instance) # Review instance - review_subset_name = get_subset_name_with_asset_doc( + review_subset_name = get_subset_name( self.review_family, self.review_variant, task_name, @@ -116,7 +116,7 @@ class CollectTVPaintInstances(pyblish.api.ContextPlugin): "family": "render" } - subset_name = get_subset_name_with_asset_doc( + subset_name = get_subset_name( self.render_pass_family, render_pass, task_name, @@ -140,7 +140,7 @@ class CollectTVPaintInstances(pyblish.api.ContextPlugin): # Override family for subset name "family": "render" } - subset_name = get_subset_name_with_asset_doc( + subset_name = get_subset_name( self.render_layer_family, variant, task_name, diff --git a/openpype/lib/plugin_tools.py b/openpype/lib/plugin_tools.py index 6534e7355f..065188625e 100644 --- a/openpype/lib/plugin_tools.py +++ b/openpype/lib/plugin_tools.py @@ -8,6 +8,7 @@ import json import warnings import functools +from openpype.client import get_asset_by_id from openpype.settings import get_project_settings log = logging.getLogger(__name__) @@ -65,7 +66,7 @@ def TaskNotSetError(*args, **kwargs): return TaskNotSetError(*args, **kwargs) -@deprecated("openpype.pipeline.create.get_subset_name_with_asset_doc") +@deprecated("openpype.pipeline.create.get_subset_name") def get_subset_name_with_asset_doc( family, variant, @@ -105,9 +106,9 @@ def get_subset_name_with_asset_doc( if 'asset_doc' is not passed. """ - from openpype.pipeline.create import get_subset_name_with_asset_doc + from openpype.pipeline.create import get_subset_name - return get_subset_name_with_asset_doc( + return get_subset_name( family, variant, task_name, @@ -119,7 +120,7 @@ def get_subset_name_with_asset_doc( ) -@deprecated("openpype.pipeline.create.get_subset_name") +@deprecated def get_subset_name( family, variant, @@ -144,11 +145,13 @@ def get_subset_name( if project_name is None: project_name = dbcon.project_name + asset_doc = get_asset_by_id(project_name, asset_id, fields=["data.tasks"]) + return get_subset_name( family, variant, task_name, - asset_id, + asset_doc, project_name, host_name, default_template, diff --git a/openpype/pipeline/create/__init__.py b/openpype/pipeline/create/__init__.py index 4f3d2c03e5..b698224924 100644 --- a/openpype/pipeline/create/__init__.py +++ b/openpype/pipeline/create/__init__.py @@ -6,7 +6,6 @@ from .constants import ( from .subset_name import ( TaskNotSetError, get_subset_name, - get_subset_name_with_asset_doc, ) from .creator_plugins import ( @@ -42,7 +41,6 @@ __all__ = ( "TaskNotSetError", "get_subset_name", - "get_subset_name_with_asset_doc", "CreatorError", diff --git a/openpype/pipeline/create/subset_name.py b/openpype/pipeline/create/subset_name.py index b6028d6427..f508263708 100644 --- a/openpype/pipeline/create/subset_name.py +++ b/openpype/pipeline/create/subset_name.py @@ -1,6 +1,5 @@ import os -from openpype.client import get_asset_by_id from openpype.settings import get_project_settings from openpype.lib import filter_profiles, prepare_template_data from openpype.pipeline import legacy_io @@ -15,7 +14,7 @@ class TaskNotSetError(KeyError): super(TaskNotSetError, self).__init__(msg) -def get_subset_name_with_asset_doc( +def get_subset_name( family, variant, task_name, @@ -108,40 +107,3 @@ def get_subset_name_with_asset_doc( fill_pairs[key] = value return template.format(**prepare_template_data(fill_pairs)) - - -def get_subset_name( - family, - variant, - task_name, - asset_id, - project_name=None, - host_name=None, - default_template=None, - dynamic_data=None, - project_settings=None -): - """Calculate subset name using OpenPype settings. - - This variant of function expects asset id as argument. - - This is legacy function should be replaced with - `get_subset_name_with_asset_doc` where asset document is expected. - """ - - if project_name is None: - project_name = legacy_io.Session["AVALON_PROJECT"] - - asset_doc = get_asset_by_id(project_name, asset_id, fields=["data.tasks"]) - - return get_subset_name_with_asset_doc( - family, - variant, - task_name, - asset_doc or {}, - project_name, - host_name, - default_template, - dynamic_data, - project_settings - ) From deec5e5c936abd1fca49cc586875e0d4f671e761 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 24 Aug 2022 12:30:29 +0200 Subject: [PATCH 32/96] nuke: fixing setting colorspace --- openpype/hosts/nuke/api/lib.py | 38 ++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index a53d932db1..10ddfca51e 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -1945,15 +1945,25 @@ class WorkfileSettings(object): if not write_node: return - # write all knobs to node - for knob in nuke_imageio_writes["knobs"]: - value = knob["value"] - if isinstance(value, six.text_type): - value = str(value) - if str(value).startswith("0x"): - value = int(value, 16) + try: + # write all knobs to node + for knob in nuke_imageio_writes["knobs"]: + value = knob["value"] + if isinstance(value, six.text_type): + value = str(value) + if str(value).startswith("0x"): + value = int(value, 16) - write_node[knob["name"]].setValue(value) + log.debug("knob: {}| value: {}".format( + knob["name"], value + )) + write_node[knob["name"]].setValue(value) + except TypeError: + log.warning( + "Legacy workflow didnt work, switching to current") + + set_node_knobs_from_settings( + write_node, nuke_imageio_writes["knobs"]) def set_reads_colorspace(self, read_clrs_inputs): """ Setting colorspace to Read nodes @@ -2010,12 +2020,14 @@ class WorkfileSettings(object): # get imageio nuke_colorspace = get_nuke_imageio_settings() + log.info("Setting colorspace to workfile...") try: self.set_root_colorspace(nuke_colorspace["workfile"]) except AttributeError: msg = "set_colorspace(): missing `workfile` settings in template" nuke.message(msg) + log.info("Setting colorspace to viewers...") try: self.set_viewers_colorspace(nuke_colorspace["viewer"]) except AttributeError: @@ -2023,24 +2035,18 @@ class WorkfileSettings(object): nuke.message(msg) log.error(msg) + log.info("Setting colorspace to write nodes...") try: self.set_writes_colorspace() except AttributeError as _error: nuke.message(_error) log.error(_error) + log.info("Setting colorspace to read nodes...") read_clrs_inputs = nuke_colorspace["regexInputs"].get("inputs", []) if read_clrs_inputs: self.set_reads_colorspace(read_clrs_inputs) - try: - for key in nuke_colorspace: - log.debug("Preset's colorspace key: {}".format(key)) - except TypeError: - msg = "Nuke is not in templates! Contact your supervisor!" - nuke.message(msg) - log.error(msg) - def reset_frame_range_handles(self): """Set frame range to current asset""" From 8539c03d72c246c125334f059522b14073cd6ed8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 24 Aug 2022 17:53:21 +0200 Subject: [PATCH 33/96] remove getattrs on instance and context --- openpype/tools/publisher/widgets/publish_widget.py | 2 -- openpype/tools/pyblish_pype/model.py | 5 +---- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/openpype/tools/publisher/widgets/publish_widget.py b/openpype/tools/publisher/widgets/publish_widget.py index 80d0265dd3..b32b5381d1 100644 --- a/openpype/tools/publisher/widgets/publish_widget.py +++ b/openpype/tools/publisher/widgets/publish_widget.py @@ -335,14 +335,12 @@ class PublishFrame(QtWidgets.QFrame): if instance is None: new_name = ( context.data.get("label") - or getattr(context, "label", None) or context.data.get("name") or "Context" ) else: new_name = ( instance.data.get("label") - or getattr(instance, "label", None) or instance.data["name"] ) diff --git a/openpype/tools/pyblish_pype/model.py b/openpype/tools/pyblish_pype/model.py index 309126a884..1479d91bb5 100644 --- a/openpype/tools/pyblish_pype/model.py +++ b/openpype/tools/pyblish_pype/model.py @@ -613,10 +613,7 @@ class InstanceItem(QtGui.QStandardItem): if role == QtCore.Qt.DisplayRole: label = None if settings.UseLabel: - label = ( - self.instance.data.get("label") - or getattr(self.instance, "label", None) - ) + label = self.instance.data.get("label") if not label: if self.is_context: From d623dfa857be9b6650a7c4cd285f73b02be32808 Mon Sep 17 00:00:00 2001 From: Kaa Maurice Date: Thu, 25 Aug 2022 11:54:09 +0200 Subject: [PATCH 34/96] 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 382ec674a8d044f2f4f1650773b78192062618d2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 25 Aug 2022 12:02:22 +0200 Subject: [PATCH 35/96] copied 'get_unique_layer_name' and 'get_background_layers' into ae lib --- openpype/hosts/aftereffects/api/lib.py | 56 ++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/openpype/hosts/aftereffects/api/lib.py b/openpype/hosts/aftereffects/api/lib.py index ce4cbf09af..dc16aaeac5 100644 --- a/openpype/hosts/aftereffects/api/lib.py +++ b/openpype/hosts/aftereffects/api/lib.py @@ -1,5 +1,7 @@ import os import sys +import re +import json import contextlib import traceback import logging @@ -68,3 +70,57 @@ def get_extension_manifest_path(): "CSXS", "manifest.xml" ) + + +def get_unique_layer_name(layers, name): + """ + Gets all layer names and if 'name' is present in them, increases + suffix by 1 (eg. creates unique layer name - for Loader) + Args: + layers (list): of strings, names only + name (string): checked value + + Returns: + (string): name_00X (without version) + """ + names = {} + for layer in layers: + layer_name = re.sub(r'_\d{3}$', '', layer) + if layer_name in names.keys(): + names[layer_name] = names[layer_name] + 1 + else: + names[layer_name] = 1 + occurrences = names.get(name, 0) + + return "{}_{:0>3d}".format(name, occurrences + 1) + + +def get_background_layers(file_url): + """ + Pulls file name from background json file, enrich with folder url for + AE to be able import files. + + Order is important, follows order in json. + + Args: + file_url (str): abs url of background json + + Returns: + (list): of abs paths to images + """ + with open(file_url) as json_file: + data = json.load(json_file) + + layers = list() + bg_folder = os.path.dirname(file_url) + for child in data['children']: + if child.get("filename"): + layers.append(os.path.join(bg_folder, child.get("filename")). + replace("\\", "/")) + else: + for layer in child['children']: + if layer.get("filename"): + layers.append(os.path.join(bg_folder, + layer.get("filename")). + replace("\\", "/")) + return layers From 5d83a428d9f7fd11fdf8002dc231c3577b7de7a3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 25 Aug 2022 12:02:37 +0200 Subject: [PATCH 36/96] change imports to new location in loaders --- .../hosts/aftereffects/plugins/load/load_background.py | 8 ++++---- openpype/hosts/aftereffects/plugins/load/load_file.py | 7 +++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/load/load_background.py b/openpype/hosts/aftereffects/plugins/load/load_background.py index d346df504a..260e780be0 100644 --- a/openpype/hosts/aftereffects/plugins/load/load_background.py +++ b/openpype/hosts/aftereffects/plugins/load/load_background.py @@ -1,14 +1,14 @@ import re -from openpype.lib import ( - get_background_layers, - get_unique_layer_name -) from openpype.pipeline import get_representation_path from openpype.hosts.aftereffects.api import ( AfterEffectsLoader, containerise ) +from openpype.hosts.aftereffects.api.lib import ( + get_background_layers, + get_unique_layer_name, +) class BackgroundLoader(AfterEffectsLoader): diff --git a/openpype/hosts/aftereffects/plugins/load/load_file.py b/openpype/hosts/aftereffects/plugins/load/load_file.py index 6ab69c6bfa..2ddc9825e5 100644 --- a/openpype/hosts/aftereffects/plugins/load/load_file.py +++ b/openpype/hosts/aftereffects/plugins/load/load_file.py @@ -1,12 +1,11 @@ import re -from openpype import lib - from openpype.pipeline import get_representation_path from openpype.hosts.aftereffects.api import ( AfterEffectsLoader, containerise ) +from openpype.hosts.aftereffects.api.lib import get_unique_layer_name class FileLoader(AfterEffectsLoader): @@ -28,7 +27,7 @@ class FileLoader(AfterEffectsLoader): stub = self.get_stub() layers = stub.get_items(comps=True, folders=True, footages=True) existing_layers = [layer.name for layer in layers] - comp_name = lib.get_unique_layer_name( + comp_name = get_unique_layer_name( existing_layers, "{}_{}".format(context["asset"]["name"], name)) import_options = {} @@ -87,7 +86,7 @@ class FileLoader(AfterEffectsLoader): if namespace_from_container != layer_name: layers = stub.get_items(comps=True) existing_layers = [layer.name for layer in layers] - layer_name = lib.get_unique_layer_name( + layer_name = get_unique_layer_name( existing_layers, "{}_{}".format(context["asset"], context["subset"])) else: # switching version - keep same name From d263a8ef9df1aa496a44c08d6c0b7e69810153d6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 25 Aug 2022 12:02:51 +0200 Subject: [PATCH 37/96] remove functions from openpype lib --- openpype/lib/__init__.py | 4 --- openpype/lib/plugin_tools.py | 54 ------------------------------------ 2 files changed, 58 deletions(-) diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py index 3d3e425a86..adb857a056 100644 --- a/openpype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -189,8 +189,6 @@ from .plugin_tools import ( filter_pyblish_plugins, set_plugin_attributes_from_settings, source_hash, - get_unique_layer_name, - get_background_layers, ) from .path_tools import ( @@ -354,8 +352,6 @@ __all__ = [ "filter_pyblish_plugins", "set_plugin_attributes_from_settings", "source_hash", - "get_unique_layer_name", - "get_background_layers", "create_hard_link", "version_up", diff --git a/openpype/lib/plugin_tools.py b/openpype/lib/plugin_tools.py index 060db94ae0..9080918dfa 100644 --- a/openpype/lib/plugin_tools.py +++ b/openpype/lib/plugin_tools.py @@ -375,60 +375,6 @@ def source_hash(filepath, *args): return "|".join([file_name, time, size] + list(args)).replace(".", ",") -def get_unique_layer_name(layers, name): - """ - Gets all layer names and if 'name' is present in them, increases - suffix by 1 (eg. creates unique layer name - for Loader) - Args: - layers (list): of strings, names only - name (string): checked value - - Returns: - (string): name_00X (without version) - """ - names = {} - for layer in layers: - layer_name = re.sub(r'_\d{3}$', '', layer) - if layer_name in names.keys(): - names[layer_name] = names[layer_name] + 1 - else: - names[layer_name] = 1 - occurrences = names.get(name, 0) - - return "{}_{:0>3d}".format(name, occurrences + 1) - - -def get_background_layers(file_url): - """ - Pulls file name from background json file, enrich with folder url for - AE to be able import files. - - Order is important, follows order in json. - - Args: - file_url (str): abs url of background json - - Returns: - (list): of abs paths to images - """ - with open(file_url) as json_file: - data = json.load(json_file) - - layers = list() - bg_folder = os.path.dirname(file_url) - for child in data['children']: - if child.get("filename"): - layers.append(os.path.join(bg_folder, child.get("filename")). - replace("\\", "/")) - else: - for layer in child['children']: - if layer.get("filename"): - layers.append(os.path.join(bg_folder, - layer.get("filename")). - replace("\\", "/")) - return layers - - def parse_json(path): """Parses json file at 'path' location From 5372c016eadfe2dd09cfc1803a6984cfca24d61b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 25 Aug 2022 12:19:47 +0200 Subject: [PATCH 38/96] moved 'OpenPypeInterface' into interfaces.py --- openpype/modules/__init__.py | 2 -- openpype/modules/base.py | 42 +++++++--------------------------- openpype/modules/interfaces.py | 29 +++++++++++++++++++++-- 3 files changed, 35 insertions(+), 38 deletions(-) diff --git a/openpype/modules/__init__.py b/openpype/modules/__init__.py index 68b5f6c247..02e7dc13ab 100644 --- a/openpype/modules/__init__.py +++ b/openpype/modules/__init__.py @@ -2,7 +2,6 @@ from .base import ( OpenPypeModule, OpenPypeAddOn, - OpenPypeInterface, load_modules, @@ -20,7 +19,6 @@ from .base import ( __all__ = ( "OpenPypeModule", "OpenPypeAddOn", - "OpenPypeInterface", "load_modules", diff --git a/openpype/modules/base.py b/openpype/modules/base.py index 1316d7f734..1b8cf5d769 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -28,6 +28,14 @@ from openpype.settings.lib import ( ) from openpype.lib import PypeLogger +from .interfaces import ( + OpenPypeInterface, + IPluginPaths, + IHostModule, + ITrayModule, + ITrayService +) + # Files that will be always ignored on modules import IGNORED_FILENAMES = ( "__pycache__", @@ -391,29 +399,7 @@ def _load_modules(): log.error(msg, exc_info=True) -class _OpenPypeInterfaceMeta(ABCMeta): - """OpenPypeInterface meta class to print proper string.""" - def __str__(self): - return "<'OpenPypeInterface.{}'>".format(self.__name__) - - def __repr__(self): - return str(self) - - -@six.add_metaclass(_OpenPypeInterfaceMeta) -class OpenPypeInterface: - """Base class of Interface that can be used as Mixin with abstract parts. - - This is way how OpenPype module or addon can tell that has implementation - for specific part or for other module/addon. - - Child classes of OpenPypeInterface may be used as mixin in different - OpenPype modules which means they have to have implemented methods defined - in the interface. By default interface does not have any abstract parts. - """ - - pass @six.add_metaclass(ABCMeta) @@ -749,8 +735,6 @@ class ModulesManager: and "actions" each containing list of paths. """ # Output structure - from openpype_interfaces import IPluginPaths - output = { "publish": [], "create": [], @@ -807,8 +791,6 @@ class ModulesManager: list: List of creator plugin paths. """ # Output structure - from openpype_interfaces import IPluginPaths - output = [] for module in self.get_enabled_modules(): # Skip module that do not inherit from `IPluginPaths` @@ -897,8 +879,6 @@ class ModulesManager: host name set to passed 'host_name'. """ - from openpype_interfaces import IHostModule - for module in self.get_enabled_modules(): if ( isinstance(module, IHostModule) @@ -915,8 +895,6 @@ class ModulesManager: inheriting 'IHostModule'. """ - from openpype_interfaces import IHostModule - host_names = { module.host_name for module in self.get_enabled_modules() @@ -1098,8 +1076,6 @@ class TrayModulesManager(ModulesManager): self.tray_menu(tray_menu) def get_enabled_tray_modules(self): - from openpype_interfaces import ITrayModule - output = [] for module in self.modules: if module.enabled and isinstance(module, ITrayModule): @@ -1175,8 +1151,6 @@ class TrayModulesManager(ModulesManager): self._report["Tray menu"] = report def start_modules(self): - from openpype_interfaces import ITrayService - report = {} time_start = time.time() prev_start_time = time_start diff --git a/openpype/modules/interfaces.py b/openpype/modules/interfaces.py index 14f49204ee..8221db4d05 100644 --- a/openpype/modules/interfaces.py +++ b/openpype/modules/interfaces.py @@ -1,8 +1,33 @@ -from abc import abstractmethod, abstractproperty +from abc import ABCMeta, abstractmethod, abstractproperty + +import six from openpype import resources -from openpype.modules import OpenPypeInterface + +class _OpenPypeInterfaceMeta(ABCMeta): + """OpenPypeInterface meta class to print proper string.""" + + def __str__(self): + return "<'OpenPypeInterface.{}'>".format(self.__name__) + + def __repr__(self): + return str(self) + + +@six.add_metaclass(_OpenPypeInterfaceMeta) +class OpenPypeInterface: + """Base class of Interface that can be used as Mixin with abstract parts. + + This is way how OpenPype module or addon can tell OpenPype that contain + implementation for specific functionality. + + Child classes of OpenPypeInterface may be used as mixin in different + OpenPype modules which means they have to have implemented methods defined + in the interface. By default interface does not have any abstract parts. + """ + + pass class IPluginPaths(OpenPypeInterface): From 8cc6086e92a6c5135428898717e6d9057f567e8c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 25 Aug 2022 12:22:19 +0200 Subject: [PATCH 39/96] removed usage of 'ILaunchHookPaths' --- openpype/modules/ftrack/ftrack_module.py | 5 ++--- openpype/modules/shotgrid/shotgrid_module.py | 5 +---- openpype/modules/slack/slack_module.py | 10 ++++------ openpype/modules/timers_manager/timers_manager.py | 11 ++++------- 4 files changed, 11 insertions(+), 20 deletions(-) diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index f99e189082..cb4f204523 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -9,7 +9,6 @@ from openpype.modules import OpenPypeModule from openpype_interfaces import ( ITrayModule, IPluginPaths, - ILaunchHookPaths, ISettingsChangeListener ) from openpype.settings import SaveWarningExc @@ -21,7 +20,6 @@ class FtrackModule( OpenPypeModule, ITrayModule, IPluginPaths, - ILaunchHookPaths, ISettingsChangeListener ): name = "ftrack" @@ -85,7 +83,8 @@ class FtrackModule( } def get_launch_hook_paths(self): - """Implementation of `ILaunchHookPaths`.""" + """Implementation for applications launch hooks.""" + return os.path.join(FTRACK_MODULE_DIR, "launch_hooks") def modify_application_launch_arguments(self, application, env): diff --git a/openpype/modules/shotgrid/shotgrid_module.py b/openpype/modules/shotgrid/shotgrid_module.py index 5644f0c35f..281c6fdcad 100644 --- a/openpype/modules/shotgrid/shotgrid_module.py +++ b/openpype/modules/shotgrid/shotgrid_module.py @@ -3,7 +3,6 @@ import os from openpype_interfaces import ( ITrayModule, IPluginPaths, - ILaunchHookPaths, ) from openpype.modules import OpenPypeModule @@ -11,9 +10,7 @@ from openpype.modules import OpenPypeModule SHOTGRID_MODULE_DIR = os.path.dirname(os.path.abspath(__file__)) -class ShotgridModule( - OpenPypeModule, ITrayModule, IPluginPaths, ILaunchHookPaths -): +class ShotgridModule(OpenPypeModule, ITrayModule, IPluginPaths): leecher_manager_url = None name = "shotgrid" enabled = False diff --git a/openpype/modules/slack/slack_module.py b/openpype/modules/slack/slack_module.py index 9b2976d766..499c1c19ce 100644 --- a/openpype/modules/slack/slack_module.py +++ b/openpype/modules/slack/slack_module.py @@ -1,14 +1,11 @@ import os from openpype.modules import OpenPypeModule -from openpype_interfaces import ( - IPluginPaths, - ILaunchHookPaths -) +from openpype.modules.interfaces import IPluginPaths SLACK_MODULE_DIR = os.path.dirname(os.path.abspath(__file__)) -class SlackIntegrationModule(OpenPypeModule, IPluginPaths, ILaunchHookPaths): +class SlackIntegrationModule(OpenPypeModule, IPluginPaths): """Allows sending notification to Slack channels during publishing.""" name = "slack" @@ -18,7 +15,8 @@ class SlackIntegrationModule(OpenPypeModule, IPluginPaths, ILaunchHookPaths): self.enabled = slack_settings["enabled"] def get_launch_hook_paths(self): - """Implementation of `ILaunchHookPaths`.""" + """Implementation for applications launch hooks.""" + return os.path.join(SLACK_MODULE_DIR, "launch_hooks") def get_plugin_paths(self): diff --git a/openpype/modules/timers_manager/timers_manager.py b/openpype/modules/timers_manager/timers_manager.py index 93332ace4f..c168e9534d 100644 --- a/openpype/modules/timers_manager/timers_manager.py +++ b/openpype/modules/timers_manager/timers_manager.py @@ -6,7 +6,6 @@ from openpype.client import get_asset_by_name from openpype.modules import OpenPypeModule from openpype_interfaces import ( ITrayService, - ILaunchHookPaths, IPluginPaths ) from openpype.lib.events import register_event_callback @@ -79,7 +78,6 @@ class ExampleTimersManagerConnector: class TimersManager( OpenPypeModule, ITrayService, - ILaunchHookPaths, IPluginPaths ): """ Handles about Timers. @@ -185,12 +183,11 @@ class TimersManager( ) def get_launch_hook_paths(self): - """Implementation of `ILaunchHookPaths`.""" + """Implementation for applications launch hooks.""" - return os.path.join( - TIMER_MODULE_DIR, - "launch_hooks" - ) + return [ + os.path.join(TIMER_MODULE_DIR, "launch_hooks") + ] def get_plugin_paths(self): """Implementation of `IPluginPaths`.""" From 8acb96c572a58c779b544dac8b79aab9d71b6663 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 25 Aug 2022 12:27:02 +0200 Subject: [PATCH 40/96] added deprecation warning to 'ILaunchHookPaths' --- openpype/modules/interfaces.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/modules/interfaces.py b/openpype/modules/interfaces.py index 8221db4d05..13655773dd 100644 --- a/openpype/modules/interfaces.py +++ b/openpype/modules/interfaces.py @@ -81,6 +81,13 @@ class ILaunchHookPaths(OpenPypeInterface): Expected result is list of paths. ["path/to/launch_hooks_dir"] + + Deprecated: + This interface is not needed since OpenPype 3.14.*. Addon just have to + implement 'get_launch_hook_paths' which can expect Application object + or nothing as argument. + + Interface class will be removed after 3.16.*. """ @abstractmethod From 7356fc666d7aa4cca7251849f4eaf4d91dfc0e75 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 25 Aug 2022 12:31:47 +0200 Subject: [PATCH 41/96] moved collection of launch hooks from modules into applications logic --- openpype/lib/applications.py | 61 +++++++++++++++++++++++++++++++++-- openpype/modules/base.py | 62 ------------------------------------ 2 files changed, 58 insertions(+), 65 deletions(-) diff --git a/openpype/lib/applications.py b/openpype/lib/applications.py index 074e815160..b389bc2539 100644 --- a/openpype/lib/applications.py +++ b/openpype/lib/applications.py @@ -950,6 +950,63 @@ class ApplicationLaunchContext: ) self.kwargs["env"] = value + def _collect_addons_launch_hook_paths(self): + """Helper to collect application launch hooks from addons. + + Module have to have implemented 'get_launch_hook_paths' method which + can expect appliction as argument or nothing. + + Returns: + List[str]: Paths to launch hook directories. + """ + + expected_types = (list, tuple, set) + + output = [] + for module in self.modules_manager.get_enabled_modules(): + # Skip module if does not have implemented 'get_launch_hook_paths' + func = getattr(module, "get_launch_hook_paths", None) + if func is None: + continue + + func = module.get_launch_hook_paths + if hasattr(inspect, "signature"): + sig = inspect.signature(func) + expect_args = len(sig.parameters) > 0 + else: + expect_args = len(inspect.getargspec(func)[0]) > 0 + + # Pass application argument if method expect it. + try: + if expect_args: + hook_paths = func(self.application) + else: + hook_paths = func() + except Exception: + self.log.warning( + "Failed to call 'get_launch_hook_paths'", + exc_info=True + ) + continue + + if not hook_paths: + continue + + # Convert string to list + if isinstance(hook_paths, six.string_types): + hook_paths = [hook_paths] + + # Skip invalid types + if not isinstance(hook_paths, expected_types): + self.log.warning(( + "Result of `get_launch_hook_paths`" + " has invalid type {}. Expected {}" + ).format(type(hook_paths), expected_types)) + continue + + output.extend(hook_paths) + return output + def paths_to_launch_hooks(self): """Directory paths where to look for launch hooks.""" # This method has potential to be part of application manager (maybe). @@ -983,9 +1040,7 @@ class ApplicationLaunchContext: paths.append(path) # Load modules paths - paths.extend( - self.modules_manager.collect_launch_hook_paths(self.application) - ) + paths.extend(self._collect_addons_launch_hook_paths()) return paths diff --git a/openpype/modules/base.py b/openpype/modules/base.py index 1b8cf5d769..25355cbd9c 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -805,68 +805,6 @@ class ModulesManager: output.extend(paths) return output - def collect_launch_hook_paths(self, app): - """Helper to collect application launch hooks. - - It used to be based on 'ILaunchHookPaths' which is not true anymore. - Module just have to have implemented 'get_launch_hook_paths' method. - - Args: - app (Application): Application object which can be used for - filtering of which launch hook paths are returned. - - Returns: - list: Paths to launch hook directories. - """ - - str_type = type("") - expected_types = (list, tuple, set) - - output = [] - for module in self.get_enabled_modules(): - # Skip module if does not have implemented 'get_launch_hook_paths' - func = getattr(module, "get_launch_hook_paths", None) - if func is None: - continue - - func = module.get_launch_hook_paths - if hasattr(inspect, "signature"): - sig = inspect.signature(func) - expect_args = len(sig.parameters) > 0 - else: - expect_args = len(inspect.getargspec(func)[0]) > 0 - - # Pass application argument if method expect it. - try: - if expect_args: - hook_paths = func(app) - else: - hook_paths = func() - except Exception: - self.log.warning( - "Failed to call 'get_launch_hook_paths'", - exc_info=True - ) - continue - - if not hook_paths: - continue - - # Convert string to list - if isinstance(hook_paths, str_type): - hook_paths = [hook_paths] - - # Skip invalid types - if not isinstance(hook_paths, expected_types): - self.log.warning(( - "Result of `get_launch_hook_paths`" - " has invalid type {}. Expected {}" - ).format(type(hook_paths), expected_types)) - continue - - output.extend(hook_paths) - return output - def get_host_module(self, host_name): """Find host module by host name. From 9b7b217faafefb5bc32873337b41a3cce415c124 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 25 Aug 2022 16:02:56 +0200 Subject: [PATCH 42/96] 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 33661b665cd60b6c4bc0fef13788f40cd906f0c9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 25 Aug 2022 16:10:17 +0200 Subject: [PATCH 43/96] 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 808d1a5dd121d2f771a75d8ea4d061522ca42306 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 25 Aug 2022 18:36:11 +0200 Subject: [PATCH 44/96] abstrac provides has log attribute --- .../sync_server/providers/abstract_provider.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/openpype/modules/sync_server/providers/abstract_provider.py b/openpype/modules/sync_server/providers/abstract_provider.py index 9c808dc80e..e11a8ba71e 100644 --- a/openpype/modules/sync_server/providers/abstract_provider.py +++ b/openpype/modules/sync_server/providers/abstract_provider.py @@ -10,6 +10,8 @@ class AbstractProvider: CODE = '' LABEL = '' + _log = None + def __init__(self, project_name, site_name, tree=None, presets=None): self.presets = None self.active = False @@ -19,6 +21,12 @@ class AbstractProvider: super(AbstractProvider, self).__init__() + @property + def log(self): + if self._log is None: + self._log = Logger.get_logger(self.__class__.__name__) + return self._log + @abc.abstractmethod def is_active(self): """ @@ -199,11 +207,11 @@ class AbstractProvider: path = anatomy.fill_root(path) except KeyError: msg = "Error in resolving local root from anatomy" - log.error(msg) + self.log.error(msg) raise ValueError(msg) except IndexError: msg = "Path {} contains unfillable placeholder" - log.error(msg) + self.log.error(msg) raise ValueError(msg) return path From 5631fb66a79fe64c38073217aebe32b1a0fa5c60 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 25 Aug 2022 18:36:31 +0200 Subject: [PATCH 45/96] use log attribute in provides --- .../modules/sync_server/providers/dropbox.py | 17 +++--- .../modules/sync_server/providers/gdrive.py | 53 +++++++++++-------- .../modules/sync_server/providers/sftp.py | 15 +++--- 3 files changed, 45 insertions(+), 40 deletions(-) diff --git a/openpype/modules/sync_server/providers/dropbox.py b/openpype/modules/sync_server/providers/dropbox.py index 89d6990841..e026ae7ef6 100644 --- a/openpype/modules/sync_server/providers/dropbox.py +++ b/openpype/modules/sync_server/providers/dropbox.py @@ -2,12 +2,9 @@ import os import dropbox -from openpype.api import Logger from .abstract_provider import AbstractProvider from ..utils import EditableScopes -log = Logger().get_logger("SyncServer") - class DropboxHandler(AbstractProvider): CODE = 'dropbox' @@ -20,26 +17,26 @@ class DropboxHandler(AbstractProvider): self.dbx = None if not self.presets: - log.info( + self.log.info( "Sync Server: There are no presets for {}.".format(site_name) ) return if not self.presets["enabled"]: - log.debug("Sync Server: Site {} not enabled for {}.". + self.log.debug("Sync Server: Site {} not enabled for {}.". format(site_name, project_name)) return token = self.presets.get("token", "") if not token: msg = "Sync Server: No access token for dropbox provider" - log.info(msg) + self.log.info(msg) return team_folder_name = self.presets.get("team_folder_name", "") if not team_folder_name: msg = "Sync Server: No team folder name for dropbox provider" - log.info(msg) + self.log.info(msg) return acting_as_member = self.presets.get("acting_as_member", "") @@ -47,7 +44,7 @@ class DropboxHandler(AbstractProvider): msg = ( "Sync Server: No acting member for dropbox provider" ) - log.info(msg) + self.log.info(msg) return try: @@ -55,7 +52,7 @@ class DropboxHandler(AbstractProvider): token, acting_as_member, team_folder_name ) except Exception as e: - log.info("Could not establish dropbox object: {}".format(e)) + self.log.info("Could not establish dropbox object: {}".format(e)) return super(AbstractProvider, self).__init__() @@ -448,7 +445,7 @@ class DropboxHandler(AbstractProvider): path = anatomy.fill_root(path) except KeyError: msg = "Error in resolving local root from anatomy" - log.error(msg) + self.log.error(msg) raise ValueError(msg) return path diff --git a/openpype/modules/sync_server/providers/gdrive.py b/openpype/modules/sync_server/providers/gdrive.py index bef707788b..9a3ce89cf5 100644 --- a/openpype/modules/sync_server/providers/gdrive.py +++ b/openpype/modules/sync_server/providers/gdrive.py @@ -5,12 +5,12 @@ import sys import six import platform -from openpype.api import Logger -from openpype.api import get_system_settings +from openpype.lib import Logger +from openpype.settings import get_system_settings from .abstract_provider import AbstractProvider from ..utils import time_function, ResumableError -log = Logger().get_logger("SyncServer") +log = Logger.get_logger("GDriveHandler") try: from googleapiclient.discovery import build @@ -69,13 +69,17 @@ class GDriveHandler(AbstractProvider): self.presets = presets if not self.presets: - log.info("Sync Server: There are no presets for {}.". - format(site_name)) + self.log.info( + "Sync Server: There are no presets for {}.".format(site_name) + ) return if not self.presets["enabled"]: - log.debug("Sync Server: Site {} not enabled for {}.". - format(site_name, project_name)) + self.log.debug( + "Sync Server: Site {} not enabled for {}.".format( + site_name, project_name + ) + ) return current_platform = platform.system().lower() @@ -85,20 +89,22 @@ class GDriveHandler(AbstractProvider): if not cred_path: msg = "Sync Server: Please, fill the credentials for gdrive "\ "provider for platform '{}' !".format(current_platform) - log.info(msg) + self.log.info(msg) return try: cred_path = cred_path.format(**os.environ) except KeyError as e: - log.info("Sync Server: The key(s) {} does not exist in the " - "environment variables".format(" ".join(e.args))) + self.log.info(( + "Sync Server: The key(s) {} does not exist in the " + "environment variables" + ).format(" ".join(e.args))) return if not os.path.exists(cred_path): msg = "Sync Server: No credentials for gdrive provider " + \ "for '{}' on path '{}'!".format(site_name, cred_path) - log.info(msg) + self.log.info(msg) return self.service = None @@ -318,7 +324,7 @@ class GDriveHandler(AbstractProvider): fields='id') media.stream() - log.debug("Start Upload! {}".format(source_path)) + self.log.debug("Start Upload! {}".format(source_path)) last_tick = status = response = None status_val = 0 while response is None: @@ -331,7 +337,7 @@ class GDriveHandler(AbstractProvider): if not last_tick or \ time.time() - last_tick >= server.LOG_PROGRESS_SEC: last_tick = time.time() - log.debug("Uploaded %d%%." % + self.log.debug("Uploaded %d%%." % int(status_val * 100)) server.update_db(project_name=project_name, new_file_id=None, @@ -350,8 +356,9 @@ class GDriveHandler(AbstractProvider): if 'has not granted' in ex._get_reason().strip(): raise PermissionError(ex._get_reason().strip()) - log.warning("Forbidden received, hit quota. " - "Injecting 60s delay.") + self.log.warning( + "Forbidden received, hit quota. Injecting 60s delay." + ) time.sleep(60) return False raise @@ -417,7 +424,7 @@ class GDriveHandler(AbstractProvider): if not last_tick or \ time.time() - last_tick >= server.LOG_PROGRESS_SEC: last_tick = time.time() - log.debug("Downloaded %d%%." % + self.log.debug("Downloaded %d%%." % int(status_val * 100)) server.update_db(project_name=project_name, new_file_id=None, @@ -629,9 +636,9 @@ class GDriveHandler(AbstractProvider): ["gdrive"] ) except KeyError: - log.info(("Sync Server: There are no presets for Gdrive " + - "provider."). - format(str(provider_presets))) + log.info(( + "Sync Server: There are no presets for Gdrive provider." + ).format(str(provider_presets))) return return provider_presets @@ -704,7 +711,7 @@ class GDriveHandler(AbstractProvider): roots[self.MY_DRIVE_STR] = self.service.files() \ .get(fileId='root').execute() except errors.HttpError: - log.warning("HttpError in sync loop, " + self.log.warning("HttpError in sync loop, " "trying next loop", exc_info=True) raise ResumableError @@ -727,7 +734,7 @@ class GDriveHandler(AbstractProvider): Returns: (dictionary) path as a key, folder id as a value """ - log.debug("build_tree len {}".format(len(folders))) + self.log.debug("build_tree len {}".format(len(folders))) if not self.root: # build only when necessary, could be expensive self.root = self._prepare_root_info() @@ -779,9 +786,9 @@ class GDriveHandler(AbstractProvider): loop_cnt += 1 if len(no_parents_yet) > 0: - log.debug("Some folders path are not resolved {}". + self.log.debug("Some folders path are not resolved {}". format(no_parents_yet)) - log.debug("Remove deleted folders from trash.") + self.log.debug("Remove deleted folders from trash.") return tree diff --git a/openpype/modules/sync_server/providers/sftp.py b/openpype/modules/sync_server/providers/sftp.py index 302ffae3e6..40f11cb9dd 100644 --- a/openpype/modules/sync_server/providers/sftp.py +++ b/openpype/modules/sync_server/providers/sftp.py @@ -4,10 +4,10 @@ import time import threading import platform -from openpype.api import Logger -from openpype.api import get_system_settings +from openpype.lib import Logger +from openpype.settings import get_system_settings from .abstract_provider import AbstractProvider -log = Logger().get_logger("SyncServer") +log = Logger.get_logger("SyncServer-SFTPHandler") pysftp = None try: @@ -43,8 +43,9 @@ class SFTPHandler(AbstractProvider): self.presets = presets if not self.presets: - log.warning("Sync Server: There are no presets for {}.". - format(site_name)) + self.log.warning( + "Sync Server: There are no presets for {}.".format(site_name) + ) return # store to instance for reconnect @@ -423,7 +424,7 @@ class SFTPHandler(AbstractProvider): return pysftp.Connection(**conn_params) except (paramiko.ssh_exception.SSHException, pysftp.exceptions.ConnectionException): - log.warning("Couldn't connect", exc_info=True) + self.log.warning("Couldn't connect", exc_info=True) def _mark_progress(self, project_name, file, representation, server, site, source_path, target_path, direction): @@ -445,7 +446,7 @@ class SFTPHandler(AbstractProvider): time.time() - last_tick >= server.LOG_PROGRESS_SEC: status_val = target_file_size / source_file_size last_tick = time.time() - log.debug(direction + "ed %d%%." % int(status_val * 100)) + self.log.debug(direction + "ed %d%%." % int(status_val * 100)) server.update_db(project_name=project_name, new_file_id=None, file=file, From 54b8719b76c98b30d30e81b828e2dfb9ce13d0a8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 25 Aug 2022 18:49:04 +0200 Subject: [PATCH 46/96] fix attr initialization --- openpype/modules/timers_manager/rest_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/timers_manager/rest_api.py b/openpype/modules/timers_manager/rest_api.py index 6686407350..4a2e9e6575 100644 --- a/openpype/modules/timers_manager/rest_api.py +++ b/openpype/modules/timers_manager/rest_api.py @@ -10,7 +10,7 @@ class TimersManagerModuleRestApi: happens in Workfile app. """ def __init__(self, user_module, server_manager): - self.log = None + self._log = None self.module = user_module self.server_manager = server_manager From 59f36cc7c8ef54e3ac54d547e5f772bc726f3f1b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 25 Aug 2022 18:49:20 +0200 Subject: [PATCH 47/96] log traceback when webserver connection is not possible --- openpype/modules/webserver/webserver_module.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/openpype/modules/webserver/webserver_module.py b/openpype/modules/webserver/webserver_module.py index 686bd27bfd..16861abd29 100644 --- a/openpype/modules/webserver/webserver_module.py +++ b/openpype/modules/webserver/webserver_module.py @@ -53,9 +53,12 @@ class WebServerModule(OpenPypeModule, ITrayService): try: module.webserver_initialization(self.server_manager) except Exception: - self.log.warning(( - "Failed to connect module \"{}\" to webserver." - ).format(module.name)) + self.log.warning( + ( + "Failed to connect module \"{}\" to webserver." + ).format(module.name), + exc_info=True + ) def tray_init(self): self.create_server_manager() From 3ad9533fa82955301383c53e096d8fde2067c778 Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Thu, 25 Aug 2022 20:10:27 +0200 Subject: [PATCH 48/96] workfile template also matches against os.environ --- openpype/pipeline/workfile/path_resolving.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/pipeline/workfile/path_resolving.py b/openpype/pipeline/workfile/path_resolving.py index ed1d1d793e..4cd225a515 100644 --- a/openpype/pipeline/workfile/path_resolving.py +++ b/openpype/pipeline/workfile/path_resolving.py @@ -408,6 +408,9 @@ def get_custom_workfile_template( # add root dict anatomy_context_data["root"] = anatomy.roots + # extend anatomy context with os.environ + anatomy_context_data.update(os.environ) + # get task type for the task in context current_task_type = anatomy_context_data["task"]["type"] From 380965927ad4aa58672008588940c455f02d08cc Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Fri, 26 Aug 2022 12:13:29 +0200 Subject: [PATCH 49/96] reversed dict merging, anatomy has precedence. --- openpype/pipeline/workfile/path_resolving.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/pipeline/workfile/path_resolving.py b/openpype/pipeline/workfile/path_resolving.py index 4cd225a515..97e00d807c 100644 --- a/openpype/pipeline/workfile/path_resolving.py +++ b/openpype/pipeline/workfile/path_resolving.py @@ -409,10 +409,11 @@ def get_custom_workfile_template( anatomy_context_data["root"] = anatomy.roots # extend anatomy context with os.environ - anatomy_context_data.update(os.environ) + full_context_data = os.environ + full_context_data.update(anatomy_context_data) # get task type for the task in context - current_task_type = anatomy_context_data["task"]["type"] + current_task_type = full_context_data["task"]["type"] # get path from matching profile matching_item = filter_profiles( @@ -424,7 +425,7 @@ def get_custom_workfile_template( if matching_item: template = matching_item["path"][platform.system().lower()] return StringTemplate.format_strict_template( - template, anatomy_context_data + template, full_context_data ).normalized() return None From 2d9f2a6e767f340589c0f1955904a2b6762e178a Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Fri, 26 Aug 2022 14:46:03 +0200 Subject: [PATCH 50/96] os.environ is now a copy not an instance --- openpype/pipeline/workfile/path_resolving.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/workfile/path_resolving.py b/openpype/pipeline/workfile/path_resolving.py index 97e00d807c..4ab4a4936c 100644 --- a/openpype/pipeline/workfile/path_resolving.py +++ b/openpype/pipeline/workfile/path_resolving.py @@ -409,7 +409,7 @@ def get_custom_workfile_template( anatomy_context_data["root"] = anatomy.roots # extend anatomy context with os.environ - full_context_data = os.environ + full_context_data = os.environ.copy() full_context_data.update(anatomy_context_data) # get task type for the task in context From 2bfa9eea445a37e830db4dda036f5f8f18168573 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Aug 2022 15:06:50 +0200 Subject: [PATCH 51/96] renamed 'IHostModule' to 'IHostAddon' --- openpype/hosts/aftereffects/module.py | 4 ++-- openpype/hosts/blender/module.py | 4 ++-- openpype/hosts/harmony/addon.py | 4 ++-- openpype/hosts/hiero/module.py | 4 ++-- openpype/hosts/maya/module.py | 4 ++-- openpype/hosts/nuke/module.py | 4 ++-- openpype/hosts/photoshop/addon.py | 4 ++-- .../standalonepublisher/standalonepublish_module.py | 4 ++-- openpype/hosts/traypublisher/module.py | 4 ++-- openpype/hosts/tvpaint/tvpaint_module.py | 4 ++-- openpype/hosts/unreal/module.py | 4 ++-- openpype/hosts/webpublisher/addon.py | 4 ++-- openpype/modules/base.py | 10 +++++----- openpype/modules/interfaces.py | 4 ++-- 14 files changed, 31 insertions(+), 31 deletions(-) diff --git a/openpype/hosts/aftereffects/module.py b/openpype/hosts/aftereffects/module.py index 93d575c186..dff9634ecf 100644 --- a/openpype/hosts/aftereffects/module.py +++ b/openpype/hosts/aftereffects/module.py @@ -1,8 +1,8 @@ from openpype.modules import OpenPypeModule -from openpype.modules.interfaces import IHostModule +from openpype.modules.interfaces import IHostAddon -class AfterEffectsModule(OpenPypeModule, IHostModule): +class AfterEffectsModule(OpenPypeModule, IHostAddon): name = "aftereffects" host_name = "aftereffects" diff --git a/openpype/hosts/blender/module.py b/openpype/hosts/blender/module.py index d6ff3b111c..3db7973c17 100644 --- a/openpype/hosts/blender/module.py +++ b/openpype/hosts/blender/module.py @@ -1,11 +1,11 @@ import os from openpype.modules import OpenPypeModule -from openpype.modules.interfaces import IHostModule +from openpype.modules.interfaces import IHostAddon BLENDER_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -class BlenderModule(OpenPypeModule, IHostModule): +class BlenderModule(OpenPypeModule, IHostAddon): name = "blender" host_name = "blender" diff --git a/openpype/hosts/harmony/addon.py b/openpype/hosts/harmony/addon.py index b051d68abb..872a7490b5 100644 --- a/openpype/hosts/harmony/addon.py +++ b/openpype/hosts/harmony/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 HARMONY_HOST_DIR = os.path.dirname(os.path.abspath(__file__)) -class HarmonyAddon(OpenPypeModule, IHostModule): +class HarmonyAddon(OpenPypeModule, IHostAddon): name = "harmony" host_name = "harmony" diff --git a/openpype/hosts/hiero/module.py b/openpype/hosts/hiero/module.py index 375486e034..7883d2255f 100644 --- a/openpype/hosts/hiero/module.py +++ b/openpype/hosts/hiero/module.py @@ -1,12 +1,12 @@ import os import platform from openpype.modules import OpenPypeModule -from openpype.modules.interfaces import IHostModule +from openpype.modules.interfaces import IHostAddon HIERO_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -class HieroModule(OpenPypeModule, IHostModule): +class HieroModule(OpenPypeModule, IHostAddon): name = "hiero" host_name = "hiero" diff --git a/openpype/hosts/maya/module.py b/openpype/hosts/maya/module.py index 5a215be8d2..674b36b250 100644 --- a/openpype/hosts/maya/module.py +++ b/openpype/hosts/maya/module.py @@ -1,11 +1,11 @@ import os from openpype.modules import OpenPypeModule -from openpype.modules.interfaces import IHostModule +from openpype.modules.interfaces import IHostAddon MAYA_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -class OpenPypeMaya(OpenPypeModule, IHostModule): +class OpenPypeMaya(OpenPypeModule, IHostAddon): name = "openpype_maya" host_name = "maya" diff --git a/openpype/hosts/nuke/module.py b/openpype/hosts/nuke/module.py index e4706a36cb..444aa75ff2 100644 --- a/openpype/hosts/nuke/module.py +++ b/openpype/hosts/nuke/module.py @@ -1,12 +1,12 @@ import os import platform from openpype.modules import OpenPypeModule -from openpype.modules.interfaces import IHostModule +from openpype.modules.interfaces import IHostAddon NUKE_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -class NukeModule(OpenPypeModule, IHostModule): +class NukeModule(OpenPypeModule, IHostAddon): name = "nuke" host_name = "nuke" diff --git a/openpype/hosts/photoshop/addon.py b/openpype/hosts/photoshop/addon.py index 18899d4de8..a41d91554b 100644 --- a/openpype/hosts/photoshop/addon.py +++ b/openpype/hosts/photoshop/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 PHOTOSHOP_HOST_DIR = os.path.dirname(os.path.abspath(__file__)) -class PhotoshopAddon(OpenPypeModule, IHostModule): +class PhotoshopAddon(OpenPypeModule, IHostAddon): name = "photoshop" host_name = "photoshop" diff --git a/openpype/hosts/standalonepublisher/standalonepublish_module.py b/openpype/hosts/standalonepublisher/standalonepublish_module.py index bf8e1d2c23..21b47beb54 100644 --- a/openpype/hosts/standalonepublisher/standalonepublish_module.py +++ b/openpype/hosts/standalonepublisher/standalonepublish_module.py @@ -5,12 +5,12 @@ import click from openpype.lib import get_openpype_execute_args from openpype.lib.execute import run_detached_process from openpype.modules import OpenPypeModule -from openpype.modules.interfaces import ITrayAction, IHostModule +from openpype.modules.interfaces import ITrayAction, IHostAddon STANDALONEPUBLISH_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -class StandAlonePublishModule(OpenPypeModule, ITrayAction, IHostModule): +class StandAlonePublishModule(OpenPypeModule, ITrayAction, IHostAddon): label = "Publish" name = "standalonepublish_tool" host_name = "standalonepublisher" diff --git a/openpype/hosts/traypublisher/module.py b/openpype/hosts/traypublisher/module.py index 92a2312fec..c35ce2093a 100644 --- a/openpype/hosts/traypublisher/module.py +++ b/openpype/hosts/traypublisher/module.py @@ -5,12 +5,12 @@ import click from openpype.lib import get_openpype_execute_args from openpype.lib.execute import run_detached_process from openpype.modules import OpenPypeModule -from openpype.modules.interfaces import ITrayAction, IHostModule +from openpype.modules.interfaces import ITrayAction, IHostAddon TRAYPUBLISH_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -class TrayPublishModule(OpenPypeModule, IHostModule, ITrayAction): +class TrayPublishModule(OpenPypeModule, IHostAddon, ITrayAction): label = "New Publish (beta)" name = "traypublish_tool" host_name = "traypublish" diff --git a/openpype/hosts/tvpaint/tvpaint_module.py b/openpype/hosts/tvpaint/tvpaint_module.py index a004359231..4b30ce667c 100644 --- a/openpype/hosts/tvpaint/tvpaint_module.py +++ b/openpype/hosts/tvpaint/tvpaint_module.py @@ -1,6 +1,6 @@ import os from openpype.modules import OpenPypeModule -from openpype.modules.interfaces import IHostModule +from openpype.modules.interfaces import IHostAddon TVPAINT_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -13,7 +13,7 @@ def get_launch_script_path(): ) -class TVPaintModule(OpenPypeModule, IHostModule): +class TVPaintModule(OpenPypeModule, IHostAddon): name = "tvpaint" host_name = "tvpaint" diff --git a/openpype/hosts/unreal/module.py b/openpype/hosts/unreal/module.py index aa08c8c130..99c8851e8e 100644 --- a/openpype/hosts/unreal/module.py +++ b/openpype/hosts/unreal/module.py @@ -1,11 +1,11 @@ import os from openpype.modules import OpenPypeModule -from openpype.modules.interfaces import IHostModule +from openpype.modules.interfaces import IHostAddon UNREAL_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -class UnrealModule(OpenPypeModule, IHostModule): +class UnrealModule(OpenPypeModule, IHostAddon): name = "unreal" host_name = "unreal" diff --git a/openpype/hosts/webpublisher/addon.py b/openpype/hosts/webpublisher/addon.py index 7d26d5a7ff..a64d74e62b 100644 --- a/openpype/hosts/webpublisher/addon.py +++ b/openpype/hosts/webpublisher/addon.py @@ -3,12 +3,12 @@ import os import click from openpype.modules import OpenPypeModule -from openpype.modules.interfaces import IHostModule +from openpype.modules.interfaces import IHostAddon WEBPUBLISHER_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -class WebpublisherAddon(OpenPypeModule, IHostModule): +class WebpublisherAddon(OpenPypeModule, IHostAddon): name = "webpublisher" host_name = "webpublisher" diff --git a/openpype/modules/base.py b/openpype/modules/base.py index 6db6ee9524..c96ca02ab7 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -35,7 +35,7 @@ from openpype.lib import ( from .interfaces import ( OpenPypeInterface, IPluginPaths, - IHostModule, + IHostAddon, ITrayModule, ITrayService ) @@ -811,13 +811,13 @@ class ModulesManager: Returns: OpenPypeModule: Found host module by name. - None: There was not found module inheriting IHostModule which has + None: There was not found module inheriting IHostAddon which has host name set to passed 'host_name'. """ for module in self.get_enabled_modules(): if ( - isinstance(module, IHostModule) + isinstance(module, IHostAddon) and module.host_name == host_name ): return module @@ -828,13 +828,13 @@ class ModulesManager: Returns: Iterable[str]: All available host names based on enabled modules - inheriting 'IHostModule'. + inheriting 'IHostAddon'. """ host_names = { module.host_name for module in self.get_enabled_modules() - if isinstance(module, IHostModule) + if isinstance(module, IHostAddon) } return host_names diff --git a/openpype/modules/interfaces.py b/openpype/modules/interfaces.py index 13655773dd..f92ec6bf2d 100644 --- a/openpype/modules/interfaces.py +++ b/openpype/modules/interfaces.py @@ -385,8 +385,8 @@ class ISettingsChangeListener(OpenPypeInterface): pass -class IHostModule(OpenPypeInterface): - """Module which also contain a host implementation.""" +class IHostAddon(OpenPypeInterface): + """Addon which also contain a host implementation.""" @abstractproperty def host_name(self): From 2c81bb5788db784073eec6a61755c288f4dd41d6 Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Fri, 26 Aug 2022 15:09:47 +0200 Subject: [PATCH 52/96] moved env logic inside matching check --- openpype/pipeline/workfile/path_resolving.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/pipeline/workfile/path_resolving.py b/openpype/pipeline/workfile/path_resolving.py index 4ab4a4936c..6d9e72dbd2 100644 --- a/openpype/pipeline/workfile/path_resolving.py +++ b/openpype/pipeline/workfile/path_resolving.py @@ -408,12 +408,8 @@ def get_custom_workfile_template( # add root dict anatomy_context_data["root"] = anatomy.roots - # extend anatomy context with os.environ - full_context_data = os.environ.copy() - full_context_data.update(anatomy_context_data) - # get task type for the task in context - current_task_type = full_context_data["task"]["type"] + current_task_type = anatomy_context_data["task"]["type"] # get path from matching profile matching_item = filter_profiles( @@ -423,6 +419,11 @@ def get_custom_workfile_template( # when path is available try to format it in case # there are some anatomy template strings if matching_item: + # extend anatomy context with os.environ to + # also allow formatting against env + full_context_data = os.environ.copy() + full_context_data.update(anatomy_context_data) + template = matching_item["path"][platform.system().lower()] return StringTemplate.format_strict_template( template, full_context_data From 0212f6fc06554945be11ecb02810f93c4103b620 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Aug 2022 15:11:14 +0200 Subject: [PATCH 53/96] renamed module.py to addon.py in unreal --- openpype/hosts/unreal/__init__.py | 4 ++-- openpype/hosts/unreal/{module.py => addon.py} | 4 ++-- openpype/hosts/unreal/lib.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) rename openpype/hosts/unreal/{module.py => addon.py} (92%) diff --git a/openpype/hosts/unreal/__init__.py b/openpype/hosts/unreal/__init__.py index 41222f4f94..42dd8f0ac4 100644 --- a/openpype/hosts/unreal/__init__.py +++ b/openpype/hosts/unreal/__init__.py @@ -1,6 +1,6 @@ -from .module import UnrealModule +from .addon import UnrealAddon __all__ = ( - "UnrealModule", + "UnrealAddon", ) diff --git a/openpype/hosts/unreal/module.py b/openpype/hosts/unreal/addon.py similarity index 92% rename from openpype/hosts/unreal/module.py rename to openpype/hosts/unreal/addon.py index 99c8851e8e..16736214c5 100644 --- a/openpype/hosts/unreal/module.py +++ b/openpype/hosts/unreal/addon.py @@ -5,14 +5,14 @@ from openpype.modules.interfaces import IHostAddon UNREAL_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -class UnrealModule(OpenPypeModule, IHostAddon): +class UnrealAddon(OpenPypeModule, IHostAddon): name = "unreal" host_name = "unreal" def initialize(self, module_settings): self.enabled = True - def add_implementation_envs(self, env, app) -> None: + def add_implementation_envs(self, env, app): """Modify environments to contain all required for implementation.""" # Set OPENPYPE_UNREAL_PLUGIN required for Unreal implementation diff --git a/openpype/hosts/unreal/lib.py b/openpype/hosts/unreal/lib.py index 8c453b38b9..d02c6de357 100644 --- a/openpype/hosts/unreal/lib.py +++ b/openpype/hosts/unreal/lib.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """Unreal launching and project tools.""" -import sys + import os import platform import json @@ -9,7 +9,7 @@ import subprocess import re from pathlib import Path from collections import OrderedDict -from openpype.api import get_project_settings +from openpype.settings import get_project_settings def get_engine_versions(env=None): From f0ddc5b746cfb9be546860cc99d1121bf9f21b61 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Aug 2022 15:11:22 +0200 Subject: [PATCH 54/96] renamed 'tvpaint_module.py' to 'addon.py' --- openpype/hosts/tvpaint/__init__.py | 6 +++--- openpype/hosts/tvpaint/{tvpaint_module.py => addon.py} | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) rename openpype/hosts/tvpaint/{tvpaint_module.py => addon.py} (95%) diff --git a/openpype/hosts/tvpaint/__init__.py b/openpype/hosts/tvpaint/__init__.py index 0a84b575dc..b98680f204 100644 --- a/openpype/hosts/tvpaint/__init__.py +++ b/openpype/hosts/tvpaint/__init__.py @@ -1,12 +1,12 @@ -from .tvpaint_module import ( +from .addon import ( get_launch_script_path, - TVPaintModule, + TVPaintAddon, TVPAINT_ROOT_DIR, ) __all__ = ( "get_launch_script_path", - "TVPaintModule", + "TVPaintAddon", "TVPAINT_ROOT_DIR", ) diff --git a/openpype/hosts/tvpaint/tvpaint_module.py b/openpype/hosts/tvpaint/addon.py similarity index 95% rename from openpype/hosts/tvpaint/tvpaint_module.py rename to openpype/hosts/tvpaint/addon.py index 4b30ce667c..d710e63f93 100644 --- a/openpype/hosts/tvpaint/tvpaint_module.py +++ b/openpype/hosts/tvpaint/addon.py @@ -13,7 +13,7 @@ def get_launch_script_path(): ) -class TVPaintModule(OpenPypeModule, IHostAddon): +class TVPaintAddon(OpenPypeModule, IHostAddon): name = "tvpaint" host_name = "tvpaint" From 3e912a88f6367f7488f90648b223abcd501e8d86 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Aug 2022 15:12:19 +0200 Subject: [PATCH 55/96] renamed module.py to addon.py, changed addon name and host name --- openpype/hosts/traypublisher/__init__.py | 4 ++-- openpype/hosts/traypublisher/{module.py => addon.py} | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) rename openpype/hosts/traypublisher/{module.py => addon.py} (86%) diff --git a/openpype/hosts/traypublisher/__init__.py b/openpype/hosts/traypublisher/__init__.py index 4eb7bf3eef..77ba908ddd 100644 --- a/openpype/hosts/traypublisher/__init__.py +++ b/openpype/hosts/traypublisher/__init__.py @@ -1,6 +1,6 @@ -from .module import TrayPublishModule +from .addon import TrayPublishAddon __all__ = ( - "TrayPublishModule", + "TrayPublishAddon", ) diff --git a/openpype/hosts/traypublisher/module.py b/openpype/hosts/traypublisher/addon.py similarity index 86% rename from openpype/hosts/traypublisher/module.py rename to openpype/hosts/traypublisher/addon.py index c35ce2093a..c86c835ed9 100644 --- a/openpype/hosts/traypublisher/module.py +++ b/openpype/hosts/traypublisher/addon.py @@ -10,10 +10,10 @@ from openpype.modules.interfaces import ITrayAction, IHostAddon TRAYPUBLISH_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -class TrayPublishModule(OpenPypeModule, IHostAddon, ITrayAction): +class TrayPublishAddon(OpenPypeModule, IHostAddon, ITrayAction): label = "New Publish (beta)" - name = "traypublish_tool" - host_name = "traypublish" + name = "traypublisher" + host_name = "traypublisher" def initialize(self, modules_settings): self.enabled = True @@ -28,7 +28,7 @@ class TrayPublishModule(OpenPypeModule, IHostAddon, ITrayAction): self._experimental_tools = ExperimentalTools() def tray_menu(self, *args, **kwargs): - super(TrayPublishModule, self).tray_menu(*args, **kwargs) + super(TrayPublishAddon, self).tray_menu(*args, **kwargs) traypublisher = self._experimental_tools.get("traypublisher") visible = False if traypublisher and traypublisher.enabled: @@ -53,7 +53,7 @@ class TrayPublishModule(OpenPypeModule, IHostAddon, ITrayAction): click_group.add_command(cli_main) -@click.group(TrayPublishModule.name, help="TrayPublisher related commands.") +@click.group(TrayPublishAddon.name, help="TrayPublisher related commands.") def cli_main(): pass From 4a35b4bea7610bedfd780882bde2a0216a3bde76 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Aug 2022 15:13:16 +0200 Subject: [PATCH 56/96] renamed standalonepublisher to addon --- openpype/hosts/standalonepublisher/__init__.py | 4 ++-- .../{standalonepublish_module.py => addon.py} | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) rename openpype/hosts/standalonepublisher/{standalonepublish_module.py => addon.py} (90%) diff --git a/openpype/hosts/standalonepublisher/__init__.py b/openpype/hosts/standalonepublisher/__init__.py index 394d5be397..f47fa6b573 100644 --- a/openpype/hosts/standalonepublisher/__init__.py +++ b/openpype/hosts/standalonepublisher/__init__.py @@ -1,6 +1,6 @@ -from .standalonepublish_module import StandAlonePublishModule +from .addon import StandAlonePublishAddon __all__ = ( - "StandAlonePublishModule", + "StandAlonePublishAddon", ) diff --git a/openpype/hosts/standalonepublisher/standalonepublish_module.py b/openpype/hosts/standalonepublisher/addon.py similarity index 90% rename from openpype/hosts/standalonepublisher/standalonepublish_module.py rename to openpype/hosts/standalonepublisher/addon.py index 21b47beb54..40a156ee70 100644 --- a/openpype/hosts/standalonepublisher/standalonepublish_module.py +++ b/openpype/hosts/standalonepublisher/addon.py @@ -10,9 +10,9 @@ from openpype.modules.interfaces import ITrayAction, IHostAddon STANDALONEPUBLISH_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -class StandAlonePublishModule(OpenPypeModule, ITrayAction, IHostAddon): +class StandAlonePublishAddon(OpenPypeModule, ITrayAction, IHostAddon): label = "Publish" - name = "standalonepublish_tool" + name = "standalonepublisher" host_name = "standalonepublisher" def initialize(self, modules_settings): @@ -42,7 +42,7 @@ class StandAlonePublishModule(OpenPypeModule, ITrayAction, IHostAddon): @click.group( - StandAlonePublishModule.name, + StandAlonePublishAddon.name, help="StandalonePublisher related commands.") def cli_main(): pass From 7af8e8998465b33841440677a16bfe9ef870e9dc Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Aug 2022 15:14:05 +0200 Subject: [PATCH 57/96] renamed maya to addon --- openpype/hosts/maya/__init__.py | 4 ++-- openpype/hosts/maya/{module.py => addon.py} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename openpype/hosts/maya/{module.py => addon.py} (94%) diff --git a/openpype/hosts/maya/__init__.py b/openpype/hosts/maya/__init__.py index 72b4d5853c..860db766f3 100644 --- a/openpype/hosts/maya/__init__.py +++ b/openpype/hosts/maya/__init__.py @@ -1,6 +1,6 @@ -from .module import OpenPypeMaya +from .addon import MayaAddon __all__ = ( - "OpenPypeMaya", + "MayaAddon", ) diff --git a/openpype/hosts/maya/module.py b/openpype/hosts/maya/addon.py similarity index 94% rename from openpype/hosts/maya/module.py rename to openpype/hosts/maya/addon.py index 674b36b250..7b1f7bf754 100644 --- a/openpype/hosts/maya/module.py +++ b/openpype/hosts/maya/addon.py @@ -5,8 +5,8 @@ from openpype.modules.interfaces import IHostAddon MAYA_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -class OpenPypeMaya(OpenPypeModule, IHostAddon): - name = "openpype_maya" +class MayaAddon(OpenPypeModule, IHostAddon): + name = "maya" host_name = "maya" def initialize(self, module_settings): From 621b2dbe882ea97c55d1910735a27e508fab303a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Aug 2022 15:14:33 +0200 Subject: [PATCH 58/96] renamed hiero to addon --- openpype/hosts/hiero/__init__.py | 6 +++--- openpype/hosts/hiero/{module.py => addon.py} | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) rename openpype/hosts/hiero/{module.py => addon.py} (97%) diff --git a/openpype/hosts/hiero/__init__.py b/openpype/hosts/hiero/__init__.py index a307e265d5..e6744d5aec 100644 --- a/openpype/hosts/hiero/__init__.py +++ b/openpype/hosts/hiero/__init__.py @@ -1,10 +1,10 @@ -from .module import ( +from .addon import ( HIERO_ROOT_DIR, - HieroModule, + HieroAddon, ) __all__ = ( "HIERO_ROOT_DIR", - "HieroModule", + "HieroAddon", ) diff --git a/openpype/hosts/hiero/module.py b/openpype/hosts/hiero/addon.py similarity index 97% rename from openpype/hosts/hiero/module.py rename to openpype/hosts/hiero/addon.py index 7883d2255f..3523e9aed7 100644 --- a/openpype/hosts/hiero/module.py +++ b/openpype/hosts/hiero/addon.py @@ -6,7 +6,7 @@ from openpype.modules.interfaces import IHostAddon HIERO_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -class HieroModule(OpenPypeModule, IHostAddon): +class HieroAddon(OpenPypeModule, IHostAddon): name = "hiero" host_name = "hiero" From a991d4b6fe008db77ff4b83c18a14a7f9f95a07f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Aug 2022 15:15:14 +0200 Subject: [PATCH 59/96] renamed blender to addon --- openpype/hosts/blender/__init__.py | 4 ++-- openpype/hosts/blender/{module.py => addon.py} | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename openpype/hosts/blender/{module.py => addon.py} (98%) diff --git a/openpype/hosts/blender/__init__.py b/openpype/hosts/blender/__init__.py index 58d7ac656f..2a6603606a 100644 --- a/openpype/hosts/blender/__init__.py +++ b/openpype/hosts/blender/__init__.py @@ -1,6 +1,6 @@ -from .module import BlenderModule +from .addon import BlenderAddon __all__ = ( - "BlenderModule", + "BlenderAddon", ) diff --git a/openpype/hosts/blender/module.py b/openpype/hosts/blender/addon.py similarity index 98% rename from openpype/hosts/blender/module.py rename to openpype/hosts/blender/addon.py index 3db7973c17..3ee638a5bb 100644 --- a/openpype/hosts/blender/module.py +++ b/openpype/hosts/blender/addon.py @@ -5,7 +5,7 @@ from openpype.modules.interfaces import IHostAddon BLENDER_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -class BlenderModule(OpenPypeModule, IHostAddon): +class BlenderAddon(OpenPypeModule, IHostAddon): name = "blender" host_name = "blender" From 3d7b2179c855d466bebb580cb8082084bb2ab44b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Aug 2022 15:15:42 +0200 Subject: [PATCH 60/96] renamed aftereffects to addon --- openpype/hosts/aftereffects/__init__.py | 4 ++-- openpype/hosts/aftereffects/{module.py => addon.py} | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename openpype/hosts/aftereffects/{module.py => addon.py} (92%) diff --git a/openpype/hosts/aftereffects/__init__.py b/openpype/hosts/aftereffects/__init__.py index c9ad6aaeeb..ae750d05b6 100644 --- a/openpype/hosts/aftereffects/__init__.py +++ b/openpype/hosts/aftereffects/__init__.py @@ -1,6 +1,6 @@ -from .module import AfterEffectsModule +from .addon import AfterEffectsAddon __all__ = ( - "AfterEffectsModule", + "AfterEffectsAddon", ) diff --git a/openpype/hosts/aftereffects/module.py b/openpype/hosts/aftereffects/addon.py similarity index 92% rename from openpype/hosts/aftereffects/module.py rename to openpype/hosts/aftereffects/addon.py index dff9634ecf..94843e7dc5 100644 --- a/openpype/hosts/aftereffects/module.py +++ b/openpype/hosts/aftereffects/addon.py @@ -2,7 +2,7 @@ from openpype.modules import OpenPypeModule from openpype.modules.interfaces import IHostAddon -class AfterEffectsModule(OpenPypeModule, IHostAddon): +class AfterEffectsAddon(OpenPypeModule, IHostAddon): name = "aftereffects" host_name = "aftereffects" From 511bf71f61426a4794ca9bc9890b49d82ce7917e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Aug 2022 15:17:28 +0200 Subject: [PATCH 61/96] fix standalone publisher settings --- openpype/hosts/standalonepublisher/addon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/standalonepublisher/addon.py b/openpype/hosts/standalonepublisher/addon.py index 40a156ee70..98ec44d4e2 100644 --- a/openpype/hosts/standalonepublisher/addon.py +++ b/openpype/hosts/standalonepublisher/addon.py @@ -16,7 +16,7 @@ class StandAlonePublishAddon(OpenPypeModule, ITrayAction, IHostAddon): host_name = "standalonepublisher" def initialize(self, modules_settings): - self.enabled = modules_settings[self.name]["enabled"] + self.enabled = modules_settings["standalonepublish_tool"]["enabled"] self.publish_paths = [ os.path.join(STANDALONEPUBLISH_ROOT_DIR, "plugins", "publish") ] From 310bc6b70523e1e24b9060d5b3163678dbca3417 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Aug 2022 15:17:51 +0200 Subject: [PATCH 62/96] renamed nuke to addon --- openpype/hosts/nuke/__init__.py | 6 +++--- openpype/hosts/nuke/{module.py => addon.py} | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) rename openpype/hosts/nuke/{module.py => addon.py} (97%) diff --git a/openpype/hosts/nuke/__init__.py b/openpype/hosts/nuke/__init__.py index 718307583e..8ab565939b 100644 --- a/openpype/hosts/nuke/__init__.py +++ b/openpype/hosts/nuke/__init__.py @@ -1,10 +1,10 @@ -from .module import ( +from .addon import ( NUKE_ROOT_DIR, - NukeModule, + NukeAddon, ) __all__ = ( "NUKE_ROOT_DIR", - "NukeModule", + "NukeAddon", ) diff --git a/openpype/hosts/nuke/module.py b/openpype/hosts/nuke/addon.py similarity index 97% rename from openpype/hosts/nuke/module.py rename to openpype/hosts/nuke/addon.py index 444aa75ff2..54e4da5195 100644 --- a/openpype/hosts/nuke/module.py +++ b/openpype/hosts/nuke/addon.py @@ -6,7 +6,7 @@ from openpype.modules.interfaces import IHostAddon NUKE_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -class NukeModule(OpenPypeModule, IHostAddon): +class NukeAddon(OpenPypeModule, IHostAddon): name = "nuke" host_name = "nuke" From 6801a719d33378e1729cc9022d6e924e7778b924 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Aug 2022 16:06:59 +0200 Subject: [PATCH 63/96] added method to get last available variant in application manager --- openpype/lib/applications.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/openpype/lib/applications.py b/openpype/lib/applications.py index eaa4c1a0a8..e249ae4f1c 100644 --- a/openpype/lib/applications.py +++ b/openpype/lib/applications.py @@ -469,6 +469,19 @@ class ApplicationManager: for tool in group: self.tools[tool.full_name] = tool + def find_latest_available_variant_for_group(self, group_name): + group = self.app_groups.get(group_name) + if group is None or not group.enabled: + return None + + output = None + for _, variant in reversed(sorted(group.variants.items())): + executable = variant.find_executable() + if executable: + output = variant + break + return output + def launch(self, app_name, **data): """Launch procedure. From 5736fa2382e7d2cb63e3a007c3b04bc2805243bf Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Aug 2022 16:07:16 +0200 Subject: [PATCH 64/96] fix testing classes --- tests/lib/testing_classes.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/lib/testing_classes.py b/tests/lib/testing_classes.py index 2b4d7deb48..64676f62f4 100644 --- a/tests/lib/testing_classes.py +++ b/tests/lib/testing_classes.py @@ -12,8 +12,6 @@ import platform from tests.lib.db_handler import DBHandler from tests.lib.file_handler import RemoteFileHandler -from openpype.lib.remote_publish import find_variant_key - class BaseTest: """Empty base test class""" @@ -210,7 +208,10 @@ class PublishTest(ModuleUnitTest): application_manager = ApplicationManager() if not app_variant: - app_variant = find_variant_key(application_manager, self.APP) + variant = ( + application_manager.find_latest_available_variant_for_group( + self.APP)) + app_variant = variant.name yield "{}/{}".format(self.APP, app_variant) @@ -342,4 +343,4 @@ class HostFixtures(PublishTest): @pytest.fixture(scope="module") def startup_scripts(self, monkeypatch_session, download_test_data): """"Adds init scripts (like userSetup) to expected location""" - raise NotImplementedError \ No newline at end of file + raise NotImplementedError From a0c3eb6f809e6aa6c7ee0c43f330a67e2466594b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Aug 2022 16:32:34 +0200 Subject: [PATCH 65/96] removed unnecessary lines --- openpype/modules/base.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index c96ca02ab7..09aea50424 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -397,9 +397,6 @@ def _load_modules(): log.error(msg, exc_info=True) - - - @six.add_metaclass(ABCMeta) class OpenPypeModule: """Base class of pype module. From 4fda8d6ff2602421bcc2eacd74d4ff8b50525d4e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Aug 2022 16:34:36 +0200 Subject: [PATCH 66/96] fix addon name --- openpype/tools/standalonepublish/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/standalonepublish/app.py b/openpype/tools/standalonepublish/app.py index 3ceeb3ad48..081235c91c 100644 --- a/openpype/tools/standalonepublish/app.py +++ b/openpype/tools/standalonepublish/app.py @@ -236,7 +236,7 @@ def main(): signal.signal(signal.SIGTERM, signal_handler) modules_manager = ModulesManager() - module = modules_manager.modules_by_name["standalonepublish_tool"] + module = modules_manager.modules_by_name["standalonepublisher"] window = Window(module.publish_paths) window.show() From 55849039b554bfab52eefaa81906dba9a6d02d06 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sat, 27 Aug 2022 04:12:30 +0000 Subject: [PATCH 67/96] [Automated] Bump version --- CHANGELOG.md | 55 ++++++++++++++++++++++++--------------------- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a45f65b6f7..2a8e962085 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [3.14.1-nightly.2](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.14.1-nightly.3](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.0...HEAD) @@ -9,22 +9,49 @@ - 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) + **🚀 Enhancements** +- 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** +- General: Logger tweaks [\#3741](https://github.com/pypeclub/OpenPype/pull/3741) +- 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: 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) +- AfterEffects: Move AE functions from general lib [\#3730](https://github.com/pypeclub/OpenPype/pull/3730) +- Blender: Define blender as module [\#3729](https://github.com/pypeclub/OpenPype/pull/3729) +- AfterEffects: Define AfterEffects as module [\#3728](https://github.com/pypeclub/OpenPype/pull/3728) +- General: Replace PypeLogger with Logger [\#3725](https://github.com/pypeclub/OpenPype/pull/3725) +- Nuke: Define nuke as module [\#3724](https://github.com/pypeclub/OpenPype/pull/3724) +- General: Move subset name functionality [\#3723](https://github.com/pypeclub/OpenPype/pull/3723) +- General: Move creators plugin getter [\#3714](https://github.com/pypeclub/OpenPype/pull/3714) +- General: Move constants from lib to client [\#3713](https://github.com/pypeclub/OpenPype/pull/3713) - Loader: Subset groups using client operations [\#3710](https://github.com/pypeclub/OpenPype/pull/3710) - TVPaint: Defined as module [\#3707](https://github.com/pypeclub/OpenPype/pull/3707) - StandalonePublisher: Define StandalonePublisher as module [\#3706](https://github.com/pypeclub/OpenPype/pull/3706) @@ -33,6 +60,7 @@ **Merged pull requests:** +- 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) @@ -40,10 +68,6 @@ [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.14.0-nightly.1...3.14.0) -**🆕 New features** - -- Maya: Build workfile by template [\#3578](https://github.com/pypeclub/OpenPype/pull/3578) - **🚀 Enhancements** - Ftrack: Addiotional component metadata [\#3685](https://github.com/pypeclub/OpenPype/pull/3685) @@ -69,7 +93,6 @@ - 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) -- General: Workfiles builder using query functions [\#3598](https://github.com/pypeclub/OpenPype/pull/3598) **Merged pull requests:** @@ -91,12 +114,6 @@ - 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) -- General: Add context to pyblish context [\#3594](https://github.com/pypeclub/OpenPype/pull/3594) -- Kitsu: Shot&Sequence name with prefix over appends [\#3593](https://github.com/pypeclub/OpenPype/pull/3593) -- Photoshop: implemented {layer} placeholder in subset template [\#3591](https://github.com/pypeclub/OpenPype/pull/3591) -- General: Python module appdirs from git [\#3589](https://github.com/pypeclub/OpenPype/pull/3589) -- Ftrack: Update ftrack api to 2.3.3 [\#3588](https://github.com/pypeclub/OpenPype/pull/3588) -- General: New Integrator small fixes [\#3583](https://github.com/pypeclub/OpenPype/pull/3583) **🐛 Bug fixes** @@ -109,35 +126,21 @@ - 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) -- Bugfix: Add OCIO as submodule to prepare for handling `maketx` color space conversion. [\#3590](https://github.com/pypeclub/OpenPype/pull/3590) -- Fix general settings environment variables resolution [\#3587](https://github.com/pypeclub/OpenPype/pull/3587) -- Editorial publishing workflow improvements [\#3580](https://github.com/pypeclub/OpenPype/pull/3580) -- General: Update imports in start script [\#3579](https://github.com/pypeclub/OpenPype/pull/3579) -- Nuke: render family integration consistency [\#3576](https://github.com/pypeclub/OpenPype/pull/3576) -- Ftrack: Handle missing published path in integrator [\#3570](https://github.com/pypeclub/OpenPype/pull/3570) **🔀 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) -- General: Use query functions in general code [\#3596](https://github.com/pypeclub/OpenPype/pull/3596) -- General: Separate extraction of template data into more functions [\#3574](https://github.com/pypeclub/OpenPype/pull/3574) -- General: Lib cleanup [\#3571](https://github.com/pypeclub/OpenPype/pull/3571) **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) -- Enable write color sets on animation publish automatically [\#3582](https://github.com/pypeclub/OpenPype/pull/3582) ## [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) -**🐛 Bug fixes** - -- Maya: fix Review image plane attribute [\#3569](https://github.com/pypeclub/OpenPype/pull/3569) - ## [3.12.1](https://github.com/pypeclub/OpenPype/tree/3.12.1) (2022-07-13) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.12.1-nightly.6...3.12.1) diff --git a/openpype/version.py b/openpype/version.py index e738689c20..7894bb8bf4 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.2" +__version__ = "3.14.1-nightly.3" diff --git a/pyproject.toml b/pyproject.toml index bfc570f597..75e4721d7f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.14.1-nightly.2" # OpenPype +version = "3.14.1-nightly.3" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From fcb047770ad41364bbb9aa50ab40765fd43132cf Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 12:07:51 +0200 Subject: [PATCH 68/96] 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 69/96] 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 6f4f87418eabdf1248dbd4db29cff77ff018b0ab Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 13:31:28 +0200 Subject: [PATCH 70/96] 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 71/96] 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 72/96] 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 73/96] 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 74/96] 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 75/96] 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 76/96] 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 77/96] 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 78/96] 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 79/96] 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 80/96] 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 81/96] 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 82/96] 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 83/96] 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 84/96] '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 85/96] 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 86/96] 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 87/96] 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 88/96] 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 89/96] 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 90/96] 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 91/96] 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 b14bb4b91e2bf7053a3e5d057f6b54d479535072 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 29 Aug 2022 17:23:22 +0200 Subject: [PATCH 92/96] 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 40a4262916043b364dbd697b30713a1154692da3 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Tue, 30 Aug 2022 08:20:59 +0000 Subject: [PATCH 93/96] [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 94/96] [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 e23ed382f8149648c404f15e18ea567bdceebfb0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 30 Aug 2022 13:23:07 +0200 Subject: [PATCH 95/96] 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 96/96] 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