I am writing a driver in blender 2.91.2 to simulate a spacecraft's Reaction Control System thrusters based on the movement of a parent object, without having to manually adjust and keyframe each "RCS" object or work with actual physics simulation.
I'm attempting to do this by using a calculated acceleration of each driven child object to determine a scale factor for its z-axis. To calculate the acceleration I am attempting to store previous frame's locations in custom properties of each object.
I am using the following script as a driver expression.
import bpy
from mathutils import Matrix, Vector, Euler
def update_matrices(obj):
if obj.parent is None:
obj.matrix_world = obj.matrix_basis
else:
obj.matrix_world = obj.parent.matrix_world.copy() @ \
obj.matrix_parent_inverse.copy() @ \
obj.matrix_basis.copy()
def rcs_calc(obj):
#update_matrices(obj) # Update stale data, doesn't work. Does change data erratically, occasionally.
# Get current location of each driven object. Doesn't seem to update when being rendered.
currentLocation = obj.matrix_world.to_translation()
# Get locations from object properties
Loc2 = Vector(obj.prevLoc2)
Loc1 = Vector(obj.prevLoc1)
# Calculate two velocity samples
vecVelocity2 = (Loc1 - Loc2)
vecVelocity1 = (currentLocation - Loc1)
#print(vecVelocity1)
# Calculate acceleration sample
vecAccel = (vecVelocity1 - vecVelocity2)
#print(vecAccel)
# Assign old locations, seems to never update...
obj.prevLoc2 = obj.prevLoc1
obj.prevLoc1 = currentLocation
# Update stale data? # Force update, see https://developer.blender.org/T63793#881438, does not work
obj.update_tag()
# Get direction
offsetAmount = Vector([0,0,1])
offsetGlobal = obj.matrix_world.to_3x3() @ offsetAmount
offsetObj = obj.matrix_world.to_translation().copy() + offsetGlobal
direction = offsetObj - obj.matrix_world.translation
#direction = obj.matrix_world.to_quaternion().copy() @ offsetAmount # Same result as above
#Invert direction so that plumes 'push' rather than 'pull'
direction *= -1
# Scale by dot product
scaleFactor = direction.normalized().dot(vecAccel.normalized())
debug = False
# Outputs text of most variables into the file 'scaleFactors' in the current directory, once per frame.
# File is never automatically cleared, be careful to avoid it getting too large!
if obj.name == "RCS.Front.225d" and debug == True:
path = './'
formatStr = ("f:{:03d}, name:{:s} \n---\n" +
"currentLocation: {:6.3f} {:6.3f} {:6.3f}, Loc1: {:6.3f} {:6.3f} {:6.3f}, Loc2: {:6.3f} {:6.3f} {:6.3f} \n" +
"vecVelocity1: {:6.3f} {:6.3f} {:6.3f}, vecVelocity2: {:6.3f} {:6.3f} {:6.3f} \n" +
"offsetGlobal: {:6.3f} {:6.3f} {:6.3f}, offsetObj: {:6.3f} {:6.3f} {:6.3f} \n" +
"vecAccel: {:6.3f} {:6.3f} {:6.3f}, direction: {:6.3f} {:6.3f} {:6.3f} \n" +
"scaleFactor: {:6.3f}\n\n")
with open(path + "scaleFactors.txt", "a") as file:
file.write(formatStr.format(bpy.context.scene.frame_current, obj.name,
currentLocation.x, currentLocation.y, currentLocation.z, Loc1.x, Loc1.y, Loc1.z, Loc2.x, Loc2.y, Loc2.z,
vecVelocity1.x, vecVelocity1.y, vecVelocity1.z, vecVelocity2.x, vecVelocity2.y, vecVelocity2.z,
offsetGlobal.x, offsetGlobal.y, offsetGlobal.z, offsetObj.x, offsetObj.y, offsetObj.z,
vecAccel.x, vecAccel.y, vecAccel.z, direction.x, direction.y, direction.z, scaleFactor))
#bpy.context.view_layer.depsgraph.update() # Update scene to prevent stale data. Causes RuntimeError
if scaleFactor < 0.25:
# Can't be zero or offsetGlobal will become 0
return 0.01
elif scaleFactor < 0.5:
return 0.5
else:
return 0.999
Create object custom properties.
bpy.types.Object.prevLoc1 = bpy.props.FloatVectorProperty(
name='Previous Location',
description='Location -1 frames ago',
subtype='TRANSLATION',
options={'LIBRARY_EDITABLE', 'ANIMATABLE'})
bpy.types.Object.prevLoc2 = bpy.props.FloatVectorProperty(
name='Previous Previous Location',
description='Location -2 frames ago',
subtype='TRANSLATION',
options={'LIBRARY_EDITABLE', 'ANIMATABLE'})
if 'rcs_calc' in bpy.app.driver_namespace:
del bpy.app.driver_namespace['rcs_calc']
bpy.app.driver_namespace['rcs_calc'] = rcs_calc
The desired result is that each driven RCS object scales with the dot product of its direction and acceleration vector so that it appears as if the RCS object is a rocket motor pushing the main keyframed object. This works almost flawlessly in the viewport, but when rendered, it does not. When rendered, the RCS objects rarely scale at all, and if they do, they do so in ways inconsistent with the viewport display.
From what I can tell from debugging by printing all the variables in the script into a file each frame, it is because obj.matrix_world.to_translation() does not update when rendering the file. Additionally, the two custom object properties (obj.prevLoc1 and obj.prevLoc2) do not seem to update either. I have very little idea as to why this is occurring.
I have found a handful of reports online of similar issues in which objects driven by their own transform channels fail to render properly, but none of them have had resolutions that have worked for me, if they had solutions at all.
Things I've tried, without success:
- Using
object.update_tag()inside the driver as per this bug report. This appears to have no effect. - Using
object.evaluated_get(depsgraph)inside the driver to get values, instead ofobject.matrix_world.to_translation(). This causes blender to segfault when the script is run. - Parenting each RCS object to its own corresponding empty, then modifying the script to use the empties' transform channels, instead of
self. This doesn't appear to change anything, and is far more clunky to manage and set up for dozens of RCS objects. - A lot of manual tweaking of values, as well as inspecting objects in the blender console. This was helpful in determining that values weren't updating.
As an example of what I mean, here are two samples of the debug files. The first was generated by playing the animation in the viewport, and the second by rendering from the terminal.
Viewport:
f:005, name:RCS.Front.225d
---
currentLocation: -0.622 -2.700 -0.622, Loc1: -0.630 -2.700 -0.630, Loc2: -0.635 -2.700 -0.635
vecVelocity1: 0.008 0.000 0.008, vecVelocity2: 0.005 0.000 0.005
offsetGlobal: -0.706 0.000 -0.706, offsetObj: -1.328 -2.700 -1.328
vecAccel: 0.003 0.000 0.003, direction: 0.706 -0.000 0.706
scaleFactor: 1.000
f:006, name:RCS.Front.225d
currentLocation: -0.611 -2.700 -0.611, Loc1: -0.622 -2.700 -0.622, Loc2: -0.630 -2.700 -0.630
vecVelocity1: 0.011 0.000 0.011, vecVelocity2: 0.008 0.000 0.008
offsetGlobal: -0.706 0.000 -0.706, offsetObj: -1.317 -2.700 -1.317
vecAccel: 0.003 0.000 0.003, direction: 0.706 -0.000 0.706
scaleFactor: 1.000
f:007, name:RCS.Front.225d
currentLocation: -0.597 -2.700 -0.597, Loc1: -0.611 -2.700 -0.611, Loc2: -0.622 -2.700 -0.622
vecVelocity1: 0.014 0.000 0.014, vecVelocity2: 0.011 0.000 0.011
offsetGlobal: -0.706 0.000 -0.706, offsetObj: -1.303 -2.700 -1.303
vecAccel: 0.003 0.000 0.003, direction: 0.706 -0.000 0.706
scaleFactor: 1.000
Rendered:
f:005, name:RCS.Front.225d
---
currentLocation: -0.636 -2.700 -0.636, Loc1: 0.000 0.000 0.000, Loc2: 0.000 0.000 0.000
vecVelocity1: -0.636 -2.700 -0.636, vecVelocity2: 0.000 0.000 0.000
offsetGlobal: -0.007 0.000 -0.007, offsetObj: -0.643 -2.700 -0.643
vecAccel: -0.636 -2.700 -0.636, direction: 0.007 -0.000 0.007
scaleFactor: -0.316
f:006, name:RCS.Front.225d
currentLocation: -0.636 -2.700 -0.636, Loc1: 0.000 0.000 0.000, Loc2: 0.000 0.000 0.000
vecVelocity1: -0.636 -2.700 -0.636, vecVelocity2: 0.000 0.000 0.000
offsetGlobal: -0.007 0.000 -0.007, offsetObj: -0.643 -2.700 -0.643
vecAccel: -0.636 -2.700 -0.636, direction: 0.007 -0.000 0.007
scaleFactor: -0.316
f:007, name:RCS.Front.225d
currentLocation: -0.636 -2.700 -0.636, Loc1: 0.000 0.000 0.000, Loc2: 0.000 0.000 0.000
vecVelocity1: -0.636 -2.700 -0.636, vecVelocity2: 0.000 0.000 0.000
offsetGlobal: -0.007 0.000 -0.007, offsetObj: -0.643 -2.700 -0.643
vecAccel: -0.636 -2.700 -0.636, direction: 0.007 -0.000 0.007
scaleFactor: -0.316
In the rendered sample, the variables have not changed at all between frames, whereas they change as expected in the viewport sample. The most important variables for demonstration's purpose are currentLocation, Loc1, and Loc2. They correspond to the object's position at the current frame, frame - 1, and frame -2, respectively.
Any advice on how to work around or fix this issue would be greatly appreciated, thank you.

