Background & Overview
I've been working on an animation which involves a series of objects changing color via an object attribute material node controlled by a custom property controlled by a driver which utilizes a custom FloatProperty (
glowStartTime) attached to each of the objects. This needs to be done over a controllable number of frames starting once a given control empty has been moved from its starting location, but otherwise irrespective of the control empty's animation.
As an example, I want to be able to tell these objects' attribute to change from 0 to 1 over 60 frames starting at whatever frame the control empty has been moved from its starting position on, irrespective of the movement of the control empty otherwise.
This works as expected in the viewport, but when rendering via f12 or the command line, the driver function doesn't seem to be able to read the updated
glowStartTime, regardless of if the object reference in the driver has been
evaluated_get(depsgraph)'d or not. Instead, it always sees it as the default value (
-1, in this case)
I've written a simple python function (
calc_glow) which I've then placed into the driver namespace, and used as a driver expression to achieve this effect. At the most basic, it requires a dependency graph, an object (
self), a factor (the z location of the control empty), and a duration (in the example file, 60 frames). I've then created a driver on the object property (
Glow Factor) with the expression
calc_glow(depsgraph, self, var, 60) where
var is the z location of the control empty.
The control empty is keyframed to move from 0 on the z-axis to 1 on the z-axis between frames 11 to 13. On frame 12, the function stores the frame that the empty first had a nonzero z-axis value (in this case, frame 12) in
glowStartTime, and calculates a 0 to 1 factor based on
frame_current_final - glowStartTime / duration. The attribute in the node tree (reading from
Glow Factor) is linked into a color ramp (Going from cyan to pink, just to be easier to see changes).
Watching this in the viewport preview, the object changes color from cyan to pink over the expected 60 frames, but when I render it as either a single frame or an animation, the drive seems unable to read the FloatProperty, and as a result stays pure cyan at any frame.
Caveats & Problems
There are of course many easier ways to make a cube change color over time, but this is a deliberately pared down example which still exhibits the issue — I plan to use this effect on several linked collections of several dozen objects each starting at a different arbitrary frame than the others. In the actual scene, the control empties will be replaced with a control bone in a rig. The control bone will be responsible for several carefully timed events, and as such the location of the control cannot be directly responsible for the
Additionally, I have looked checked the blender manual regarding properties, drivers, and the Gotchas page without finding anything that seems to indicate a solution. Using
depsgraph.view_layer.update() in the driver causes blender to error out.
I've also found a number of blender bug reports which seem similar, though not identical (See here and here), which suggest using object.update_tag() or other similar things. These seems to have no effect inside of my driver function, or cause crashes or errors.
Most of my testing has been done in Blender 2.93 LTS because that's the version I work in normally, but in my limited testing the same effect can be observed in the (current as of 2022-07-31) stable version 3.2.1.
Script & Blend File
Here's a copy of the script & blender for reference. In the blend file, the numerous print statements, as well as test_handler1, 2, and 3, are all for debugging purposes and could easily be removed.
import bpy def calc_glow(dp, obj, factor, duration): """Calculate glow length for obj of duration frames starting as soon as factor is > 0""" curFrame = dp.scene.frame_current_final evalObj = obj.evaluated_get(dp) obj.update_tag() evalObj.update_tag() if (factor > 0) and (evalObj.glowStartTime == -1): obj.glowStartTime = curFrame retVal = 0 elif (factor > 0): retVal = (curFrame - evalObj.glowStartTime) / duration else: obj.glowStartTime = -1 retVal = 0 obj.update_tag() evalObj.update_tag() return retVal def register(): if not hasattr(bpy.types.Object, 'glowStartTime'): print("no obj.glowStartTime, creating") bpy.types.Object.glowStartTime = bpy.props.FloatProperty( name = "Glow start frame", description = "Frame to start glow time calculation from", subtype = 'TIME', unit = 'TIME', default = -1) if 'calc_glow' in bpy.app.driver_namespace: del bpy.app.driver_namespace['calc_glow'] bpy.app.driver_namespace['calc_glow'] = calc_glow def unregister(): if 'calc_glow' in bpy.app.driver_namespace: del bpy.app.driver_namespace['calc_glow'] if hasattr(bpy.types.Object, 'glowStartTime'): del bpy.types.Object,glowStartTime if __name__ == "__main__": register()
I am unsure of how to make drivers be capable of reading properties properly in rendered view, and any and all assistance would be greatly appreciated, thank you.