2
$\begingroup$

I'm trying to animate a paragraph of text where each word is a separate frame. Looping through the full text can come later, right now I'm just trying to figure out switching the text from one frame to another. I don't want or need to animate typing and deleting text, I just want every frame to have a different text body. Here's what I have:

import bpy

text = bpy.data.objects['Text']
text.data.body = 'Here is my text'
text.keyframe_insert(data_path="data.body", frame=1)

text.data.body = 'Now it should change'
text.keyframe_insert(data_path="data.body", frame=2)

The error I get on the first keyframe_insert line is ValueError: bpy_struct.keyframe_insert() path spans ID blocks. Is it even possible to keyframe the body? I don't actually know what data_path I should be aiming for, data.body was just a guess. I haven't found a ton of resources on animating object properties, or even what object properties are legal to animate, so any answers or resources in that direction would be much appreciated.

$\endgroup$

1 Answer 1

4
$\begingroup$

I don't think you can keyframe the body of a text object, directly, so I think you should do something like this:

import bpy

def update(self):
    text = bpy.data.objects['Text']
    frame = bpy.context.scene.frame_current
    if frame <= 1:
        text.data.body = 'Here is my text'
    elif frame >= 2:
        text.data.body = 'Now it should change'

def register():
    bpy.app.handlers.frame_change_post.append(update)

def unregister():
    bpy.app.handlers.frame_change_post.remove(update)

register()

I have formatted this to be ready to be placed in an addon (you can see the register functions there), but you can use it as you want.

The idea is to have a frame_change handler that triggers an update function every time you change the frame (it's ideal for animations). The update function simply changes the text.data.body based on the current frame.

If you want to have more flexibility when animating, you can use custom properties:

import bpy

bpy.data.objects['Text']["Text animator"] = 0       #create custom prop

def update(self):
    text = bpy.data.objects['Text']
    frame = bpy.context.scene.frame_current
    if text ["Text animator"] < 1:
        text.data.body = 'Here is my text'
    elif text ["Text animator"] >= 1:
        text.data.body = 'Now it should change'

def register():
    bpy.app.handlers.frame_change_post.append(update)

def unregister():
    bpy.app.handlers.frame_change_post.remove(update)

register()

This way you can easily animate the custom property using keyframes and fcurves, and when the property value is more or less than 1 it changes the text body (of course you can set up more elif statements for values greater than 2, 3, 4, etc)

enter image description here

$\endgroup$
3
  • $\begingroup$ Fantastic, thank you! If I went the custom properties route, would I need to manually set all the keyframes for it? Word count = frame count for this animation, and manually setting >1000 keyframes is something I'm trying to avoid. $\endgroup$
    – Tinstar
    Commented Oct 20, 2019 at 15:06
  • $\begingroup$ You can set the keyframes up or crete them in python (unlike text body, a custom prop is a normal animatable fcurve). I can modify the script for you, but I just need to know what text do you want: do you want random words every frame, do you want a number that increments, do you want random words from a txt file,...? $\endgroup$
    – Tareyes
    Commented Oct 21, 2019 at 10:32
  • $\begingroup$ Something like: import bpy def update(self): text = bpy.data.objects['Text'] frame = bpy.context.scene.frame_current text.data.body = "This is frame number " + str(frame) def register(): bpy.app.handlers.frame_change_post.append(update) def unregister(): bpy.app.handlers.frame_change_post.remove(update) register() (sorry for the weird format) $\endgroup$
    – Tareyes
    Commented Oct 21, 2019 at 10:43

You must log in to answer this question.

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