From 7bfedcfd99f03ce8e8813b003105c24129104345 Mon Sep 17 00:00:00 2001 From: jezscha Date: Thu, 13 May 2021 16:54:57 +0000 Subject: [PATCH 01/21] Create draft PR for #1376 From 843fa6f6e08db1a6934b50d0be9739f627ccefb9 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 14 May 2021 17:51:24 +0100 Subject: [PATCH 02/21] Removed Animation Set as asset type --- .../blender/plugins/create/create_setdress.py | 25 ---- .../hosts/blender/plugins/load/load_layout.py | 27 +--- .../publish/extract_animation_collection.py | 61 --------- .../unreal/plugins/load/load_setdress.py | 127 ------------------ 4 files changed, 7 insertions(+), 233 deletions(-) delete mode 100644 openpype/hosts/blender/plugins/create/create_setdress.py delete mode 100644 openpype/hosts/blender/plugins/publish/extract_animation_collection.py delete mode 100644 openpype/hosts/unreal/plugins/load/load_setdress.py diff --git a/openpype/hosts/blender/plugins/create/create_setdress.py b/openpype/hosts/blender/plugins/create/create_setdress.py deleted file mode 100644 index 97c737c235..0000000000 --- a/openpype/hosts/blender/plugins/create/create_setdress.py +++ /dev/null @@ -1,25 +0,0 @@ -import bpy - -from avalon import api, blender -import openpype.hosts.blender.api.plugin - - -class CreateSetDress(openpype.hosts.blender.api.plugin.Creator): - """A grouped package of loaded content""" - - name = "setdressMain" - label = "Set Dress" - family = "setdress" - icon = "cubes" - defaults = ["Main", "Anim"] - - def process(self): - asset = self.data["asset"] - subset = self.data["subset"] - name = openpype.hosts.blender.api.plugin.asset_name(asset, subset) - collection = bpy.data.collections.new(name=name) - bpy.context.scene.collection.children.link(collection) - self.data['task'] = api.Session.get('AVALON_TASK') - blender.lib.imprint(collection, self.data) - - return collection diff --git a/openpype/hosts/blender/plugins/load/load_layout.py b/openpype/hosts/blender/plugins/load/load_layout.py index f1f2fdcddd..08a905fbf0 100644 --- a/openpype/hosts/blender/plugins/load/load_layout.py +++ b/openpype/hosts/blender/plugins/load/load_layout.py @@ -25,9 +25,6 @@ class BlendLayoutLoader(plugin.AssetLoader): icon = "code-fork" color = "orange" - animation_creator_name = "CreateAnimation" - setdress_creator_name = "CreateSetDress" - def _remove(self, objects, obj_container): for obj in list(objects): if obj.type == 'ARMATURE': @@ -293,7 +290,6 @@ class UnrealLayoutLoader(plugin.AssetLoader): color = "orange" animation_creator_name = "CreateAnimation" - setdress_creator_name = "CreateSetDress" def _remove_objects(self, objects): for obj in list(objects): @@ -383,7 +379,7 @@ class UnrealLayoutLoader(plugin.AssetLoader): def _process( self, libpath, layout_container, container_name, representation, - actions, parent + actions, parent_collection ): with open(libpath, "r") as fp: data = json.load(fp) @@ -392,6 +388,11 @@ class UnrealLayoutLoader(plugin.AssetLoader): layout_collection = bpy.data.collections.new(container_name) scene.collection.children.link(layout_collection) + parent = parent_collection + + if parent is None: + parent = scene.collection + all_loaders = api.discover(api.Loader) avalon_container = bpy.data.collections.get( @@ -516,23 +517,9 @@ class UnrealLayoutLoader(plugin.AssetLoader): container_metadata["libpath"] = libpath container_metadata["lib_container"] = lib_container - # Create a setdress subset to contain all the animation for all - # the rigs in the layout - creator_plugin = get_creator_by_name(self.setdress_creator_name) - if not creator_plugin: - raise ValueError("Creator plugin \"{}\" was not found.".format( - self.setdress_creator_name - )) - parent = api.create( - creator_plugin, - name="animation", - asset=api.Session["AVALON_ASSET"], - options={"useSelection": True}, - data={"dependencies": str(context["representation"]["_id"])}) - layout_collection = self._process( libpath, layout_container, container_name, - str(context["representation"]["_id"]), None, parent) + str(context["representation"]["_id"]), None, None) container_metadata["obj_container"] = layout_collection diff --git a/openpype/hosts/blender/plugins/publish/extract_animation_collection.py b/openpype/hosts/blender/plugins/publish/extract_animation_collection.py deleted file mode 100644 index 19dc59c5cd..0000000000 --- a/openpype/hosts/blender/plugins/publish/extract_animation_collection.py +++ /dev/null @@ -1,61 +0,0 @@ -import os -import json - -import openpype.api -import pyblish.api - -import bpy - - -class ExtractSetDress(openpype.api.Extractor): - """Extract setdress.""" - - label = "Extract SetDress" - hosts = ["blender"] - families = ["setdress"] - optional = True - order = pyblish.api.ExtractorOrder + 0.1 - - def process(self, instance): - stagingdir = self.staging_dir(instance) - - json_data = [] - - for i in instance.context: - collection = i.data.get("name") - container = None - for obj in bpy.data.collections[collection].objects: - if obj.type == "ARMATURE": - container_name = obj.get("avalon").get("container_name") - container = bpy.data.collections[container_name] - if container: - json_dict = { - "subset": i.data.get("subset"), - "container": container.name, - } - json_dict["instance_name"] = container.get("avalon").get( - "instance_name" - ) - json_data.append(json_dict) - - if "representations" not in instance.data: - instance.data["representations"] = [] - - json_filename = f"{instance.name}.json" - json_path = os.path.join(stagingdir, json_filename) - - with open(json_path, "w+") as file: - json.dump(json_data, fp=file, indent=2) - - json_representation = { - "name": "json", - "ext": "json", - "files": json_filename, - "stagingDir": stagingdir, - } - instance.data["representations"].append(json_representation) - - self.log.info( - "Extracted instance '{}' to: {}".format(instance.name, - json_representation) - ) diff --git a/openpype/hosts/unreal/plugins/load/load_setdress.py b/openpype/hosts/unreal/plugins/load/load_setdress.py deleted file mode 100644 index da302deb1c..0000000000 --- a/openpype/hosts/unreal/plugins/load/load_setdress.py +++ /dev/null @@ -1,127 +0,0 @@ -import json - -from avalon import api -import unreal - - -class AnimationCollectionLoader(api.Loader): - """Load Unreal SkeletalMesh from FBX""" - - families = ["setdress"] - representations = ["json"] - - label = "Load Animation Collection" - icon = "cube" - color = "orange" - - def load(self, context, name, namespace, options): - from avalon import api, pipeline - from avalon.unreal import lib - from avalon.unreal import pipeline as unreal_pipeline - import unreal - - # Create directory for asset and avalon container - root = "/Game/Avalon/Assets" - asset = context.get('asset').get('name') - suffix = "_CON" - - tools = unreal.AssetToolsHelpers().get_asset_tools() - asset_dir, container_name = tools.create_unique_asset_name( - "{}/{}".format(root, asset), suffix="") - - container_name += suffix - - unreal.EditorAssetLibrary.make_directory(asset_dir) - - libpath = self.fname - - with open(libpath, "r") as fp: - data = json.load(fp) - - all_loaders = api.discover(api.Loader) - - for element in data: - reference = element.get('_id') - - loaders = api.loaders_from_representation(all_loaders, reference) - loader = None - for l in loaders: - if l.__name__ == "AnimationFBXLoader": - loader = l - break - - if not loader: - continue - - instance_name = element.get('instance_name') - - api.load( - loader, - reference, - namespace=instance_name, - options=element - ) - - # Create Asset Container - lib.create_avalon_container( - container=container_name, path=asset_dir) - - data = { - "schema": "openpype:container-2.0", - "id": pipeline.AVALON_CONTAINER_ID, - "asset": asset, - "namespace": asset_dir, - "container_name": container_name, - "loader": str(self.__class__.__name__), - "representation": context["representation"]["_id"], - "parent": context["representation"]["parent"], - "family": context["representation"]["context"]["family"] - } - unreal_pipeline.imprint( - "{}/{}".format(asset_dir, container_name), data) - - asset_content = unreal.EditorAssetLibrary.list_assets( - asset_dir, recursive=True, include_folder=True - ) - - return asset_content - - def update(self, container, representation): - from avalon import api, io - from avalon.unreal import pipeline - - source_path = api.get_representation_path(representation) - - with open(source_path, "r") as fp: - data = json.load(fp) - - animation_containers = [ - i for i in pipeline.ls() if - i.get('asset') == container.get('asset') and - i.get('family') == 'animation'] - - for element in data: - new_version = io.find_one({"_id": io.ObjectId(element.get('_id'))}) - new_version_number = new_version.get('context').get('version') - anim_container = None - for i in animation_containers: - if i.get('container_name') == (element.get('subset') + "_CON"): - anim_container = i - break - if not anim_container: - continue - - api.update(anim_container, new_version_number) - - container_path = "{}/{}".format(container["namespace"], - container["objectName"]) - # update metadata - pipeline.imprint( - container_path, - { - "representation": str(representation["_id"]), - "parent": str(representation["parent"]) - }) - - def remove(self, container): - unreal.EditorAssetLibrary.delete_directory(container["namespace"]) From b4c826c4a93208ac85098fde0a1483ecff4c6e61 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 14 May 2021 17:52:11 +0100 Subject: [PATCH 03/21] Fixed problem with non local actions when loading rigs --- openpype/hosts/blender/plugins/load/load_rig.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/blender/plugins/load/load_rig.py b/openpype/hosts/blender/plugins/load/load_rig.py index c5690a6ab8..0c92354310 100644 --- a/openpype/hosts/blender/plugins/load/load_rig.py +++ b/openpype/hosts/blender/plugins/load/load_rig.py @@ -107,6 +107,9 @@ class BlendRigLoader(plugin.AssetLoader): if action is not None: local_obj.animation_data.action = action + elif local_obj.animation_data.action is not None: + plugin.prepare_data( + local_obj.animation_data.action, collection_name) # Set link the drivers to the local object if local_obj.data.animation_data: From 8c03e16314db5fe886f5864a8c2e593bb79d5762 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 14 May 2021 17:56:16 +0100 Subject: [PATCH 04/21] Improved extraction of animation from Blender and loading in Unreal A json file is extracted together with the animation fbx. The json file stores the instance name of the asset in Unreal. Thus, when loading the animation in Unreal, it can be associated with a skeleton and automatically applied to the right SkeletalMesh. --- .../plugins/publish/extract_fbx_animation.py | 33 ++++++++++++++++ .../unreal/plugins/load/load_animation.py | 38 +++++++++++++++---- 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py index 1036800705..418dd100b2 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py @@ -1,4 +1,5 @@ import os +import json import openpype.api @@ -121,6 +122,30 @@ class ExtractAnimationFBX(openpype.api.Extractor): pair[1].user_clear() bpy.data.actions.remove(pair[1]) + json_filename = f"{instance.name}.json" + json_path = os.path.join(stagingdir, json_filename) + + json_dict = {} + + collection = instance.data.get("name") + container = None + for obj in bpy.data.collections[collection].objects: + if obj.type == "ARMATURE": + container_name = obj.get("avalon").get("container_name") + container = bpy.data.collections[container_name] + if container: + json_dict = { + # "representation": container.get("avalon").get( + # "representation" + # ), + "instance_name": container.get("avalon").get( + "instance_name" + ) + } + + with open(json_path, "w+") as file: + json.dump(json_dict, fp=file, indent=2) + if "representations" not in instance.data: instance.data["representations"] = [] @@ -130,7 +155,15 @@ class ExtractAnimationFBX(openpype.api.Extractor): 'files': fbx_filename, "stagingDir": stagingdir, } + json_representation = { + 'name': 'json', + 'ext': 'json', + 'files': json_filename, + "stagingDir": stagingdir, + } instance.data["representations"].append(fbx_representation) + instance.data["representations"].append(json_representation) + self.log.info("Extracted instance '{}' to: {}".format( instance.name, fbx_representation)) diff --git a/openpype/hosts/unreal/plugins/load/load_animation.py b/openpype/hosts/unreal/plugins/load/load_animation.py index 18910983ea..a53328847d 100644 --- a/openpype/hosts/unreal/plugins/load/load_animation.py +++ b/openpype/hosts/unreal/plugins/load/load_animation.py @@ -1,4 +1,5 @@ import os +import json from avalon import api, pipeline from avalon.unreal import lib @@ -61,10 +62,16 @@ class AnimationFBXLoader(api.Loader): task = unreal.AssetImportTask() task.options = unreal.FbxImportUI() - # If there are no options, the process cannot be automated - if options: + libpath = self.fname.replace("fbx", "json") + + with open(libpath, "r") as fp: + data = json.load(fp) + + instance_name = data.get("instance_name") + + if instance_name: automated = True - actor_name = 'PersistentLevel.' + options.get('instance_name') + actor_name = 'PersistentLevel.' + instance_name actor = unreal.EditorLevelLibrary.get_actor_reference(actor_name) skeleton = actor.skeletal_mesh_component.skeletal_mesh.skeleton task.options.set_editor_property('skeleton', skeleton) @@ -81,16 +88,31 @@ class AnimationFBXLoader(api.Loader): # set import options here task.options.set_editor_property( - 'automated_import_should_detect_type', True) + 'automated_import_should_detect_type', False) task.options.set_editor_property( - 'original_import_type', unreal.FBXImportType.FBXIT_ANIMATION) + 'original_import_type', unreal.FBXImportType.FBXIT_SKELETAL_MESH) + task.options.set_editor_property( + 'mesh_type_to_import', unreal.FBXImportType.FBXIT_ANIMATION) task.options.set_editor_property('import_mesh', False) task.options.set_editor_property('import_animations', True) + task.options.set_editor_property('override_full_name', True) - task.options.skeletal_mesh_import_data.set_editor_property( - 'import_content_type', - unreal.FBXImportContentType.FBXICT_SKINNING_WEIGHTS + task.options.anim_sequence_import_data.set_editor_property( + 'animation_length', + unreal.FBXAnimationLengthImportType.FBXALIT_EXPORTED_TIME ) + task.options.anim_sequence_import_data.set_editor_property( + 'import_meshes_in_bone_hierarchy', False) + task.options.anim_sequence_import_data.set_editor_property( + 'use_default_sample_rate', True) + task.options.anim_sequence_import_data.set_editor_property( + 'import_custom_attribute', True) + task.options.anim_sequence_import_data.set_editor_property( + 'import_bone_tracks', True) + task.options.anim_sequence_import_data.set_editor_property( + 'remove_redundant_keys', True) + task.options.anim_sequence_import_data.set_editor_property( + 'convert_scene', True) unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) From 963127e08e45698e03a7f79a720c4f4e44c0d56c Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 14 May 2021 18:04:07 +0100 Subject: [PATCH 05/21] Hound fix --- .../hosts/blender/plugins/publish/extract_fbx_animation.py | 7 +------ openpype/hosts/unreal/plugins/load/load_animation.py | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py index 418dd100b2..8312114c7b 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py @@ -135,12 +135,7 @@ class ExtractAnimationFBX(openpype.api.Extractor): container = bpy.data.collections[container_name] if container: json_dict = { - # "representation": container.get("avalon").get( - # "representation" - # ), - "instance_name": container.get("avalon").get( - "instance_name" - ) + "instance_name": container.get("avalon").get("instance_name") } with open(json_path, "w+") as file: diff --git a/openpype/hosts/unreal/plugins/load/load_animation.py b/openpype/hosts/unreal/plugins/load/load_animation.py index a53328847d..481285d603 100644 --- a/openpype/hosts/unreal/plugins/load/load_animation.py +++ b/openpype/hosts/unreal/plugins/load/load_animation.py @@ -98,7 +98,7 @@ class AnimationFBXLoader(api.Loader): task.options.set_editor_property('override_full_name', True) task.options.anim_sequence_import_data.set_editor_property( - 'animation_length', + 'animation_length', unreal.FBXAnimationLengthImportType.FBXALIT_EXPORTED_TIME ) task.options.anim_sequence_import_data.set_editor_property( From a7c52ba9f178bb8b939fc4152dca28b313642406 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 17 May 2021 13:23:38 +0200 Subject: [PATCH 06/21] Hiero: precollect and exctrack effects --- .../extract_clip_effects.py | 9 +++++---- .../precollect_clip_effects.py | 13 ++++++++++--- 2 files changed, 15 insertions(+), 7 deletions(-) rename openpype/hosts/hiero/plugins/{publish_old_workflow => publish}/extract_clip_effects.py (92%) rename openpype/hosts/hiero/plugins/{publish_old_workflow => publish}/precollect_clip_effects.py (93%) diff --git a/openpype/hosts/hiero/plugins/publish_old_workflow/extract_clip_effects.py b/openpype/hosts/hiero/plugins/publish/extract_clip_effects.py similarity index 92% rename from openpype/hosts/hiero/plugins/publish_old_workflow/extract_clip_effects.py rename to openpype/hosts/hiero/plugins/publish/extract_clip_effects.py index d2ac7f4786..5b0aa270a7 100644 --- a/openpype/hosts/hiero/plugins/publish_old_workflow/extract_clip_effects.py +++ b/openpype/hosts/hiero/plugins/publish/extract_clip_effects.py @@ -52,10 +52,11 @@ class ExtractClipEffects(openpype.api.Extractor): instance.data["representations"] = list() transfer_data = [ - "handleStart", "handleEnd", "sourceIn", "sourceOut", - "frameStart", "frameEnd", "sourceInH", "sourceOutH", - "clipIn", "clipOut", "clipInH", "clipOutH", "asset", "track", - "version" + "handleStart", "handleEnd", + "sourceStart", "sourceStartH", "sourceEnd", "sourceEndH", + "frameStart", "frameEnd", + "clipIn", "clipOut", "clipInH", "clipOutH", + "asset", "version" ] # pass data to version diff --git a/openpype/hosts/hiero/plugins/publish_old_workflow/precollect_clip_effects.py b/openpype/hosts/hiero/plugins/publish/precollect_clip_effects.py similarity index 93% rename from openpype/hosts/hiero/plugins/publish_old_workflow/precollect_clip_effects.py rename to openpype/hosts/hiero/plugins/publish/precollect_clip_effects.py index f9bde24255..5a9f89651c 100644 --- a/openpype/hosts/hiero/plugins/publish_old_workflow/precollect_clip_effects.py +++ b/openpype/hosts/hiero/plugins/publish/precollect_clip_effects.py @@ -5,7 +5,7 @@ import pyblish.api class PreCollectClipEffects(pyblish.api.InstancePlugin): """Collect soft effects instances.""" - order = pyblish.api.CollectorOrder - 0.508 + order = pyblish.api.CollectorOrder - 0.579 label = "Pre-collect Clip Effects Instances" families = ["clip"] @@ -24,7 +24,8 @@ class PreCollectClipEffects(pyblish.api.InstancePlugin): self.clip_in_h = self.clip_in - self.handle_start self.clip_out_h = self.clip_out + self.handle_end - track = instance.data["trackItem"] + track_item = instance.data["item"] + track = track_item.parent() track_index = track.trackIndex() tracks_effect_items = instance.context.data.get("tracksEffectItems") clip_effect_items = instance.data.get("clipEffectItems") @@ -112,7 +113,12 @@ class PreCollectClipEffects(pyblish.api.InstancePlugin): node = sitem.node() node_serialized = self.node_serialisation(node) node_name = sitem.name() - node_class = re.sub(r"\d+", "", node_name) + + if "_" in node_name: + node_class = re.sub(r"(?:_)[_0-9]+", "", node_name) # more numbers + else: + node_class = re.sub(r"\d+", "", node_name) # one number + # collect timelineIn/Out effect_t_in = int(sitem.timelineIn()) effect_t_out = int(sitem.timelineOut()) @@ -121,6 +127,7 @@ class PreCollectClipEffects(pyblish.api.InstancePlugin): return self.log.debug("node_name: `{}`".format(node_name)) + self.log.debug("node_class: `{}`".format(node_class)) return {node_name: { "class": node_class, From eaf53410a5c32fd0b98520b5dc68c730af251ff0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 17 May 2021 14:02:18 +0200 Subject: [PATCH 07/21] Hiero: return only TrackItems --- openpype/hosts/hiero/api/lib.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index a9982d96c4..d8a235be77 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -214,7 +214,9 @@ def get_track_items( # add all if no track_type is defined return_list.append(track_item) - return return_list + # return output list but make sure all items are TrackItems + return [_i for _i in return_list + if type(_i) == hiero.core.TrackItem] def get_track_item_pype_tag(track_item): From 1910b5ec958b8259ea0bf372b65de8ea6059addc Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 19 May 2021 17:24:27 +0200 Subject: [PATCH 08/21] Nuke: updating loaders for effects --- .../load/{load_luts.py => load_effects.py} | 32 ++++++++++++------- .../{load_luts_ip.py => load_effects_ip.py} | 25 ++++++++------- 2 files changed, 34 insertions(+), 23 deletions(-) rename openpype/hosts/nuke/plugins/load/{load_luts.py => load_effects.py} (94%) rename openpype/hosts/nuke/plugins/load/{load_luts_ip.py => load_effects_ip.py} (95%) diff --git a/openpype/hosts/nuke/plugins/load/load_luts.py b/openpype/hosts/nuke/plugins/load/load_effects.py similarity index 94% rename from openpype/hosts/nuke/plugins/load/load_luts.py rename to openpype/hosts/nuke/plugins/load/load_effects.py index 85ec3e2060..6306767f37 100644 --- a/openpype/hosts/nuke/plugins/load/load_luts.py +++ b/openpype/hosts/nuke/plugins/load/load_effects.py @@ -4,18 +4,19 @@ import json from collections import OrderedDict -class LoadLuts(api.Loader): +class LoadEffects(api.Loader): """Loading colorspace soft effect exported from nukestudio""" - representations = ["lutJson"] - families = ["lut"] + representations = ["effectJson"] + families = ["effect"] - label = "Load Luts - nodes" + label = "Load Effects - nodes" order = 0 icon = "cc" color = style.colors.light ignore_attr = ["useLifetime"] + def load(self, context, name, namespace, data): """ Loading function to get the soft effects to particular read node @@ -66,15 +67,15 @@ class LoadLuts(api.Loader): for key, value in json.load(f).iteritems()} # get correct order of nodes by positions on track and subtrack - nodes_order = self.reorder_nodes(json_f["effects"]) + nodes_order = self.reorder_nodes(json_f) # adding nodes to node graph # just in case we are in group lets jump out of it nuke.endGroup() - GN = nuke.createNode("Group") - - GN["name"].setValue(object_name) + GN = nuke.createNode( + "Group", + "name {}_1".format(object_name)) # adding content to the group node with GN: @@ -186,7 +187,7 @@ class LoadLuts(api.Loader): for key, value in json.load(f).iteritems()} # get correct order of nodes by positions on track and subtrack - nodes_order = self.reorder_nodes(json_f["effects"]) + nodes_order = self.reorder_nodes(json_f) # adding nodes to node graph # just in case we are in group lets jump out of it @@ -266,7 +267,11 @@ class LoadLuts(api.Loader): None: if nothing found """ search_name = "{0}_{1}".format(asset, subset) - node = [n for n in nuke.allNodes() if search_name in n["name"].value()] + + node = [ + n for n in nuke.allNodes(filter="Read") + if search_name in n["file"].value() + ] if len(node) > 0: rn = node[0] else: @@ -286,8 +291,10 @@ class LoadLuts(api.Loader): def reorder_nodes(self, data): new_order = OrderedDict() - trackNums = [v["trackIndex"] for k, v in data.items()] - subTrackNums = [v["subTrackIndex"] for k, v in data.items()] + trackNums = [v["trackIndex"] for k, v in data.items() + if isinstance(v, dict)] + subTrackNums = [v["subTrackIndex"] for k, v in data.items() + if isinstance(v, dict)] for trackIndex in range( min(trackNums), max(trackNums) + 1): @@ -300,6 +307,7 @@ class LoadLuts(api.Loader): def get_item(self, data, trackIndex, subTrackIndex): return {key: val for key, val in data.items() + if isinstance(val, dict) if subTrackIndex == val["subTrackIndex"] if trackIndex == val["trackIndex"]} diff --git a/openpype/hosts/nuke/plugins/load/load_luts_ip.py b/openpype/hosts/nuke/plugins/load/load_effects_ip.py similarity index 95% rename from openpype/hosts/nuke/plugins/load/load_luts_ip.py rename to openpype/hosts/nuke/plugins/load/load_effects_ip.py index a0af29c7f4..6c71f2ae16 100644 --- a/openpype/hosts/nuke/plugins/load/load_luts_ip.py +++ b/openpype/hosts/nuke/plugins/load/load_effects_ip.py @@ -5,13 +5,13 @@ from collections import OrderedDict from openpype.hosts.nuke.api import lib -class LoadLutsInputProcess(api.Loader): +class LoadEffectsInputProcess(api.Loader): """Loading colorspace soft effect exported from nukestudio""" - representations = ["lutJson"] - families = ["lut"] + representations = ["effectJson"] + families = ["effect"] - label = "Load Luts - Input Process" + label = "Load Effects - Input Process" order = 0 icon = "eye" color = style.colors.alert @@ -67,15 +67,15 @@ class LoadLutsInputProcess(api.Loader): for key, value in json.load(f).iteritems()} # get correct order of nodes by positions on track and subtrack - nodes_order = self.reorder_nodes(json_f["effects"]) + nodes_order = self.reorder_nodes(json_f) # adding nodes to node graph # just in case we are in group lets jump out of it nuke.endGroup() - GN = nuke.createNode("Group") - - GN["name"].setValue(object_name) + GN = nuke.createNode( + "Group", + "name {}_1".format(object_name)) # adding content to the group node with GN: @@ -190,7 +190,7 @@ class LoadLutsInputProcess(api.Loader): for key, value in json.load(f).iteritems()} # get correct order of nodes by positions on track and subtrack - nodes_order = self.reorder_nodes(json_f["effects"]) + nodes_order = self.reorder_nodes(json_f) # adding nodes to node graph # just in case we are in group lets jump out of it @@ -304,8 +304,10 @@ class LoadLutsInputProcess(api.Loader): def reorder_nodes(self, data): new_order = OrderedDict() - trackNums = [v["trackIndex"] for k, v in data.items()] - subTrackNums = [v["subTrackIndex"] for k, v in data.items()] + trackNums = [v["trackIndex"] for k, v in data.items() + if isinstance(v, dict)] + subTrackNums = [v["subTrackIndex"] for k, v in data.items() + if isinstance(v, dict)] for trackIndex in range( min(trackNums), max(trackNums) + 1): @@ -318,6 +320,7 @@ class LoadLutsInputProcess(api.Loader): def get_item(self, data, trackIndex, subTrackIndex): return {key: val for key, val in data.items() + if isinstance(val, dict) if subTrackIndex == val["subTrackIndex"] if trackIndex == val["trackIndex"]} From 4b925ae760d32c58e3e3c48b7cea393e7842354e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 19 May 2021 17:26:20 +0200 Subject: [PATCH 09/21] Global: updating families filters --- openpype/plugins/publish/collect_resources_path.py | 4 ++-- openpype/plugins/publish/integrate_new.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/plugins/publish/collect_resources_path.py b/openpype/plugins/publish/collect_resources_path.py index 04a33cd5be..98b59332da 100644 --- a/openpype/plugins/publish/collect_resources_path.py +++ b/openpype/plugins/publish/collect_resources_path.py @@ -39,7 +39,6 @@ class CollectResourcesPath(pyblish.api.InstancePlugin): "rig", "plate", "look", - "lut", "yetiRig", "yeticache", "nukenodes", @@ -52,7 +51,8 @@ class CollectResourcesPath(pyblish.api.InstancePlugin): "fbx", "textures", "action", - "background" + "background", + "effect" ] def process(self, instance): diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 9769f0d165..3a926789fb 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -78,7 +78,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "rig", "plate", "look", - "lut", "audio", "yetiRig", "yeticache", @@ -97,7 +96,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "editorial", "background", "camerarig", - "redshiftproxy" + "redshiftproxy", + "effect" ] exclude_families = ["clip"] db_representation_context_keys = [ From 3bc9cdac4e5c8f24c43190473d993703551d8f26 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 19 May 2021 17:26:48 +0200 Subject: [PATCH 10/21] Hiero: fix audio instance creation only if any audio available --- openpype/plugins/publish/extract_otio_audio_tracks.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/extract_otio_audio_tracks.py b/openpype/plugins/publish/extract_otio_audio_tracks.py index 43e40097f7..7ba55d0c39 100644 --- a/openpype/plugins/publish/extract_otio_audio_tracks.py +++ b/openpype/plugins/publish/extract_otio_audio_tracks.py @@ -40,12 +40,15 @@ class ExtractOtioAudioTracks(pyblish.api.ContextPlugin): # get sequence otio_timeline = context.data["otioTimeline"] - # temp file - audio_temp_fpath = self.create_temp_file("audio") - # get all audio inputs from otio timeline audio_inputs = self.get_audio_track_items(otio_timeline) + if not audio_inputs: + return + + # temp file + audio_temp_fpath = self.create_temp_file("audio") + # create empty audio with longest duration empty = self.create_empty(audio_inputs) From 46a5907b34b9cb1f052a9660a4517c68528e9a53 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 19 May 2021 17:27:28 +0200 Subject: [PATCH 11/21] Hiero: collection of colorspace data to workfile instance and context --- .../plugins/publish/precollect_workfile.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/hiero/plugins/publish/precollect_workfile.py b/openpype/hosts/hiero/plugins/publish/precollect_workfile.py index bc4ef7e150..530a433423 100644 --- a/openpype/hosts/hiero/plugins/publish/precollect_workfile.py +++ b/openpype/hosts/hiero/plugins/publish/precollect_workfile.py @@ -75,10 +75,26 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin): "activeProject": project, "otioTimeline": otio_timeline, "currentFile": curent_file, - "fps": fps, + "colorspace": self.get_colorspace(project), + "fps": fps } context.data.update(context_data) self.log.info("Creating instance: {}".format(instance)) self.log.debug("__ instance.data: {}".format(pformat(instance.data))) self.log.debug("__ context_data: {}".format(pformat(context_data))) + + def get_colorspace(self, project): + # get workfile's colorspace properties + return { + "useOCIOEnvironmentOverride": project.useOCIOEnvironmentOverride(), + "lutSetting16Bit": project.lutSetting16Bit(), + "lutSetting8Bit": project.lutSetting8Bit(), + "lutSettingFloat": project.lutSettingFloat(), + "lutSettingLog": project.lutSettingLog(), + "lutSettingViewer": project.lutSettingViewer(), + "lutSettingWorkingSpace": project.lutSettingWorkingSpace(), + "lutUseOCIOForExport": project.lutUseOCIOForExport(), + "ocioConfigName": project.ocioConfigName(), + "ocioConfigPath": project.ocioConfigPath() + } From a8fa89a2b6b830fa379ca9be4d61f041dbafaa23 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 19 May 2021 17:28:08 +0200 Subject: [PATCH 12/21] Hiero: collection of soft-effects with instance creation --- .../plugins/publish/precollect_instances.py | 141 +++++++++++++++++- 1 file changed, 134 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/hiero/plugins/publish/precollect_instances.py b/openpype/hosts/hiero/plugins/publish/precollect_instances.py index 8cccdec99a..6d72fa3567 100644 --- a/openpype/hosts/hiero/plugins/publish/precollect_instances.py +++ b/openpype/hosts/hiero/plugins/publish/precollect_instances.py @@ -2,6 +2,9 @@ import pyblish import openpype from openpype.hosts.hiero import api as phiero from openpype.hosts.hiero.otio import hiero_export +import hiero + +from compiler.ast import flatten # # developer reload modules from pprint import pformat @@ -14,18 +17,40 @@ class PrecollectInstances(pyblish.api.ContextPlugin): label = "Precollect Instances" hosts = ["hiero"] + audio_track_items = [] + def process(self, context): - otio_timeline = context.data["otioTimeline"] + self.otio_timeline = context.data["otioTimeline"] + selected_timeline_items = phiero.get_track_items( - selected=True, check_enabled=True, check_tagged=True) + selected=True, check_tagged=True, check_enabled=True) + + # only return enabled track items + if not selected_timeline_items: + selected_timeline_items = phiero.get_track_items( + check_enabled=True, check_tagged=True) + self.log.info( "Processing enabled track items: {}".format( selected_timeline_items)) + # add all tracks subtreck effect items to context + all_tracks = hiero.ui.activeSequence().videoTracks() + tracks_effect_items = self.collect_sub_track_items(all_tracks) + context.data["tracksEffectItems"] = tracks_effect_items + + # process all sellected timeline track items for track_item in selected_timeline_items: data = {} clip_name = track_item.name() + source_clip = track_item.source() + + # get clips subtracks and anotations + annotations = self.clip_annotations(source_clip) + subtracks = self.clip_subtrack(track_item) + self.log.debug("Annotations: {}".format(annotations)) + self.log.debug(">> Subtracks: {}".format(subtracks)) # get openpype tag data tag_data = phiero.get_track_item_pype_data(track_item) @@ -76,12 +101,15 @@ class PrecollectInstances(pyblish.api.ContextPlugin): "item": track_item, "families": families, "publish": tag_data["publish"], - "fps": context.data["fps"] + "fps": context.data["fps"], + + # clip's effect + "clipEffectItems": subtracks, + "clipAnnotations": annotations }) # otio clip data - otio_data = self.get_otio_clip_instance_data( - otio_timeline, track_item) or {} + otio_data = self.get_otio_clip_instance_data(track_item) or {} self.log.debug("__ otio_data: {}".format(pformat(otio_data))) data.update(otio_data) self.log.debug("__ data: {}".format(pformat(data))) @@ -185,6 +213,10 @@ class PrecollectInstances(pyblish.api.ContextPlugin): item = data.get("item") clip_name = item.name() + # test if any audio clips + if not self.test_any_audio(item): + return + asset = data["asset"] subset = "audioMain" @@ -215,7 +247,29 @@ class PrecollectInstances(pyblish.api.ContextPlugin): self.log.debug( "_ instance.data: {}".format(pformat(instance.data))) - def get_otio_clip_instance_data(self, otio_timeline, track_item): + def test_any_audio(self, track_item): + # collect all audio tracks to class variable + if not self.audio_track_items: + for otio_clip in self.otio_timeline.each_clip(): + if otio_clip.parent().kind != "Audio": + continue + self.audio_track_items.append(otio_clip) + + # get track item timeline range + timeline_range = self.create_otio_time_range_from_timeline_item_data( + track_item) + + # loop trough audio track items and search for overlaping clip + for otio_audio in self.audio_track_items: + parent_range = otio_audio.range_in_parent() + + # if any overaling clip found then return True + if openpype.lib.is_overlapping_otio_ranges( + parent_range, timeline_range, strict=False): + return True + + + def get_otio_clip_instance_data(self, track_item): """ Return otio objects for timeline, track and clip @@ -231,7 +285,7 @@ class PrecollectInstances(pyblish.api.ContextPlugin): ti_track_name = track_item.parent().name() timeline_range = self.create_otio_time_range_from_timeline_item_data( track_item) - for otio_clip in otio_timeline.each_clip(): + for otio_clip in self.otio_timeline.each_clip(): track_name = otio_clip.parent().name parent_range = otio_clip.range_in_parent() if ti_track_name not in track_name: @@ -258,3 +312,76 @@ class PrecollectInstances(pyblish.api.ContextPlugin): return hiero_export.create_otio_time_range( frame_start, frame_duration, fps) + + @staticmethod + def collect_sub_track_items(tracks): + """ + Returns dictionary with track index as key and list of subtracks + """ + # collect all subtrack items + sub_track_items = {} + for track in tracks: + items = track.items() + + # skip if no clips on track > need track with effect only + if items: + continue + + # skip all disabled tracks + if not track.isEnabled(): + continue + + track_index = track.trackIndex() + _sub_track_items = flatten(track.subTrackItems()) + + # continue only if any subtrack items are collected + if len(_sub_track_items) < 1: + continue + + enabled_sti = [] + # loop all found subtrack items and check if they are enabled + for _sti in _sub_track_items: + # checking if not enabled + if not _sti.isEnabled(): + continue + if isinstance(_sti, hiero.core.Annotation): + continue + # collect the subtrack item + enabled_sti.append(_sti) + + # continue only if any subtrack items are collected + if len(enabled_sti) < 1: + continue + + # add collection of subtrackitems to dict + sub_track_items[track_index] = enabled_sti + + return sub_track_items + + @staticmethod + def clip_annotations(clip): + """ + Returns list of Clip's hiero.core.Annotation + """ + annotations = [] + subTrackItems = flatten(clip.subTrackItems()) + annotations += [item for item in subTrackItems if isinstance( + item, hiero.core.Annotation)] + return annotations + + @staticmethod + def clip_subtrack(clip): + """ + Returns list of Clip's hiero.core.SubTrackItem + """ + subtracks = [] + subTrackItems = flatten(clip.parent().subTrackItems()) + for item in subTrackItems: + # avoid all anotation + if isinstance(item, hiero.core.Annotation): + continue + # # avoid all not anaibled + if not item.isEnabled(): + continue + subtracks.append(item) + return subtracks From 3dd4cc073c7b56d615ab62402cd3422d9e08d848 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 19 May 2021 17:32:06 +0200 Subject: [PATCH 13/21] hound: suggestions --- openpype/hosts/hiero/plugins/publish/precollect_instances.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/hiero/plugins/publish/precollect_instances.py b/openpype/hosts/hiero/plugins/publish/precollect_instances.py index 6d72fa3567..f7449561ef 100644 --- a/openpype/hosts/hiero/plugins/publish/precollect_instances.py +++ b/openpype/hosts/hiero/plugins/publish/precollect_instances.py @@ -268,7 +268,6 @@ class PrecollectInstances(pyblish.api.ContextPlugin): parent_range, timeline_range, strict=False): return True - def get_otio_clip_instance_data(self, track_item): """ Return otio objects for timeline, track and clip From 7efd2aeecea330ce985c819fbbb45ca6b689496d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 21 May 2021 13:02:44 +0200 Subject: [PATCH 14/21] enhance windows and add linux launcher --- tools/run_project_manager.ps1 | 42 ++++++++++++++ tools/run_projectmanager.sh | 103 ++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 tools/run_projectmanager.sh diff --git a/tools/run_project_manager.ps1 b/tools/run_project_manager.ps1 index 67c2d2eb5e..9886a80316 100644 --- a/tools/run_project_manager.ps1 +++ b/tools/run_project_manager.ps1 @@ -10,9 +10,51 @@ PS> .\run_project_manager.ps1 #> + +$art = @" + + . . .. . .. + _oOOP3OPP3Op_. . + .PPpo~. .. ~2p. .. .... . . + .Ppo . .pPO3Op.. . O:. . . . + .3Pp . oP3'. 'P33. . 4 .. . . . .. . . . + .~OP 3PO. .Op3 : . .. _____ _____ _____ + .P3O . oP3oP3O3P' . . . . / /./ /./ / + O3:. O3p~ . .:. . ./____/./____/ /____/ + 'P . 3p3. oP3~. ..P:. . . .. . . .. . . . + . ': . Po' .Opo'. .3O. . o[ by Pype Club ]]]==- - - . . + . '_ .. . . _OP3.. . .https://openpype.io.. . + ~P3.OPPPO3OP~ . .. . + . ' '. . .. . . . .. . + +"@ + +Write-Host $art -ForegroundColor DarkGreen + $current_dir = Get-Location $script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent $openpype_root = (Get-Item $script_dir).parent.FullName + +$env:_INSIDE_OPENPYPE_TOOL = "1" + +# make sure Poetry is in PATH +if (-not (Test-Path 'env:POETRY_HOME')) { + $env:POETRY_HOME = "$openpype_root\.poetry" +} +$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin" + Set-Location -Path $openpype_root + +Write-Host ">>> " -NoNewline -ForegroundColor Green +Write-Host "Reading Poetry ... " -NoNewline +if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) { + Write-Host "NOT FOUND" -ForegroundColor Yellow + Write-Host "*** " -NoNewline -ForegroundColor Yellow + Write-Host "We need to install Poetry create virtual env first ..." + & "$openpype_root\tools\create_env.ps1" +} else { + Write-Host "OK" -ForegroundColor Green +} + & poetry run python "$($openpype_root)\start.py" projectmanager Set-Location -Path $current_dir diff --git a/tools/run_projectmanager.sh b/tools/run_projectmanager.sh new file mode 100644 index 0000000000..312f321d67 --- /dev/null +++ b/tools/run_projectmanager.sh @@ -0,0 +1,103 @@ +#!/usr/bin/env bash + +# Run OpenPype Settings GUI + + +art () { + cat <<-EOF + + . . .. . .. + _oOOP3OPP3Op_. . + .PPpo~· ·· ~2p. ·· ···· · · + ·Ppo · .pPO3Op.· · O:· · · · + .3Pp · oP3'· 'P33· · 4 ·· · · · ·· · · · + ·~OP 3PO· .Op3 : · ·· _____ _____ _____ + ·P3O · oP3oP3O3P' · · · · / /·/ /·/ / + O3:· O3p~ · ·:· · ·/____/·/____/ /____/ + 'P · 3p3· oP3~· ·.P:· · · ·· · · ·· · · · + · ': · Po' ·Opo'· .3O· . o[ by Pype Club ]]]==- - - · · + · '_ .. · . _OP3·· · ·https://openpype.io·· · + ~P3·OPPPO3OP~ · ·· · + · ' '· · ·· · · · ·· · + +EOF +} + +# Colors for terminal + +RST='\033[0m' # Text Reset + +# Regular Colors +Black='\033[0;30m' # Black +Red='\033[0;31m' # Red +Green='\033[0;32m' # Green +Yellow='\033[0;33m' # Yellow +Blue='\033[0;34m' # Blue +Purple='\033[0;35m' # Purple +Cyan='\033[0;36m' # Cyan +White='\033[0;37m' # White + +# Bold +BBlack='\033[1;30m' # Black +BRed='\033[1;31m' # Red +BGreen='\033[1;32m' # Green +BYellow='\033[1;33m' # Yellow +BBlue='\033[1;34m' # Blue +BPurple='\033[1;35m' # Purple +BCyan='\033[1;36m' # Cyan +BWhite='\033[1;37m' # White + +# Bold High Intensity +BIBlack='\033[1;90m' # Black +BIRed='\033[1;91m' # Red +BIGreen='\033[1;92m' # Green +BIYellow='\033[1;93m' # Yellow +BIBlue='\033[1;94m' # Blue +BIPurple='\033[1;95m' # Purple +BICyan='\033[1;96m' # Cyan +BIWhite='\033[1;97m' # White + + +############################################################################## +# Return absolute path +# Globals: +# None +# Arguments: +# Path to resolve +# Returns: +# None +############################################################################### +realpath () { + echo $(cd $(dirname "$1"); pwd)/$(basename "$1") +} + +# Main +main () { + + # Directories + openpype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}"))) + + _inside_openpype_tool="1" + + # make sure Poetry is in PATH + if [[ -z $POETRY_HOME ]]; then + export POETRY_HOME="$openpype_root/.poetry" + fi + export PATH="$POETRY_HOME/bin:$PATH" + + pushd "$openpype_root" > /dev/null || return > /dev/null + + echo -e "${BIGreen}>>>${RST} Reading Poetry ... \c" + if [ -f "$POETRY_HOME/bin/poetry" ]; then + echo -e "${BIGreen}OK${RST}" + else + echo -e "${BIYellow}NOT FOUND${RST}" + echo -e "${BIYellow}***${RST} We need to install Poetry and virtual env ..." + . "$openpype_root/tools/create_env.sh" || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return; } + fi + + echo -e "${BIGreen}>>>${RST} Generating zip from current sources ..." + poetry run python "$openpype_root/start.py" projectmanager +} + +main From 85d3ce529e149b75321f7bfdb76081195be14610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 21 May 2021 13:04:18 +0200 Subject: [PATCH 15/21] set shell script executable --- tools/run_projectmanager.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 tools/run_projectmanager.sh diff --git a/tools/run_projectmanager.sh b/tools/run_projectmanager.sh old mode 100644 new mode 100755 From 4c1d9e7e8d01d3af4df18385b35d6ef9596c81d2 Mon Sep 17 00:00:00 2001 From: jezscha Date: Fri, 21 May 2021 14:45:17 +0000 Subject: [PATCH 16/21] Create draft PR for #1412 From 01cf1dde71d6f436b5d137c1e0527aec564ea35c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 24 May 2021 13:13:36 +0200 Subject: [PATCH 17/21] Nuke: set context callback on script load --- openpype/hosts/nuke/api/lib.py | 2 +- openpype/hosts/nuke/startup/menu.py | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index e6dde813a7..63cac0fd8b 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -1059,7 +1059,7 @@ class WorkfileSettings(object): # replace reset resolution from avalon core to pype's self.reset_frame_range_handles() # add colorspace menu item - # self.set_colorspace() + self.set_colorspace() def set_favorites(self): work_dir = os.getenv("AVALON_WORKDIR") diff --git a/openpype/hosts/nuke/startup/menu.py b/openpype/hosts/nuke/startup/menu.py index 9eb656afa9..c452acb709 100644 --- a/openpype/hosts/nuke/startup/menu.py +++ b/openpype/hosts/nuke/startup/menu.py @@ -1,6 +1,7 @@ from openpype.hosts.nuke.api.lib import ( on_script_load, - check_inventory_versions + check_inventory_versions, + WorkfileSettings ) import nuke @@ -9,8 +10,14 @@ from openpype.api import Logger log = Logger().get_logger(__name__) -nuke.addOnScriptSave(on_script_load) +# fix ffmpeg settings on script +nuke.addOnScriptLoad(on_script_load) + +# set checker for last versions on loaded containers nuke.addOnScriptLoad(check_inventory_versions) nuke.addOnScriptSave(check_inventory_versions) +# # set apply all workfile settings on script load and save +nuke.addOnScriptLoad(WorkfileSettings().set_context_settings) + log.info('Automatic syncing of write file knob to script version') From 1f73b96bd053fed35c957d1c5e1d1d69fd3a4cf3 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 24 May 2021 17:40:01 +0200 Subject: [PATCH 18/21] Hiero: publishing issues - ffmpeg path with space - no need to add `ftrackreview` to tags --- .../publish/extract_otio_audio_tracks.py | 18 +++++++++--------- .../plugins/publish/extract_otio_review.py | 14 ++++++++------ 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/openpype/plugins/publish/extract_otio_audio_tracks.py b/openpype/plugins/publish/extract_otio_audio_tracks.py index 43e40097f7..57da6d274c 100644 --- a/openpype/plugins/publish/extract_otio_audio_tracks.py +++ b/openpype/plugins/publish/extract_otio_audio_tracks.py @@ -53,14 +53,14 @@ class ExtractOtioAudioTracks(pyblish.api.ContextPlugin): audio_inputs.insert(0, empty) # create cmd - cmd = self.ffmpeg_path + " " + cmd = '"{}"'.format(self.ffmpeg_path) + " " cmd += self.create_cmd(audio_inputs) - cmd += audio_temp_fpath + cmd += "\"{}\"".format(audio_temp_fpath) # run subprocess self.log.debug("Executing: {}".format(cmd)) openpype.api.run_subprocess( - cmd, shell=True, logger=self.log + cmd, logger=self.log ) # remove empty @@ -97,17 +97,17 @@ class ExtractOtioAudioTracks(pyblish.api.ContextPlugin): audio_fpath = self.create_temp_file(name) cmd = " ".join([ - self.ffmpeg_path, + '"{}"'.format(self.ffmpeg_path), "-ss {}".format(start_sec), "-t {}".format(duration_sec), - "-i {}".format(audio_file), + "-i \"{}\"".format(audio_file), audio_fpath ]) # run subprocess self.log.debug("Executing: {}".format(cmd)) openpype.api.run_subprocess( - cmd, shell=True, logger=self.log + cmd, logger=self.log ) else: audio_fpath = recycling_file.pop() @@ -218,11 +218,11 @@ class ExtractOtioAudioTracks(pyblish.api.ContextPlugin): # create empty cmd cmd = " ".join([ - self.ffmpeg_path, + '"{}"'.format(self.ffmpeg_path), "-f lavfi", "-i anullsrc=channel_layout=stereo:sample_rate=48000", "-t {}".format(max_duration_sec), - empty_fpath + "\"{}\"".format(empty_fpath) ]) # generate empty with ffmpeg @@ -230,7 +230,7 @@ class ExtractOtioAudioTracks(pyblish.api.ContextPlugin): self.log.debug("Executing: {}".format(cmd)) openpype.api.run_subprocess( - cmd, shell=True, logger=self.log + cmd, logger=self.log ) # return dict with output diff --git a/openpype/plugins/publish/extract_otio_review.py b/openpype/plugins/publish/extract_otio_review.py index 07fe6f2731..2f46bcb375 100644 --- a/openpype/plugins/publish/extract_otio_review.py +++ b/openpype/plugins/publish/extract_otio_review.py @@ -209,7 +209,7 @@ class ExtractOTIOReview(openpype.api.Extractor): "frameStart": start, "frameEnd": end, "stagingDir": self.staging_dir, - "tags": ["review", "ftrackreview", "delete"] + "tags": ["review", "delete"] } collection = clique.Collection( @@ -313,7 +313,7 @@ class ExtractOTIOReview(openpype.api.Extractor): out_frame_start += end_offset # start command list - command = [ffmpeg_path] + command = ['"{}"'.format(ffmpeg_path)] if sequence: input_dir, collection = sequence @@ -326,7 +326,7 @@ class ExtractOTIOReview(openpype.api.Extractor): # form command for rendering gap files command.extend([ "-start_number {}".format(in_frame_start), - "-i {}".format(input_path) + "-i \"{}\"".format(input_path) ]) elif video: @@ -341,7 +341,7 @@ class ExtractOTIOReview(openpype.api.Extractor): command.extend([ "-ss {}".format(sec_start), "-t {}".format(sec_duration), - "-i {}".format(video_path) + "-i \"{}\"".format(video_path) ]) elif gap: @@ -360,11 +360,13 @@ class ExtractOTIOReview(openpype.api.Extractor): # add output attributes command.extend([ "-start_number {}".format(out_frame_start), - output_path + "\"{}\"".format(output_path) ]) # execute self.log.debug("Executing: {}".format(" ".join(command))) - output = openpype.api.run_subprocess(" ".join(command), shell=True) + output = openpype.api.run_subprocess( + " ".join(command), logger=self.log + ) self.log.debug("Output: {}".format(output)) def _generate_used_frames(self, duration, end_offset=None): From 5a5e40400c9af55bda0687b5088e45525ca7e8bb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 24 May 2021 19:06:30 +0200 Subject: [PATCH 19/21] create AvalonMongoDB on object creation --- .../event_handlers_user/action_delete_asset.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py b/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py index ff39db4383..cf658a3eea 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py @@ -11,23 +11,28 @@ from avalon.api import AvalonMongoDB class DeleteAssetSubset(BaseAction): '''Edit meta data action.''' - #: Action identifier. + # Action identifier. identifier = "delete.asset.subset" - #: Action label. + # Action label. label = "Delete Asset/Subsets" - #: Action description. + # Action description. description = "Removes from Avalon with all childs and asset from Ftrack" icon = statics_icon("ftrack", "action_icons", "DeleteAsset.svg") settings_key = "delete_asset_subset" - #: Db connection - dbcon = AvalonMongoDB() + # Db connection + dbcon = None splitter = {"type": "label", "value": "---"} action_data_by_id = {} asset_prefix = "asset:" subset_prefix = "subset:" + def __init__(self, *args, **kwargs): + self.dbcon = AvalonMongoDB() + + super(DeleteAssetSubset, self).__init__(*args, **kwargs) + def discover(self, session, entities, event): """ Validation """ task_ids = [] From e7eeb6fc57b6e58cacfd06c2a76b09f72452d943 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 24 May 2021 19:07:06 +0200 Subject: [PATCH 20/21] faster asset doc query --- .../ftrack/event_handlers_user/action_delete_asset.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py b/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py index cf658a3eea..ecb5b1e041 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py @@ -451,7 +451,14 @@ class DeleteAssetSubset(BaseAction): if len(assets_to_delete) > 0: map_av_ftrack_id = spec_data["without_ftrack_id"] # Prepare data when deleting whole avalon asset - avalon_assets = self.dbcon.find({"type": "asset"}) + avalon_assets = self.dbcon.find( + {"type": "asset"}, + { + "_id": 1, + "data.visualParent": 1, + "data.ftrackId": 1 + } + ) avalon_assets_by_parent = collections.defaultdict(list) for asset in avalon_assets: asset_id = asset["_id"] From ca5405abccad0b78a46d741632be382d4eae0f70 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 24 May 2021 19:07:44 +0200 Subject: [PATCH 21/21] entities are deleted by link length (higher length earlier removement) --- .../action_delete_asset.py | 34 +++++-------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py b/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py index ecb5b1e041..c20491349f 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py @@ -549,11 +549,13 @@ class DeleteAssetSubset(BaseAction): ftrack_proc_txt, ", ".join(ftrack_ids_to_delete) )) - ftrack_ents_to_delete = ( + entities_by_link_len = ( self._filter_entities_to_delete(ftrack_ids_to_delete, session) ) - for entity in ftrack_ents_to_delete: - session.delete(entity) + for link_len in sorted(entities_by_link_len.keys(), reverse=True): + for entity in entities_by_link_len[link_len]: + session.delete(entity) + try: session.commit() except Exception: @@ -612,29 +614,11 @@ class DeleteAssetSubset(BaseAction): joined_ids_to_delete ) ).all() - filtered = to_delete_entities[:] - while True: - changed = False - _filtered = filtered[:] - for entity in filtered: - entity_id = entity["id"] + entities_by_link_len = collections.defaultdict(list) + for entity in to_delete_entities: + entities_by_link_len[len(entity["link"])].append(entity) - for _entity in tuple(_filtered): - if entity_id == _entity["id"]: - continue - - for _link in _entity["link"]: - if entity_id == _link["id"] and _entity in _filtered: - _filtered.remove(_entity) - changed = True - break - - filtered = _filtered - - if not changed: - break - - return filtered + return entities_by_link_len def report_handle(self, report_messages, project_name, event): if not report_messages: