Layout now includes animations

This commit is contained in:
Simone Barbieri 2021-10-13 17:29:31 +01:00
parent 11a576a0ed
commit db23c995e2
3 changed files with 271 additions and 38 deletions

View file

@ -110,6 +110,8 @@ class BlendRigLoader(plugin.AssetLoader):
plugin.prepare_data(local_obj.data, group_name)
if action is not None:
if local_obj.animation_data is None:
local_obj.animation_data_create()
local_obj.animation_data.action = action
elif (local_obj.animation_data and
local_obj.animation_data.action is not None):

View file

@ -2,9 +2,12 @@ import os
import json
import bpy
import bpy_extras
import bpy_extras.anim_utils
from avalon import io
from avalon.blender.pipeline import AVALON_PROPERTY
from openpype.hosts.blender.api import plugin
import openpype.api
@ -16,6 +19,99 @@ class ExtractLayout(openpype.api.Extractor):
families = ["layout"]
optional = True
def _export_animation(self, asset, instance, stagingdir):
file_names = []
for obj in asset.children:
if obj.type != "ARMATURE":
continue
asset_group_name = asset.name
asset.name = asset.get(AVALON_PROPERTY).get("asset_name")
armature_name = obj.name
original_name = armature_name.split(':')[1]
obj.name = original_name
object_action_pairs = []
original_actions = []
starting_frames = []
ending_frames = []
# For each armature, we make a copy of the current action
curr_action = None
copy_action = None
if obj.animation_data and obj.animation_data.action:
curr_action = obj.animation_data.action
copy_action = curr_action.copy()
curr_frame_range = curr_action.frame_range
starting_frames.append(curr_frame_range[0])
ending_frames.append(curr_frame_range[1])
else:
self.log.info("Object have no animation.")
continue
object_action_pairs.append((obj, copy_action))
original_actions.append(curr_action)
# We compute the starting and ending frames
max_frame = min(starting_frames)
min_frame = max(ending_frames)
# We bake the copy of the current action for each object
bpy_extras.anim_utils.bake_action_objects(
object_action_pairs,
frames=range(int(min_frame), int(max_frame)),
do_object=False,
do_clean=False
)
for o in bpy.data.objects:
o.select_set(False)
asset.select_set(True)
obj.select_set(True)
fbx_filename = f"{instance.name}_{asset_group_name}.fbx"
filepath = os.path.join(stagingdir, fbx_filename)
override = plugin.create_blender_context(
active=asset, selected=[asset, obj])
bpy.ops.export_scene.fbx(
override,
filepath=filepath,
use_active_collection=False,
use_selection=True,
bake_anim_use_nla_strips=False,
bake_anim_use_all_actions=False,
add_leaf_bones=False,
armature_nodetype='ROOT',
object_types={'EMPTY', 'ARMATURE'}
)
obj.name = armature_name
asset.name = asset_group_name
asset.select_set(False)
obj.select_set(False)
# 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])
file_names.append(fbx_filename)
return file_names
def process(self, instance):
# Define extract output file path
stagingdir = self.staging_dir(instance)
@ -23,7 +119,11 @@ class ExtractLayout(openpype.api.Extractor):
# Perform extraction
self.log.info("Performing extraction..")
if "representations" not in instance.data:
instance.data["representations"] = []
json_data = []
fbx_files = []
asset_group = bpy.data.objects[str(instance)]
@ -78,22 +178,32 @@ class ExtractLayout(openpype.api.Extractor):
}
json_data.append(json_element)
# Extract the animation as well
if family == "rig":
fbx_files.extend(
self._export_animation(
asset, instance, stagingdir))
json_filename = "{}.json".format(instance.name)
json_path = os.path.join(stagingdir, json_filename)
with open(json_path, "w+") as file:
json.dump(json_data, fp=file, indent=2)
if "representations" not in instance.data:
instance.data["representations"] = []
representation = {
json_representation = {
'name': 'json',
'ext': 'json',
'files': json_filename,
"stagingDir": stagingdir,
}
instance.data["representations"].append(representation)
fbx_representation = {
'name': 'fbx',
'ext': 'fbx',
'files': fbx_files,
"stagingDir": stagingdir,
}
instance.data["representations"].append(json_representation)
instance.data["representations"].append(fbx_representation)
self.log.info("Extracted instance '%s' to: %s",
instance.name, representation)
instance.name, json_representation)

View file

