I have written two custom driver functions that both do the same thing. Return the actual length of a bendy bone taking into account its curvature. A custom function was necessary because the default length property for a bone just gives the length from head to tail disregarding any curvature that is part of the bendy bone.
Of the two functions the first uses self and calculates the length of the bone the expression lives on. The other one uses two arguments, the first being self so the armature can be derived and the second argument being the name of the bendy bone whose length needs to be calculated. Both of them use the same logic for calculating the final length.
My issue is that there seems to be a problem with updates. The output is mostly smooth with the occasional random jitter. This is not dependent on a specific value or even a specific time. I feel this is related to the dependency graph because it seems to resolve itself on scene update. If it happens when I manually manipulate the playhead even letting go of mousedown is enough to refresh the scene and fix it. If it happens during animation playback and I manage to pause on the right moment, just selecting a different bone is enough to fix it.
I don't see any mentions about circular dependencies in the terminal and it also does not feel like the value lags. When doing research I read about drivers having issues with automatically updating when custom properties they depend on are updated through python. Since this function does not use any custom properties it did not seem relevant. I also read about possibly having to tag objects for update but this feels odd to me because a) it works most of the time and I would expect it not to work at all if this was the case b) which objects should I tag for updates? The bendy bone whose length I need may be part of a really complex rig where many things influence its final shape and therefore length.
I have also supplied a blender file with a working example that shows this behavior. The scene contains an animated bendy bone. Right next to it is a bone whose length is driven by "bbone_length_by_name". As you play the animation you will see it scale up and down and occasionally glitch out.
I have also added a link to a short video file that shows the jittery behavior. Since writing this post i've noticed that manipulating the viewport seems to make the jittering worse. If I leave the view static the jittering is less.
Since I wondered whether it was a display issue ive also printed the value to the terminal. As you can see there are weird jumps that last just a frame. So its not just a display issue, its actually because the values from the function are wrong sometimes (ive marked a few). These frames are not fixed btw, the spikes are random each playthrough.
Here is the code:
import bpy
import math
from mathutils import Vector
def get_bbone_segment_location(target_bendybone, segment_index):
# in worldspace
bone_armature = target_bendybone.id_data
matrix_segment = target_bendybone.bbone_segment_matrix(segment_index, rest=False)
matrix_final = bone_armature.matrix_world @ target_bendybone.matrix @ matrix_segment
return matrix_final.decompose()[0]
def get_bbone_segment_locations(target_bendybone):
location_list = []
num_segments = target_bendybone.bone.bbone_segments
for index in range(num_segments):
location_list.append(get_bbone_segment_location(target_bendybone, index))
return location_list
def calculate_length(points):
total_length = 0.0
for i in range(len(points) - 1):
distance = 0.0
point1 = points[i]
point2 = points[i + 1]
distance = (point2 - point1).length
total_length += distance
return total_length
# function to use on the bendy bone itself where self can be used for the target_bendybone argument
def get_bbone_length(target_bendybone):
location_list = get_bbone_segment_locations(target_bendybone)
return calculate_length(location_list)
# function to use on another bone within the same armature where the name of the bendy_bone needs to be supplied
def get_bbone_length_by_name(self, target_bendybone_name):
bone_armature = self.id_data
target_bendybone = bone_armature.pose.bones[target_bendybone_name]
return get_bbone_length(target_bendybone)
bpy.app.driver_namespace['bbone_length'] = get_bbone_length
bpy.app.driver_namespace['bbone_length_by_name'] = get_bbone_length_by_name
Any help or pointers would be really appreciated because I have no idea how to go from here.