1
$\begingroup$

I want to animate any number of text objects using Python.

For example, imagine 10 text objects, changing their values to different strings, on any frame.

In short, I want full control to animate the text.data.body property.

Searching on the web, I found a solution for simple cases -> Keyframe on text body in Python?

And then I wrote my own code. However, I get TypeError: 'NoneType' object is not callable from my console, because I am trying to call my update_text function with different parameters.

Apparently, a function must be given to the 'register' without being called. Does anybody have any idea? I see that this is yet an unsolved problem...

Thanks in advanced!


def update_text(text_obj, old_text, new_text, key_frame):
    cur_frame = bpy.context.scene.frame_current
    if cur_frame <= key_frame:
        text_obj.data.body = old_text
    elif cur_frame > key_frame:
        text_obj.data.body = new_text

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



# update text content
register(update_text(my_text_obj, "old", "new", 10))
$\endgroup$
0

1 Answer 1

5
$\begingroup$

Return a callable

Please read the handler docs https://docs.blender.org/api/current/bpy.app.handlers.html A handler method takes two arguments the scene and the depsgraph. You append a function to handlers. In above update is a callable function whereas update(foo) is the result of calling that function, which without a return, returns None by default. Explaining error message.

That covered, using a method similar to answer here

Handler-script updates in viewport but not in render

Recommend passing the name rather than the object , otherwise object removal may cause null reference issues. The object is fetched from the scene objects collection by name in the handler.

Q code

import bpy

def update_text(name, old_text, new_text, key_frame):
    def handler(scene, depsgraph):
        text_ob = scene.objects.get(name)
        if not text_ob:
            return
        f = scene.frame_current
        text_ob.data.body = new_text if f >= key_frame else old_text
    return handler


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

# while testing to avoid buildup
bpy.app.handlers.frame_change_post.clear() 
# update text content
register(update_text("Text", "old", "new", 10))

Am currently using 2.90alpha and this is updating both in viewport and when rendered. Will find some issues with earlier versions of 2.8 not updating in render.

Check out the answer in link above re using a text data block as input using line numbers as frames. Instead of replacing body on each line could append or hold forward if a line is empty. (eg for example "old" on line 1, and "new" on line 10.

enter image description here

import bpy

# pass arguments to build a handler
def text_body_handler(text):
    lines = len(text.lines)
    # the handler method
    def text_body(scene):
        f = scene.frame_current
        text_ob = scene.objects.get("Text")
        if text_ob and f <= lines:
            body = text.lines[f-1].body
            if body:
                text_ob.data.body = body

    return text_body

bpy.app.handlers.frame_change_post.clear()           
bpy.app.handlers.frame_change_post.append(text_body_handler(bpy.data.texts["Text"]))

Related

Updating text object in Blender 2.81 using python

Note also: Avoid mixing context with handlers. It may be the case that the scene being "handled" is not the context scene. (bpy.context.scene) perhaps given a bit of a bum steer by answer in Q link..

$\endgroup$
4
  • 1
    $\begingroup$ Your solution is working perfectly! Deffing a function inside a function? Prodigious. I am using official 2.83 release and it is working there. Thank you very very very much! $\endgroup$
    – hrk
    Jun 23, 2020 at 19:37
  • $\begingroup$ I would like share how I used your answer in my code, there is only a little bit of change. I want a certain text to be visible during a frame period, to achive this, I simply modified the if statement as if f >= keyframe - 30 and f < keyframe: text_obj.data.body = new_text - Thanks again for your help! $\endgroup$
    – hrk
    Jun 23, 2020 at 19:42
  • 1
    $\begingroup$ Thankyou, appreciated. Happy to help. Remember pondering whilst writing earlier answer that could name text blocks and text objects the same, search for matches and change bodies accordingly. Seeing new comment is going to be much easier to put in a file. The conditional could be if f in range(start, end) Make sure f is an int. $\endgroup$
    – batFINGER
    Jun 23, 2020 at 19:44
  • $\begingroup$ Was having a similar issue, where I wanted to pass an argument to function after render is completed. This solved my problem. Thanks a lot. Upvoted. $\endgroup$ Dec 12, 2021 at 7:37

You must log in to answer this question.

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