@ -1,9 +1,12 @@
import os
import json
from pathlib import Path
import unreal
from unreal import EditorAssetLibrary
from unreal import EditorLevelLibrary
from unreal import AssetToolsHelpers
from unreal import FBXImportType
from unreal import MathLibrary as umath
from avalon import api, pipeline
@ -58,6 +61,8 @@ class LayoutLoader(api.Loader):
def _process_family(self, assets, classname, transform):
ar = unreal.AssetRegistryHelpers.get_asset_registry()
actors = []
for asset in assets:
obj = ar.get_asset_by_object_path(asset).get_asset()
if obj.get_class().get_name() == classname:
@ -75,7 +80,91 @@ class LayoutLoader(api.Loader):
), False)
actor.set_actor_scale3d(transform.get('scale'))
actors.append(actor)
return actors
def _import_animation(
self, asset_dir, path, rig_count, instance_name, skeleton,
actors_dict):
anim_path = f"{asset_dir}/animations/{path.with_suffix('').name}_{rig_count:02d}"
# Import animation
task = unreal.AssetImportTask()
task.options = unreal.FbxImportUI()
task.set_editor_property(
'filename', str(path.with_suffix(f".{rig_count:02d}.fbx")))
task.set_editor_property('destination_path', anim_path)
task.set_editor_property(
'destination_name', f"{instance_name}_animation")
task.set_editor_property('replace_existing', False)
task.set_editor_property('automated', True)
task.set_editor_property('save', False)
# set import options here
task.options.set_editor_property(
'automated_import_should_detect_type', False)
task.options.set_editor_property(
'original_import_type', FBXImportType.FBXIT_SKELETAL_MESH)
task.options.set_editor_property(
'mesh_type_to_import', 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.set_editor_property('skeleton', skeleton)
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)
AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
asset_content = unreal.EditorAssetLibrary.list_assets(
anim_path, recursive=False, include_folder=False
)
animation = None
for a in asset_content:
unreal.EditorAssetLibrary.save_asset(a)
imported_asset_data = unreal.EditorAssetLibrary.find_asset_data(a)
imported_asset = unreal.AssetRegistryHelpers.get_asset(
imported_asset_data)
if imported_asset.__class__ == unreal.AnimSequence:
animation = imported_asset
break
if animation:
actor = None
if actors_dict.get(instance_name):
for a in actors_dict.get(instance_name):
if a.get_class().get_name() == 'SkeletalMeshActor':
actor = a
break
animation.set_editor_property('enable_root_motion', True)
actor.skeletal_mesh_component.set_editor_property(
'animation_mode', unreal.AnimationMode.ANIMATION_SINGLE_NODE)
actor.skeletal_mesh_component.animation_data.set_editor_property(
'anim_to_play', animation)
def _process(self, libpath, asset_dir, loaded=None):
ar = unreal.AssetRegistryHelpers.get_asset_registry()
with open(libpath, "r") as fp:
data = json.load(fp)
@ -84,46 +173,78 @@ class LayoutLoader(api.Loader):
if not loaded:
loaded = []
path = Path(libpath)
rig_count = 0
skeleton_dict = {}
actors_dict = {}
for element in data:
reference = element.get('reference_fbx')
if reference in loaded:
continue
loaded.append(reference)
family = element.get('family')
loaders = api.loaders_from_representation(
all_loaders, reference)
loader = self._get_loader(loaders, family)
if not loader:
continue
instance_name = element.get('instance_name')
options = {
"asset_dir": asset_dir
}
skeleton = None
assets = api.load(
loader,
reference,
namespace=instance_name,
options=options
)
if reference not in loaded:
loaded.append(reference)
instances = [
item for item in data
if item.get('reference_fbx') == reference]
family = element.get('family')
loaders = api.loaders_from_representation(
all_loaders, reference)
loader = self._get_loader(loaders, family)
for instance in instances:
transform = instance.get('transform')
if not loader:
continue
if family == 'model':
self._process_family(assets, 'StaticMesh', transform)
elif family == 'rig':
self._process_family(assets, 'SkeletalMesh', transform)
options = {
"asset_dir": asset_dir
}
assets = api.load(
loader,
reference,
namespace=instance_name,
options=options
)
instances = [
item for item in data
if item.get('reference_fbx') == reference]
for instance in instances:
transform = instance.get('transform')
inst = instance.get('instance_name')
actors = []
if family == 'model':
actors = self._process_family(
assets, 'StaticMesh', transform)
elif family == 'rig':
actors = self._process_family(
assets, 'SkeletalMesh', transform)
actors_dict[inst] = actors
if family == 'rig':
# Finds skeleton among the imported assets
for asset in assets:
obj = ar.get_asset_by_object_path(asset).get_asset()
if obj.get_class().get_name() == 'Skeleton':
skeleton = obj
if skeleton:
break
if skeleton:
skeleton_dict[reference] = skeleton
else:
skeleton = skeleton_dict.get(reference)
if skeleton:
self._import_animation(
asset_dir, path, rig_count, instance_name, skeleton,
actors_dict)
rig_count += 1
def _remove_family(self, assets, components, classname, propname):
ar = unreal.AssetRegistryHelpers.get_asset_registry()