mail me

Combining animation tracks and strips into a single animation strip for scene-wide animation GLTF export

Author: Christoffer Blomvik
Material: Article
Published: 04.08.23
Keywords: blender, addon, add-on, game dev, gltf, gltf 2.0, animation, strip, track
View table of contents

Abstract

Blender can be used to fully animate cinematic sequences within game engines by the use of GLTFs. However, when importing a GLTF file only a single animation can play with the use of AnimationPlayers within Godot within a scene object. This means, if a GLTF file has multiple animation strips to drive different objects and characters within the scene then only one of these objects can be animated at the time. To get around this, all the animation strips within the Blender files need to be combined into a single strip upon export. The GLTF exporter inside Blender is able to do this, but it needs concrete parameters in order to work. This article will list these parameters.

Function parameters

To be brief, the parameters you are after are the following:

bpy.ops.export_scene.gltf(filepath=save_location, use_selection=False, export_animations=True, export_animation_mode='SCENE', export_anim_scene_split_object=False, export_nla_strips=False)

Parameters explained

bpy.ops.export_scene.gltf(filepath='')

This is where the file will be stored. In the function definition, you may pass in a variable that is available from the Blender GUI so it won’t have to be hardcoded. An example of this can be seen in the script at the bottom of the article.

use_selection=False

This is to not limit the object to export to the current selection of objects in the scene. It will then export every object in the scene, which is what we want with this script.

export_animations=True

This is to include the animations within the GLTF file. If this is off then we have no animations to import in Godot, for instance.

export_animation_mode='SCENE'

This tells the exporter to export the scene as a single baked animation strip. So no matter how many strips you have assigned to a number of different objects within the NLA editor, this will all be combined into a single animation strip in the GLTF file.

export_anim_scene_split_object=False

This parameter, if true, would combine any animation strip assigned to an object’s animation track into a single animation strip and take the name of its object. I.e, this would result in multiple animation strips in the GLTF file, which is not what we want. Therefore, we set this to False.

export_nla_strips=False

By having this to false, all current assigned actions become one GLTF animation strip. Else, this would combine all animations on a single track into a single animation strip by name, and that would result in multiple animation strips in the GLTF file.

Add-on and script

An image of the add-on, showing the viewport with the addon active in the side-bar, which shows the buttons and UI elements of the addon

Here’s a small script which create a GUI element with a few buttons and a few more parameters than the example above, such as setting check_existing to False to simply override existing files without needing to press a confirmation message. The use_active_collection=True makes it so only the active collections are exported (note, however, that they are exported individually and not combined), use_active_collection_with_nested=True makes it possible to export collections that are nested under the active collection.

You may, however, want to look over other parameters to get the exporter the way you want, and you can find all the parameters in the blender docs.

bl_info = {

    "name": "Scene to one-strip GLTF",

    "blender": (2, 80, 0),

    "author": "Christoffer Blomvik (mail@destroy.host)",

    "version": (0, 1),

    "category": "3D View",

}

import bpy, sys, os, random

from bpy.props import (StringProperty, BoolProperty, EnumProperty)

       

from mathutils import Matrix, Vector

#----------------- UI parameters

bpy.types.Scene.FilePath = StringProperty(

    name="",

    subtype='DIR_PATH',

    description="The absolute path where the files will be saved",

)

bpy.types.Scene.file_name = StringProperty(

    name="",

    description="The exported file will have this name, rather than the name of the collection that is active. When exporting multiple objects, this will result in each export overriding the previous",

)

bpy.types.Scene.eGLTFFormat = EnumProperty(

    name="",

    description="GLTF file format",

    items=[

        ('GLB', "GLTF Binary", "Exports a single file, with all data packed in binary form. Most efficient and portable, but more difficult to edit later"),

        ('GLTF_SEPARATE', "GLTF Separate", "Exports multiple files, with separate JSON, binary and texture data. Easiest to edit later"),

        ('GLTF_EMBEDDED', "GLTF Embedded", "Exports a single file, with all data packed in JSON. Less efficient than binary, but easier to edit later"),

    ],

    default=0

)

bpy.types.Scene.bOverrideFileName = BoolProperty(

    name="Override File Name",

    description="Enable to override the file name",

    default = False

)

bpy.types.Scene.bExportCameras = BoolProperty(

    name="Export cameras",

    description="Export selected cameras",

    default = True

)

class MESH_OT_quick_export_scene(bpy.types.Operator):

    bl_idname = 'mesh.quick_export_scene'

    bl_label = "Export Object(s)"

    bl_description = "Exports all objects and animations in the scene to one GLTF file with all animations"

   

    def execute(self, context):    

        if bpy.context.scene.FilePath == "":

            return {'FINISHED'}

       

        export_scene()

        bpy.context.view_layer.update()    

        return {'FINISHED'}

def export_scene(context = bpy.context):

    oldLoc = bpy.context.scene.FilePath

    if bpy.context.scene.bOverrideFileName == False:

        saveLoc = bpy.context.scene.FilePath + bpy.context.collection.name

    else:

        if context.scene.file_name == '':

            context.scene.file_name = bpy.context.collection.name

        saveLoc = bpy.context.scene.FilePath + context.scene.file_name

    export_to_gltf(saveLoc)

def export_to_gltf(save_location, context = bpy.context):

    bpy.ops.export_scene.gltf(filepath=save_location, check_existing=False, use_selection=False, export_animations=True, export_animation_mode='SCENE', export_anim_scene_split_object=False, export_nla_strips=False, export_cameras=context.scene.bExportCameras, use_active_collection=True, use_active_collection_with_nested=True)

class PANEL_PT_file_stack(bpy.types.Panel):

    bl_idname = "PANEL_PT_file_setting"

    bl_space_type = 'VIEW_3D'

    bl_region_type = 'UI'

    bl_category = "Scene to one-strip GLTF"

    bl_label = "File Settings"

   

    def draw (self, context):

        layout = self.layout

        sce = context.scene

       

        col = layout.column(align=True)

        col.prop(sce, "bOverrideFileName")

        if context.scene.bOverrideFileName == True:

            col.prop(sce, "file_name")

        col = layout.column(align=True)

        col.label(text="File path")

        col.prop(sce, "FilePath")

       

        col = layout.column(align=True)

        col.label(text="GLTF format")

        col.prop(sce, "eGLTFFormat")

           

        col = layout.column(align=True)

        col.operator('mesh.quick_export_scene')

       

class PANEL_PT_animation_stack(bpy.types.Panel):

    bl_idname = "PANEL_PT_animation_settings"

    bl_space_type = 'VIEW_3D'

    bl_region_type = 'UI'

    bl_category = "Scene to one-strip GLTF"

    bl_label = "Animation Settings"

   

    def draw (self, context):

        layout = self.layout

        sce = context.scene

       

        col = layout.column(align=True)

        col.prop(sce, "bExportCameras")

       

   

def register():

    bpy.utils.register_class(MESH_OT_quick_export_scene)

    bpy.utils.register_class(PANEL_PT_file_stack)

    bpy.utils.register_class(PANEL_PT_animation_stack)

       

def unregister():

    bpy.utils.unregister_class(MESH_OT_quick_export_scene)

    bpy.utils.unregister_class(PANEL_PT_file_stack)

    bpy.utils.unregister_class(PANEL_PT_animation_stack)

#uncomment these following lines if you want to make changes to the script and run it with ALT+P. Remember to save your script with ALT+S when you are done.

#if __name__ == "__main__":

    #register()