From bce22acf35ef1156a96b418450311e2354eabff7 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 30 Sep 2020 17:11:32 +0100 Subject: [PATCH] Implemented extraction of the animation and setdress --- .../publish/extract_animation_collection.py | 56 ++++++++++++ .../blender/publish/extract_fbx_animation.py | 89 +++++++++---------- .../blender/publish/integrate_animation.py | 49 ++++++++++ 3 files changed, 148 insertions(+), 46 deletions(-) create mode 100644 pype/plugins/blender/publish/extract_animation_collection.py create mode 100644 pype/plugins/blender/publish/integrate_animation.py diff --git a/pype/plugins/blender/publish/extract_animation_collection.py b/pype/plugins/blender/publish/extract_animation_collection.py new file mode 100644 index 0000000000..e5e0877280 --- /dev/null +++ b/pype/plugins/blender/publish/extract_animation_collection.py @@ -0,0 +1,56 @@ +import os +import json + +import pype.api +import pyblish.api + +import bpy + +class ExtractSetDress(pype.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 = {} + json_dict['subset'] = i.data.get('subset') + json_dict['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/pype/plugins/blender/publish/extract_fbx_animation.py b/pype/plugins/blender/publish/extract_fbx_animation.py index d51c641e9c..9c421560f0 100644 --- a/pype/plugins/blender/publish/extract_fbx_animation.py +++ b/pype/plugins/blender/publish/extract_fbx_animation.py @@ -17,14 +17,10 @@ class ExtractAnimationFBX(pype.api.Extractor): def process(self, instance): # Define extract output file path - stagingdir = self.staging_dir(instance) - filename = f"{instance.name}.fbx" - filepath = os.path.join(stagingdir, filename) context = bpy.context scene = context.scene - view_layer = context.view_layer # Perform extraction self.log.info("Performing extraction..") @@ -35,22 +31,6 @@ class ExtractAnimationFBX(pype.api.Extractor): assert len(collections) == 1, "There should be one and only one " \ "collection collected for this asset" - old_active_layer_collection = view_layer.active_layer_collection - - layers = view_layer.layer_collection.children - - # Get the layer collection from the collection we need to export. - # This is needed because in Blender you can only set the active - # collection with the layer collection, and there is no way to get - # the layer collection from the collection - # (but there is the vice versa). - layer_collections = [ - layer for layer in layers if layer.collection == collections[0]] - - assert len(layer_collections) == 1 - - view_layer.active_layer_collection = layer_collections[0] - old_scale = scene.unit_settings.scale_length # We set the scale of the scene for the export @@ -59,6 +39,15 @@ class ExtractAnimationFBX(pype.api.Extractor): armatures = [ obj for obj in collections[0].objects if obj.type == 'ARMATURE'] + assert len(collections) == 1, "There should be one and only one " \ + "armature collected for this asset" + + armature = armatures[0] + + armature_name = armature.name + original_name = armature_name.split(':')[0] + armature.name = original_name + object_action_pairs = [] original_actions = [] @@ -66,23 +55,23 @@ class ExtractAnimationFBX(pype.api.Extractor): ending_frames = [] # For each armature, we make a copy of the current action - for obj in armatures: + curr_action = None + copy_action = None - curr_action = None - copy_action = None + if armature.animation_data and armature.animation_data.action: + curr_action = armature.animation_data.action + copy_action = curr_action.copy() - if obj.animation_data and obj.animation_data.action: + curr_frame_range = curr_action.frame_range - curr_action = obj.animation_data.action - copy_action = curr_action.copy() + starting_frames.append(curr_frame_range[0]) + ending_frames.append(curr_frame_range[1]) + else: + self.log.info("Object have no animation.") + return - curr_frame_range = curr_action.frame_range - - starting_frames.append(curr_frame_range[0]) - ending_frames.append(curr_frame_range[1]) - - object_action_pairs.append((obj, copy_action)) - original_actions.append(curr_action) + object_action_pairs.append((armature, copy_action)) + original_actions.append(curr_action) # We compute the starting and ending frames max_frame = min(starting_frames) @@ -96,44 +85,52 @@ class ExtractAnimationFBX(pype.api.Extractor): do_clean=False ) - # We export the fbx + for obj in bpy.data.objects: + obj.select_set(False) + + armature.select_set(True) + fbx_filename = f"{instance.name}_{armature.name}.fbx" + filepath = os.path.join(stagingdir, fbx_filename) + + override = bpy.context.copy() + override['selected_objects'] = [armature] bpy.ops.export_scene.fbx( + override, filepath=filepath, - use_active_collection=True, + use_selection=True, bake_anim_use_nla_strips=False, bake_anim_use_all_actions=False, - add_leaf_bones=False + add_leaf_bones=False, + armature_nodetype='ROOT', + object_types={'ARMATURE'} ) - - view_layer.active_layer_collection = old_active_layer_collection + armature.name = armature_name + armature.select_set(False) scene.unit_settings.scale_length = old_scale # We delete the baked action and set the original one back for i in range(0, len(object_action_pairs)): - pair = object_action_pairs[i] action = original_actions[i] if action: - pair[0].animation_data.action = action if pair[1]: - pair[1].user_clear() bpy.data.actions.remove(pair[1]) if "representations" not in instance.data: instance.data["representations"] = [] - representation = { + fbx_representation = { 'name': 'fbx', 'ext': 'fbx', - 'files': filename, + 'files': fbx_filename, "stagingDir": stagingdir, } - instance.data["representations"].append(representation) + instance.data["representations"].append(fbx_representation) - self.log.info("Extracted instance '%s' to: %s", - instance.name, representation) + self.log.info("Extracted instance '{}' to: {}".format( + instance.name, fbx_representation)) diff --git a/pype/plugins/blender/publish/integrate_animation.py b/pype/plugins/blender/publish/integrate_animation.py new file mode 100644 index 0000000000..90e94a4aac --- /dev/null +++ b/pype/plugins/blender/publish/integrate_animation.py @@ -0,0 +1,49 @@ +import json + +from avalon import io +import pyblish.api + + +class IntegrateAnimation(pyblish.api.InstancePlugin): + """Generate a JSON file for animation.""" + + label = "Integrate Animation" + order = pyblish.api.IntegratorOrder + 0.1 + optional = True + hosts = ["blender"] + families = ["setdress"] + + def process(self, instance): + self.log.info("Integrate Animation") + + representation = instance.data.get('representations')[0] + json_path = representation.get('publishedFiles')[0] + + with open(json_path, "r") as file: + data = json.load(file) + + # Update the json file for the setdress to add the published + # representations of the animations + for json_dict in data: + i = None + for elem in instance.context: + if elem.data.get('subset') == json_dict['subset']: + i = elem + break + if not i: + continue + rep = None + pub_repr = i.data.get('published_representations') + for elem in pub_repr: + if pub_repr.get(elem).get('representation').get('name') == "fbx": + rep = pub_repr.get(elem) + break + if not rep: + continue + obj_id = rep.get('representation').get('_id') + + if obj_id: + json_dict['_id'] = str(obj_id) + + with open(json_path, "w") as file: + json.dump(data, fp=file, indent=2)