From 87ffe55fd5f704fd981b678f94f3570268025955 Mon Sep 17 00:00:00 2001 From: Julien Duroure Date: Tue, 17 Sep 2019 22:18:19 +0200 Subject: glTF importer: better multi-object animation management --- io_scene_gltf2/__init__.py | 2 +- .../blender/imp/gltf2_blender_animation.py | 32 ++-- .../blender/imp/gltf2_blender_animation_bone.py | 69 +------ .../blender/imp/gltf2_blender_animation_node.py | 203 ++++++++------------- .../blender/imp/gltf2_blender_animation_utils.py | 28 ++- .../blender/imp/gltf2_blender_animation_weight.py | 109 +++++++++++ io_scene_gltf2/blender/imp/gltf2_blender_gltf.py | 29 +++ io_scene_gltf2/blender/imp/gltf2_blender_scene.py | 21 ++- 8 files changed, 268 insertions(+), 225 deletions(-) create mode 100644 io_scene_gltf2/blender/imp/gltf2_blender_animation_weight.py (limited to 'io_scene_gltf2') diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py index 6fa115e8..48b49ed5 100755 --- a/io_scene_gltf2/__init__.py +++ b/io_scene_gltf2/__init__.py @@ -15,7 +15,7 @@ bl_info = { 'name': 'glTF 2.0 format', 'author': 'Julien Duroure, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors', - "version": (0, 9, 67), + "version": (0, 9, 68), 'blender': (2, 81, 6), 'location': 'File > Import-Export', 'description': 'Import-Export as glTF 2.0', diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_animation.py b/io_scene_gltf2/blender/imp/gltf2_blender_animation.py index 93014d00..6394145d 100755 --- a/io_scene_gltf2/blender/imp/gltf2_blender_animation.py +++ b/io_scene_gltf2/blender/imp/gltf2_blender_animation.py @@ -12,8 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +import bpy + from .gltf2_blender_animation_bone import BlenderBoneAnim from .gltf2_blender_animation_node import BlenderNodeAnim +from .gltf2_blender_animation_weight import BlenderWeightAnim +from .gltf2_blender_animation_utils import restore_animation_on_object class BlenderAnimation(): @@ -28,32 +32,28 @@ class BlenderAnimation(): BlenderBoneAnim.anim(gltf, anim_idx, node_idx) else: BlenderNodeAnim.anim(gltf, anim_idx, node_idx) + BlenderWeightAnim.anim(gltf, anim_idx, node_idx) if gltf.data.nodes[node_idx].children: for child in gltf.data.nodes[node_idx].children: BlenderAnimation.anim(gltf, anim_idx, child) @staticmethod - def stash_action(gltf, anim_idx, node_idx, action_name): + def restore_animation(gltf, node_idx, animation_name): + """Restores the actions for an animation by its track name on + the subtree starting at node_idx.""" + node = gltf.data.nodes[node_idx] - if gltf.data.nodes[node_idx].is_joint: - BlenderBoneAnim.stash_action(gltf, anim_idx, node_idx, action_name) + if node.is_joint: + obj = bpy.data.objects[gltf.data.skins[node.skin_id].blender_armature_name] else: - BlenderNodeAnim.stash_action(gltf, anim_idx, node_idx, action_name) - - if gltf.data.nodes[node_idx].children: - for child in gltf.data.nodes[node_idx].children: - BlenderAnimation.stash_action(gltf, anim_idx, child, action_name) + obj = bpy.data.objects[node.blender_object] - @staticmethod - def restore_last_action(gltf, node_idx): - - if gltf.data.nodes[node_idx].is_joint: - BlenderBoneAnim.restore_last_action(gltf, node_idx) - else: - BlenderNodeAnim.restore_last_action(gltf, node_idx) + restore_animation_on_object(obj, animation_name) + if obj.data and hasattr(obj.data, 'shape_keys'): + restore_animation_on_object(obj.data.shape_keys, animation_name) if gltf.data.nodes[node_idx].children: for child in gltf.data.nodes[node_idx].children: - BlenderAnimation.restore_last_action(gltf, child) + BlenderAnimation.restore_animation(gltf, child, animation_name) diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_animation_bone.py b/io_scene_gltf2/blender/imp/gltf2_blender_animation_bone.py index ef88bc37..74b907ae 100755 --- a/io_scene_gltf2/blender/imp/gltf2_blender_animation_bone.py +++ b/io_scene_gltf2/blender/imp/gltf2_blender_animation_bone.py @@ -18,7 +18,7 @@ from mathutils import Matrix from ..com.gltf2_blender_conversion import loc_gltf_to_blender, quaternion_gltf_to_blender, scale_to_matrix from ...io.imp.gltf2_io_binary import BinaryData -from .gltf2_blender_animation_utils import simulate_stash, restore_last_action +from .gltf2_blender_animation_utils import simulate_stash class BlenderBoneAnim(): @@ -40,31 +40,6 @@ class BlenderBoneAnim(): else: kf.interpolation = 'LINEAR' - @staticmethod - def stash_action(gltf, anim_idx, node_idx, action_name): - node = gltf.data.nodes[node_idx] - obj = bpy.data.objects[gltf.data.skins[node.skin_id].blender_armature_name] - - if anim_idx not in node.animations.keys(): - return - - if (obj.name, action_name) in gltf.actions_stashed.keys(): - return - - start_frame = bpy.context.scene.frame_start - - animation_name = gltf.data.animations[anim_idx].name - simulate_stash(obj, animation_name, bpy.data.actions[action_name], start_frame) - - gltf.actions_stashed[(obj.name, action_name)] = True - - @staticmethod - def restore_last_action(gltf, node_idx): - node = gltf.data.nodes[node_idx] - obj = bpy.data.objects[gltf.data.skins[node.skin_id].blender_armature_name] - - restore_last_action(obj) - @staticmethod def parse_translation_channel(gltf, node, obj, bone, channel, animation): """Manage Location animation.""" @@ -239,7 +214,8 @@ class BlenderBoneAnim(): def anim(gltf, anim_idx, node_idx): """Manage animation.""" node = gltf.data.nodes[node_idx] - obj = bpy.data.objects[gltf.data.skins[node.skin_id].blender_armature_name] + blender_armature_name = gltf.data.skins[node.skin_id].blender_armature_name + obj = bpy.data.objects[blender_armature_name] bone = obj.pose.bones[node.blender_bone_name] if anim_idx not in node.animations.keys(): @@ -247,38 +223,16 @@ class BlenderBoneAnim(): animation = gltf.data.animations[anim_idx] - if animation.name: - name = animation.name + "_" + obj.name - else: - name = "Animation_" + str(anim_idx) + "_" + obj.name - if len(name) >= 63: - # Name is too long to be kept, we are going to keep only animation name for now - name = animation.name - if len(name) >= 63: - # Very long name! - name = "Animation_" + str(anim_idx) - if name not in bpy.data.actions: + action = gltf.arma_cache.get(blender_armature_name) + if not action: + name = animation.track_name + "_" + obj.name action = bpy.data.actions.new(name) - else: - if name in gltf.animation_managed: - # multiple animation with same name in glTF file - # Create a new action with new name if needed - if name in gltf.current_animation_names.keys(): - action = bpy.data.actions[gltf.current_animation_names[name]] - name = gltf.current_animation_names[name] - else: - action = bpy.data.actions.new(name) - else: - action = bpy.data.actions[name] - # Check if this action has some users. - # If no user (only 1 indeed), that means that this action must be deleted - # (is an action from a deleted object) - if action.users == 1: - bpy.data.actions.remove(action) - action = bpy.data.actions.new(name) + gltf.needs_stash.append((obj, animation.track_name, action)) + gltf.arma_cache[blender_armature_name] = action + if not obj.animation_data: obj.animation_data_create() - obj.animation_data.action = bpy.data.actions[action.name] + obj.animation_data.action = action for channel_idx in node.animations[anim_idx]: channel = animation.channels[channel_idx] @@ -292,6 +246,3 @@ class BlenderBoneAnim(): elif channel.target.path == "scale": BlenderBoneAnim.parse_scale_channel(gltf, node, obj, bone, channel, animation) - if action.name not in gltf.current_animation_names.keys(): - gltf.current_animation_names[name] = action.name - diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_animation_node.py b/io_scene_gltf2/blender/imp/gltf2_blender_animation_node.py index cf570a20..0b020567 100755 --- a/io_scene_gltf2/blender/imp/gltf2_blender_animation_node.py +++ b/io_scene_gltf2/blender/imp/gltf2_blender_animation_node.py @@ -18,7 +18,7 @@ from mathutils import Vector from ..com.gltf2_blender_conversion import loc_gltf_to_blender, quaternion_gltf_to_blender, scale_gltf_to_blender from ..com.gltf2_blender_conversion import correction_rotation from ...io.imp.gltf2_io_binary import BinaryData -from .gltf2_blender_animation_utils import simulate_stash, restore_last_action +from .gltf2_blender_animation_utils import simulate_stash class BlenderNodeAnim(): @@ -40,31 +40,6 @@ class BlenderNodeAnim(): else: kf.interpolation = 'LINEAR' - @staticmethod - def stash_action(gltf, anim_idx, node_idx, action_name): - node = gltf.data.nodes[node_idx] - obj = bpy.data.objects[node.blender_object] - - if anim_idx not in node.animations.keys(): - return - - if (obj.name, action_name) in gltf.actions_stashed.keys(): - return - - start_frame = bpy.context.scene.frame_start - - animation_name = gltf.data.animations[anim_idx].name - simulate_stash(obj, animation_name, bpy.data.actions[action_name], start_frame) - - gltf.actions_stashed[(obj.name, action_name)] = True - - @staticmethod - def restore_last_action(gltf, node_idx): - node = gltf.data.nodes[node_idx] - obj = bpy.data.objects[node.blender_object] - - restore_last_action(obj) - @staticmethod def anim(gltf, anim_idx, node_idx): """Manage animation.""" @@ -72,31 +47,25 @@ class BlenderNodeAnim(): obj = bpy.data.objects[node.blender_object] fps = bpy.context.scene.render.fps + animation = gltf.data.animations[anim_idx] + if anim_idx not in node.animations.keys(): return - animation = gltf.data.animations[anim_idx] - - if animation.name: - name = animation.name + "_" + obj.name + for channel_idx in node.animations[anim_idx]: + channel = animation.channels[channel_idx] + if channel.target.path in ['translation', 'rotation', 'scale']: + break else: - name = "Animation_" + str(anim_idx) + "_" + obj.name - if len(name) >= 63: - # Name is too long to be kept, we are going to keep only animation name for now - name = animation.name - if len(name) >= 63: - # Very long name! - name = "Animation_" + str(anim_idx) + return + + name = animation.track_name + "_" + obj.name action = bpy.data.actions.new(name) - # Check if this action has some users. - # If no user (only 1 indeed), that means that this action must be deleted - # (is an action from a deleted object) - if action.users == 1: - bpy.data.actions.remove(action) - action = bpy.data.actions.new(name) + gltf.needs_stash.append((obj, animation.track_name, action)) + if not obj.animation_data: obj.animation_data_create() - obj.animation_data.action = bpy.data.actions[action.name] + obj.animation_data.action = action for channel_idx in node.animations[anim_idx]: channel = animation.channels[channel_idx] @@ -104,92 +73,64 @@ class BlenderNodeAnim(): keys = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].input) values = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].output) - if channel.target.path in ['translation', 'rotation', 'scale']: - - # There is an animation on object - # We can't remove Yup2Zup object - gltf.animation_object = True - - if animation.samplers[channel.sampler].interpolation == "CUBICSPLINE": - # TODO manage tangent? - values = [values[idx * 3 + 1] for idx in range(0, len(keys))] - - if channel.target.path == "translation": - blender_path = "location" - group_name = "Location" - num_components = 3 - values = [loc_gltf_to_blender(vals) for vals in values] - - elif channel.target.path == "rotation": - blender_path = "rotation_quaternion" - group_name = "Rotation" - num_components = 4 - if node.correction_needed is True: - values = [ - (quaternion_gltf_to_blender(vals).to_matrix().to_4x4() @ correction_rotation()).to_quaternion() - for vals in values - ] - else: - values = [quaternion_gltf_to_blender(vals) for vals in values] - - - # Manage antipodal quaternions - for i in range(1, len(values)): - if values[i].dot(values[i-1]) < 0: - values[i] = -values[i] - - elif channel.target.path == "scale": - blender_path = "scale" - group_name = "Scale" - num_components = 3 - values = [scale_gltf_to_blender(vals) for vals in values] - - coords = [0] * (2 * len(keys)) - coords[::2] = (key[0] * fps for key in keys) - - if group_name not in action.groups: - action.groups.new(group_name) - group = action.groups[group_name] - - for i in range(0, num_components): - fcurve = action.fcurves.new(data_path=blender_path, index=i) - fcurve.group = group - - fcurve.keyframe_points.add(len(keys)) - coords[1::2] = (vals[i] for vals in values) - fcurve.keyframe_points.foreach_set('co', coords) - - # Setting interpolation - for kf in fcurve.keyframe_points: - BlenderNodeAnim.set_interpolation(animation.samplers[channel.sampler].interpolation, kf) - fcurve.update() # force updating tangents (this may change when tangent will be managed) - - elif channel.target.path == 'weights': - - # retrieve number of targets - nb_targets = 0 - for prim in gltf.data.meshes[gltf.data.nodes[node_idx].mesh].primitives: - if prim.targets: - if len(prim.targets) > nb_targets: - nb_targets = len(prim.targets) - - if animation.samplers[channel.sampler].interpolation == "CUBICSPLINE": - factor = 3 - delta = nb_targets + if channel.target.path not in ['translation', 'rotation', 'scale']: + continue + + # There is an animation on object + # We can't remove Yup2Zup object + gltf.animation_object = True + + if animation.samplers[channel.sampler].interpolation == "CUBICSPLINE": + # TODO manage tangent? + values = [values[idx * 3 + 1] for idx in range(0, len(keys))] + + if channel.target.path == "translation": + blender_path = "location" + group_name = "Location" + num_components = 3 + values = [loc_gltf_to_blender(vals) for vals in values] + + elif channel.target.path == "rotation": + blender_path = "rotation_quaternion" + group_name = "Rotation" + num_components = 4 + if node.correction_needed is True: + values = [ + (quaternion_gltf_to_blender(vals).to_matrix().to_4x4() @ correction_rotation()).to_quaternion() + for vals in values + ] else: - factor = 1 - delta = 0 - - for idx, key in enumerate(keys): - for sk in range(nb_targets): - if gltf.shapekeys[sk] is not None: # Do not animate shapekeys not created - obj.data.shape_keys.key_blocks[gltf.shapekeys[sk]].value = values[factor * idx * nb_targets + delta + sk][0] - obj.data.shape_keys.key_blocks[gltf.shapekeys[sk]].keyframe_insert( - "value", - frame=key[0] * fps, - group='ShapeKeys' - ) - - if action.name not in gltf.current_animation_names.keys(): - gltf.current_animation_names[name] = action.name + values = [quaternion_gltf_to_blender(vals) for vals in values] + + + # Manage antipodal quaternions + for i in range(1, len(values)): + if values[i].dot(values[i-1]) < 0: + values[i] = -values[i] + + elif channel.target.path == "scale": + blender_path = "scale" + group_name = "Scale" + num_components = 3 + values = [scale_gltf_to_blender(vals) for vals in values] + + coords = [0] * (2 * len(keys)) + coords[::2] = (key[0] * fps for key in keys) + + if group_name not in action.groups: + action.groups.new(group_name) + group = action.groups[group_name] + + for i in range(0, num_components): + fcurve = action.fcurves.new(data_path=blender_path, index=i) + fcurve.group = group + + fcurve.keyframe_points.add(len(keys)) + coords[1::2] = (vals[i] for vals in values) + fcurve.keyframe_points.foreach_set('co', coords) + + # Setting interpolation + for kf in fcurve.keyframe_points: + BlenderNodeAnim.set_interpolation(animation.samplers[channel.sampler].interpolation, kf) + fcurve.update() # force updating tangents (this may change when tangent will be managed) diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_animation_utils.py b/io_scene_gltf2/blender/imp/gltf2_blender_animation_utils.py index 888d88a4..c835ceb7 100644 --- a/io_scene_gltf2/blender/imp/gltf2_blender_animation_utils.py +++ b/io_scene_gltf2/blender/imp/gltf2_blender_animation_utils.py @@ -12,7 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -def simulate_stash(obj, gltf_animation_name, action, start_frame): +import bpy + +def simulate_stash(obj, track_name, action, start_frame=None): # Simulate stash : # * add a track # * add an action on track @@ -20,20 +22,26 @@ def simulate_stash(obj, gltf_animation_name, action, start_frame): # * remove active action from object tracks = obj.animation_data.nla_tracks new_track = tracks.new(prev=None) - new_track.name = gltf_animation_name if gltf_animation_name is not None else action.name + new_track.name = track_name + if start_frame is None: + start_frame = bpy.context.scene.frame_start strip = new_track.strips.new(action.name, start_frame, action) new_track.lock = True new_track.mute = True obj.animation_data.action = None -def restore_last_action(obj): - - if not obj.animation_data: - return - tracks = obj.animation_data.nla_tracks - if len(tracks) == 0: +def restore_animation_on_object(obj, anim_name): + if not getattr(obj, 'animation_data', None): return - if len(tracks[0].strips) == 0: + + for track in obj.animation_data.nla_tracks: + if track.name != anim_name: + continue + if not track.strips: + continue + + obj.animation_data.action = track.strips[0].action return - obj.animation_data.action = tracks[0].strips[0].action + + obj.animation_data.action = None diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_animation_weight.py b/io_scene_gltf2/blender/imp/gltf2_blender_animation_weight.py new file mode 100644 index 00000000..baa0d95e --- /dev/null +++ b/io_scene_gltf2/blender/imp/gltf2_blender_animation_weight.py @@ -0,0 +1,109 @@ +# Copyright 2018-2019 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import bpy + +from ...io.imp.gltf2_io_binary import BinaryData +from .gltf2_blender_animation_utils import simulate_stash + + +class BlenderWeightAnim(): + """Blender ShapeKey Animation.""" + def __new__(cls, *args, **kwargs): + raise RuntimeError("%s should not be instantiated" % cls) + + @staticmethod + def set_interpolation(interpolation, kf): + """Manage interpolation.""" + if interpolation == "LINEAR": + kf.interpolation = 'LINEAR' + elif interpolation == "STEP": + kf.interpolation = 'CONSTANT' + elif interpolation == "CUBICSPLINE": + kf.interpolation = 'BEZIER' + kf.handle_right_type = 'AUTO' + kf.handle_left_type = 'AUTO' + else: + kf.interpolation = 'LINEAR' + + @staticmethod + def anim(gltf, anim_idx, node_idx): + """Manage animation.""" + node = gltf.data.nodes[node_idx] + obj = bpy.data.objects[node.blender_object] + fps = bpy.context.scene.render.fps + + animation = gltf.data.animations[anim_idx] + + if anim_idx not in node.animations.keys(): + return + + for channel_idx in node.animations[anim_idx]: + channel = animation.channels[channel_idx] + if channel.target.path == "weights": + break + else: + return + + name = animation.track_name + "_" + obj.name + action = bpy.data.actions.new(name) + action.id_root = "KEY" + gltf.needs_stash.append((obj.data.shape_keys, animation.track_name, action)) + + if not obj.data.shape_keys.animation_data: + obj.data.shape_keys.animation_data_create() + obj.data.shape_keys.animation_data.action = action + + keys = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].input) + values = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].output) + + # retrieve number of targets + nb_targets = 0 + for prim in gltf.data.meshes[gltf.data.nodes[node_idx].mesh].primitives: + if prim.targets: + if len(prim.targets) > nb_targets: + nb_targets = len(prim.targets) + + if animation.samplers[channel.sampler].interpolation == "CUBICSPLINE": + offset = nb_targets + stride = 3 * nb_targets + else: + offset = 0 + stride = nb_targets + + coords = [0] * (2 * len(keys)) + coords[::2] = (key[0] * fps for key in keys) + + group_name = "ShapeKeys" + if group_name not in action.groups: + action.groups.new(group_name) + group = action.groups[group_name] + + for sk in range(nb_targets): + if gltf.shapekeys[sk] is not None: # Do not animate shapekeys not created + kb_name = obj.data.shape_keys.key_blocks[gltf.shapekeys[sk]].name + data_path = "key_blocks[" + json.dumps(kb_name) + "].value" + fcurve = action.fcurves.new(data_path=data_path) + fcurve.group = group + + fcurve.keyframe_points.add(len(keys)) + coords[1::2] = (values[offset + stride * i + sk][0] for i in range(len(keys))) + fcurve.keyframe_points.foreach_set('co', coords) + + # Setting interpolation + for kf in fcurve.keyframe_points: + BlenderWeightAnim.set_interpolation(animation.samplers[channel.sampler].interpolation, kf) + fcurve.update() # force updating tangents (this may change when tangent will be managed) + diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_gltf.py b/io_scene_gltf2/blender/imp/gltf2_blender_gltf.py index 53b200b2..0f603bf3 100755 --- a/io_scene_gltf2/blender/imp/gltf2_blender_gltf.py +++ b/io_scene_gltf2/blender/imp/gltf2_blender_gltf.py @@ -256,7 +256,14 @@ class BlenderGlTF(): for node_idx, node in enumerate(gltf.data.nodes): node.animations = {} + track_names = set() for anim_idx, anim in enumerate(gltf.data.animations): + # Pick pair-wise unique name for each animation to use as a name + # for its NLA tracks. + desired_name = anim.name or "Anim_%d" % anim_idx + anim.track_name = BlenderGlTF.find_unused_name(track_names, desired_name) + track_names.add(anim.track_name) + for channel_idx, channel in enumerate(anim.channels): if channel.target.node is None: continue @@ -274,3 +281,25 @@ class BlenderGlTF(): mesh.blender_name = None mesh.is_weight_animated = False + @staticmethod + def find_unused_name(haystack, desired_name): + """Finds a name not in haystack and <= 63 UTF-8 bytes. + (the limit on the size of a Blender name.) + If a is taken, tries a.001, then a.002, etc. + """ + stem = desired_name[:63] + suffix = '' + cntr = 1 + while True: + name = stem + suffix + + if len(name.encode('utf-8')) > 63: + stem = stem[:-1] + continue + + if name not in haystack: + return name + + suffix = '.%03d' % cntr + cntr += 1 + diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_scene.py b/io_scene_gltf2/blender/imp/gltf2_blender_scene.py index 6e84aac5..aa9684c7 100755 --- a/io_scene_gltf2/blender/imp/gltf2_blender_scene.py +++ b/io_scene_gltf2/blender/imp/gltf2_blender_scene.py @@ -18,6 +18,7 @@ from mathutils import Quaternion from .gltf2_blender_node import BlenderNode from .gltf2_blender_skin import BlenderSkin from .gltf2_blender_animation import BlenderAnimation +from .gltf2_blender_animation_utils import simulate_stash class BlenderScene(): @@ -93,19 +94,23 @@ class BlenderScene(): BlenderSkin.create_armature_modifiers(gltf, skin_id) if gltf.data.animations: - gltf.animation_managed = [] for anim_idx, anim in enumerate(gltf.data.animations): - gltf.current_animation_names = {} - gltf.actions_stashed= {} + # Blender armature name -> action all its bones should use + gltf.arma_cache = {} + # Things we need to stash when we're done. + gltf.needs_stash = [] + if list_nodes is not None: for node_idx in list_nodes: BlenderAnimation.anim(gltf, anim_idx, node_idx) - for an in gltf.current_animation_names.values(): - gltf.animation_managed.append(an) - for node_idx in list_nodes: - BlenderAnimation.stash_action(gltf, anim_idx, node_idx, an) + + for (obj, anim_name, action) in gltf.needs_stash: + simulate_stash(obj, anim_name, action) + + # Restore first animation + anim_name = gltf.data.animations[0].track_name for node_idx in list_nodes: - BlenderAnimation.restore_last_action(gltf, node_idx) + BlenderAnimation.restore_animation(gltf, node_idx, anim_name) if bpy.app.debug_value != 100: # Parent root node to rotation object -- cgit v1.2.3