I thought I would check in on the state of the API. Yep, they renamed stuff again.
The old driver hack for 2.52 does not work in 2.55.
This is an updated version of the frame change event driver for 2.55.
The concept is that an empty, called “mt_event_crank” has a driver on it’s y-axis. That driver get’s run every frame. The driver looks at a block of code and executes that code every frame.
You can place your own code in the text file “pydrivers.py” to make it run every frame.
Currently, in this example, I am showing how you can change the copy that a font object displays on-the-fly based upon what frame number you are currently at in the animation. When you rewind and play, the font will change all by itself without any key frames.
Nice, the enterframe event thing works great! I’ll be playing with this.
But looks like a lot of work to just access the TextCurve.body… is all the code necessary?
If only you coud just keyframe that property, as in C4D.
All that code is necessary, but you do get the added benefit of being able to change any parameter of the font object, not just the copy.
I too was quite disappointed after all the 2.49-2.5 re-arrangement there was no improvement to the font object at all. I want to keyframe my copy. After all don’t we live in the 21st century!
Brilliant… been a bit stumped with this for a car rig I built one using drivers to increment values but that was problematic with baking dynamic paint and scrubbing on the timeline.
It is not perfect…
I just discovered it does not work when you render the animation.
It seems the bpy.context.scene goes out of scope during render operations. The code still executes, but the code has no access to the scene.
I reported it as a bug and Ton acknowledged it. There is hope for an “official” frame change event for 2.5 yet!
where the var list is a comma separated list of vars from the driver set up. Still pretty “fresh” with python. Could you explain why you set it up with the 0.0 and the [] index?
This has proved most handy thanks again.
EDIT: Hmm scratch that LOL… it gives the result wanted on the driver panel but doesn’t update with the timeline.
The line was provided directly from Campbell, as a suggested alternate. I assumed the 0.0 is the return value for the driver. If it still works like 2.49, the driver does need a return float value, which is the value of the curve at any given point in time. I’m not sure what the index is all about, however…?
Really, the only thing needed to get passed is the current frame #. But that does not work. I tried it.
You can pass the value of the variable from the drivers variable list.
You can get the currentframe from the driver variable “single property” Type Scene object … “yourscene” path “frame_current”… Then pass the variable name in the call… for instance the default name would be “var” … It looks like it has to be a float type … setting a property path to location for instance and hoping to pass a vector crashes blender hehe.
I’m pretty new to python coming from more c, csharp side of things. On algoriths blog he mentions that all functions that are imported to the builtin namespace are available to driver expressions cos for example
Is there a way of importing pydrivers into that namespace? and hence be able to call our functions directly.
All right, I read Algorith’s Blog. That helped a lot. I have updated the original BLEND file with the corrected driver expression and the added variable for current frame.
Thanks for working this out with me. Now I have a way to render a python driven font based animation.
I was using varlist meaning the driver variables you set up. If i set up three driver variables lets say a is the current_frame x is the x location and d is the diameter I can pass them to the method using
The variables get passed to the method ok when using the UI but cant get the driver to run with the animation unfortunately.
I’ve attached a file with a simple example of passing the current frame. The method prints the frame # Ok and passes it back as a value Ok… it just doesn’t “run” with the timeline… Also this way I need to save and reload blender to change methods… Ultimately I think the devs will set up a reserved name, eg pydrivers.py to have helper functions imported into the builtin namespace… and then just call them from the driver by name. It makes it easier to have conditional expressions when you can actually see the code.
Whoops I reanswered your post from before… Give me a hoy if you have it updating ok… I want it for a car rig to calc wheel rot etc.
Try wrapping your initial expression in a parenthesis.
(__import__("pydrivers").myEvent(var))
I did that on my working example and I was getting a different return value every frame, as I scrubbed the animation. I was just returning what was passed, but with the Show Debug Info checkbox activated, you can see the return values.
Hallo, first time that I got it working … a questions remains:
Once activated, how to change the behavior, seems to be fixed IN the .blend
changing the pydriver.py script has no effect.
Does it mean that one has delete the empty? It looks like it.
Changing script and add a new empty …etc?
Something to do with the import statement in python i think. My theory is that the call picks up the first imported method of that name… hence the indexing in post 7… I’m new to python, so rather than finding out, i get around it by reloading the blend file if i make any changes to the pydrivers script.
It is a pain, but if I make changes to the python script, my work flow is to save the blender file, close Blender and open it back up. A successful import seems to “lock” that code into memory. So new changes to the code require a complete shut down to blender and a reopen.
Also, if the changes to the script produce an error, the driver will unlink, automatically. Then you have to click back in the text field of the driver and hit enter. Observe the panel, if an error still exists, the driver will report that and unhook the code again.
As I mentioned, a pain. Also I would re-iterate that any animations produced this way will not render if the code you place in the pydrivers.py requires a call to the bpy.context. It falls out of scope during a render. This bug is reported and assigned.
I made a slight change to the property being passed. Instead of using a global variable, I simply pass the current frame, which was where my font copy change script was erroring out upon render. Now my frame change event no longer accesses bpy.context.scene thus it can continue processing while scrubbing the animation or rendering it.
This is a step forward, it would be nice if it were possible to just pass the scene itself to the frameChange event. I am still not that good with RNA paths yet so it might be possible. But I think this solution can work for small scripts that needs to work with “fixed” or known objects in the scene.
My next test will be to see if it can work in a more demanding situation, like BlendText, where all objects are removed from the scene and then replaced in the scene upon frame change. BlendText does require a scene object during it’s frame change because it does unlinking and re-linking of objects on the fly.
was only using the global old_frame variable to check against the passed frame number to only run the script when frame has actually changed. Thinking of making it a “wrapper” and passing a function to it
Tried this little bit of code in your driver. I would have thought the scene is available to the driver??? Definitely the context scene is… didn’t try but can prob manipulate objects in other scenes as well.
print("Processing a frame change event on frame #" + str(this_frame) + ".")
if this_frame == 44 :
ft = bpy.data.curves.new('mytext','FONT')
ft.body = "Text added and linked to scene in driver"
gr = bpy.data.objects.new("grumble",ft)
bpy.context.scene.objects.link(gr)
if this_frame == 88:
gr = bpy.data.objects["grumble"]
bpy.context.scene.objects.unlink(gr)
bpy.data.objects.remove(gr)
adds a text object at frame 44 and renders it no problem… and removes it at frame 88. Ofcourse some better checking in the code etc is required .or perhaps setting up a global to keep track of it all.