Saturday, January 2, 2021

doodle animation exporter in blender

Hi,

this is some work i did on an animation exporter for blender.  I haven't made it into an addon yet but hopefully some of the script is helpful for learning.  it basically uses pickle to write out the animation data because i tried to use objects to store the animation data instead of a dictionary.  There may be better ways to implement this but hope its helpful.

Happy Sketching!

Nate


#some work in animation exporter from blender. testing in 2.79
#Usage:
#little error checking modify use at your own risk

"""
#for saving animation
#change this path for where to save animation
path = '/Users/Nathaniel/Documents/src_blender/python/snippets/tmp/anim.pickle'
obj_name = 'Cube' #change this to object name want to save animation for
exportAnimation(obj_name,path)

#for importing animation
#importAnimation(obj_name,path)

"""


import bpy
import pickle
import os

def exportAnimation(obj_name = None, out_path = ''):
    """write animation of a single object to disc
    """
    #little error checking.
    
    if obj_name not in bpy.data.objects:
        print("expecting an object name in scene. doing nothing")
        return

    outDir = os.path.dirname(out_path)
    if not os.path.exists(outDir):
        print('Requires an out directory that exists to write animation file')
        return
        
    obj = bpy.data.objects[obj_name]
    
    #read keyframe info into objects. no error checking. assumes object has keys 
    fc_obj_list = []
    
    action_in_scene = obj.animation_data.action
    if action_in_scene is None:
        print("no animation action on object. skipping animation export")
        return        
    fcurves_in_scene = action_in_scene.fcurves
    if fcurves_in_scene is None:
        print("no animation on object. skipping animation export")
        return
        
    for fc in fcurves_in_scene:
        kf_obj_list = []
        for kf in fc.keyframe_points:
            #need to add additional keyframe info to dictionary here ex tangents
            kf_obj = KeyframePoint(data = {'co':(kf.co.x,kf.co.y),
                                            'handle_left':(kf.handle_left.x,kf.handle_left.y),
                                            'handle_right':(kf.handle_right.x, kf.handle_right.y),
                                            'handle_left_type':kf.handle_left_type,
                                            'handle_right_type':kf.handle_right_type,
                                            'interpolation':kf.interpolation
                                            } )
            kf_obj_list.append(kf_obj)
        #make fcurve object
        fc_obj = FCurve(points=kf_obj_list,
                        data_path = fc.data_path,
                        array_index = fc.array_index,
                        extrapolation = fc.extrapolation,
                        color_mode = fc.color_mode)
        fc_obj_list.append(fc_obj)
    
    #make action object from fcurves
    action_obj = Action(fcurves = fc_obj_list)
    
    #write action to disc
    writeObject(  obj = action_obj, path = out_path )



#importAnimation
def importAnimation(obj_name=None, path = ''):
    anim_data = readObject(path = path)
    
    #remove all animation on object
    obj = bpy.data.objects[obj_name]
    obj.animation_data_clear()
        
    #create a new action on object. for now ignoring additional way 
    #to find action name to prevent trailing digits on action name.
    obj.animation_data_create() #so we have .action attribute
    obj.animation_data.action = bpy.data.actions.new(name= obj_name+'Action')
    
    #create fcurves on action
    for fc in anim_data.fcurves:
        #create new fcurve
        fc_scene = obj.animation_data.action.fcurves.new(fc.data_path,fc.array_index)
        #add keyframe points for fcurve
        for pt in fc.points:
            print(pt.data['co'])
            kp = fc_scene.keyframe_points.insert( pt.data['co'][0], pt.data['co'][1] )
            #need to edit tangents here
            kp.handle_left = pt.data['handle_left']
            kp.handle_right = pt.data['handle_right']
            kp.handle_left_type = pt.data['handle_left_type']
            kp.handle_right_type = pt.data['handle_right_type']
            kp.interpolation = pt.data['interpolation']

        #handle addiditional fcurve attributes
        fc_scene.extrapolation = fc.extrapolation
        fc_scene.color_mode = fc.color_mode

#helper classes for storing animation from blender
#class Action has list of fcurves
class Action(object):
    def __init__(self, **kwargs):
        self.fcurves = kwargs.get('fcurves', None) #list of fcurves. using none for default
        self.name = kwargs.get('name', None)
        self.path = kwargs.get('path', None)
        
class FCurve(object):
    def __init__(self, **kwargs):
        self.points = kwargs.get('points', None) #list of keyframe points
        self.data_path = kwargs.get('data_path', None) #str (ex: ‘location’ for positions)
        self.array_index = kwargs.get('array_index', None) #int (0,1,2 for x,y,z)
        self.extrapolation = kwargs.get('extrapolation', None) #str  (ex: ‘CONSTANT’ flat at ends, linear would have linear bits at ends)
        self.color_mode = kwargs.get('color_mode',None) #str for color ex: AUTO_RGB
        
class KeyframePoint(object):
    def __init__(self, data = None):
        self.data = data #dict of keyframe info example co, tangent info

#helper pickle functions no error checking
def writeObject(obj = None, path=''):
    #no error checking.
    #need to check output directory exists
    if obj is None:
        return
    with open( path, "wb") as out_file:
        pickle.dump(obj, out_file)

def readObject(path=''):
    result = None
    
    if not os.path.exists(path):
        print("cannot find path to read")
        return

    with open( path, "rb") as in_file:
        result = pickle.load(in_file)
        
    return result






""" simple example:
p1 = KeyframePoint(data={'co':(3,2)})
p2 = KeyframePoint(data={'co':(4,3)})
f1 = FCurve(points=[p1,p2])
f2 = FCurve(points=[p1,p2])
a1 = Action(fcurves=[f1,f2])

outPath = '/Users/Nathaniel/Documents/src_blender/python/snippets/tmp/anim.pickle'
with open( outPath, "wb") as out_file:
    pickle.dump(a1, out_file)
    

with open( outPath, "rb") as in_file:
    anim_data = pickle.load(in_file)

for fc in anim_data.fcurves:
    for pt in fc.points:
        print(pt.data['co'])
"""

#Inspired by
#https://stackoverflow.com/questions/4530611/saving-and-loading-objects-and-using-pickle