From 843fa6f6e08db1a6934b50d0be9739f627ccefb9 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 14 May 2021 17:51:24 +0100 Subject: [PATCH 1/4] 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 2/4] 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 3/4] 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 4/4] 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(