4
$\begingroup$

Is it possible to store each animation frame as a new object? I searched quite a long time now on google and was wondering that I cannot find any information about this.

I just have a simple model with a simple pose animation and I'm looking for a way to get each frame stored as a new object, that I end up with maybe 100 objects "frame001", "frame002", "frame003", ...

I need this for a game engine that does not support bone animations, but I can fake such an animation by exporting this object (as a collada) and then setting only one of the "frame-objects" visible in this engine.


I tried to write a quick and dirty python script myself after some basic reading, but unfortunately the most important action (the conversion to a mesh) does not work:

import bpy

count=0

for x in range(bpy.data.scenes["Scene"].frame_start,bpy.data.scenes["Scene"].frame_end):
    bpy.ops.object.duplicate_move()

bpy.ops.object.select_all(action='DESELECT')

obs = bpy.data.objects
bpy.data.scenes["Scene"].frame_current=0        
for ob in obs:
    bpy.ops.object.select_all(action='DESELECT')
    if ob.type == 'MESH' and ob.name.startswith("model"):
        if count>0:
            ob.select=True
            bpy.ops.object.convert(target='MESH')
            bpy.data.scenes["Scene"].frame_current+=1
        count=count+1
$\endgroup$
4
  • $\begingroup$ I've added python to the tags since I think you'll need this. $\endgroup$ Commented Nov 30, 2014 at 11:29
  • $\begingroup$ I've tried that but the collada exporter always jumped to the last frame. $\endgroup$
    – stacker
    Commented Nov 30, 2014 at 12:59
  • $\begingroup$ .obj animation export should work fine. Is this an option for you? $\endgroup$
    – p2or
    Commented Nov 30, 2014 at 16:25
  • 1
    $\begingroup$ Blender has an exporter for "Lightwave Motion Designer (MDD)". If you can't use the file format the script may help $\endgroup$
    – sambler
    Commented Dec 1, 2014 at 11:08

2 Answers 2

3
$\begingroup$

You should avoid operators whenever possible:

import bpy

scene = bpy.context.scene
frame_current = scene.frame_current

for f in range(scene.frame_start, scene.frame_end + 1):
    scene.frame_set(f)

    meshes = []
    for ob in scene.objects:
        if ob.type == 'MESH' and ob.name.startswith("model"):
            me = ob.to_mesh(scene, True, 'PREVIEW')
            meshes.append(me)

    # do something with the meshes (note: they aren't linked to anything)

    # Clean up mesh copies
    for me in meshes:
        bpy.data.meshes.remove(me)

scene.frame_set(frame_current)

This will apply modifiers to all mesh objects in the current scene that start with the name model. The resulting (new!) mesh datablocks can be used for export.

Note however, that you will have to create objects and link the meshes to them if you plan to call an export operator - they usually export all (selected) objects, not invisible mesh datablocks. You may wanna check out the exporter code and see if you can import the actual export routine to avoid that operator call and maybe even the object creation.


If you want to apply animation in a way, that there's one static object per frame of an animated model, you may use the following script:

import bpy

scene = bpy.context.scene
frame_current = scene.frame_current

r = range(scene.frame_start, scene.frame_end + 1)
l = len(str(len(r)))
obs = []

for f in r:
    scene.frame_set(f)

    for ob in scene.objects:
        if ob.type == 'MESH' and ob.name.startswith("model"):
            me = ob.to_mesh(scene, True, 'PREVIEW')
            me.transform(ob.matrix_world)
            new_ob = bpy.data.objects.new("%s_frame%0*i" % (ob.name, l, f), me)
            obs.append(new_ob)

scene.frame_set(frame_current)
for ob in obs:
    scene.objects.link(ob)
scene.update()

It applies modifiers as well as transformations (.matrix_world, so loc/rot/scale).

$\endgroup$
5
  • $\begingroup$ Excactly was I was looking for / trying to create! $\endgroup$
    – daniel
    Commented Dec 1, 2014 at 11:47
  • $\begingroup$ Thank you for this very helpful answer! Is there a way to modify the script so that the object origins don't get left behind? Currently all my mesh data looks great, but I can't join the meshes as shape keys successfully because the origin point is offset by the animation. $\endgroup$
    – Mentalist
    Commented Jul 12, 2017 at 14:39
  • 1
    $\begingroup$ @Mentalist: Take the code of the 2nd example above, remove the call to me.transform() and add new_ob.matrix_world = ob.matrix_world.copy() above obs.append(new_ob), that should do the trick (unless the object is parented to another object, I believe it would require some additional logic in that edge case). $\endgroup$
    – CodeManX
    Commented Jul 26, 2017 at 21:10
  • $\begingroup$ Your awesome script is no longer working in recent versions of Blender due to some Python API changes with update, frame_current, etc. I tried to fix it, but have not had success. Could you please take a look and add a 2.8+ updated version? $\endgroup$
    – Mentalist
    Commented Oct 8, 2020 at 12:33
  • $\begingroup$ CodeManX, Please disregard my last comment. batFINGER has kindly provided an updated version of your script in this answer. :-) $\endgroup$
    – Mentalist
    Commented Oct 13, 2020 at 9:11
1
$\begingroup$

Following is an example script that converts the mesh (animated by an armature) to separate objects. To keep the code simple, the assumption is that there is only one modifier (Armature) and the mesh was bound to armature using vertex groups. There are 5 frames of animation (frames 1, 2, 3, 4, 5)

import bpy

# Select the mesh that is animated by a keyframed armature in pose mode.
source_object = bpy.context.active_object

objects = []

for index in range(1, 6):
    bpy.ops.object.duplicate_move(
        OBJECT_OT_duplicate={"linked":False, "mode":'TRANSLATION'}, 
        TRANSFORM_OT_translate={"value":(0, 0, 0), 
                                "constraint_axis":(True, False, False)
                                }
    )

    objects.append(bpy.context.active_object)

for index in range(1, 6):
    bpy.context.scene.frame_set(index)

    override_context = bpy.context.copy()

    override_context['active_object'] = objects[index-1]
    override_context['object'] = objects[index-1]
    override_context['selected_objects'] = [objects[index-1]]

    bpy.ops.object.modifier_apply(override_context, apply_as='DATA', modifier='Armature')
    bpy.ops.object.vertex_group_remove(override_context, all=True)
    objects[index-1].select = False

bpy.context.scene.frame_set(1)
source_object.select = True
$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .