Renaming edit/pose bones breaks the drivers using them.
Opened this issue · 3 comments
When renaming a bone (in pose & edit mode alike), the drivers using the bone break:
The bone name is put in the path
section of the driver (Single Property type)
When renaming, the bone's name is not updated in the driver making it invalid, or worst, getting unwanted values from an other bone.
I Think the excpected behaviour would be to update the bone name in the driver.
This could be tricky. I need to see if and how I could solve this issue
How come when renaming and object the driver is updated but not when renaming a bone ? Is it because Prop is a PointerProperty & Path a StringProperty ?
Anyway, wouldn't getting a list of all drivers be enought to loop over them & update their variables' path ? Sure it might slow down a bit the renaming if there are lots of drivers, but a check box to enable driver updating for bones, lets the user decide.
When doing a renaming operation I think, to get a list of driver's variables to update, something like that should be done:
"""
Thanks to testure:
https://blenderartists.org/t/find-all-the-drivers-in-the-file/1442126/2
"""
import bpy
from pprint import pprint
# List of "data types" where drivers can be found.
collections = [
"scenes",
"objects",
"meshes",
"materials",
"textures",
"speakers",
"worlds",
"curves",
"armatures",
"particles",
"lattices",
"shape_keys",
"lights",
"cameras",
"node_groups",
# TODO: add anything else that's missing here!
]
# To only update the needed drivers.
active_armature = bpy.context.active_object
target_list = [] # [("target_bone_name", ref_to_variable_target)]
# Loop over every data types (collections).
for col in collections:
# Get data type (e.g. "bpy.data.objects")
collection = eval(f"bpy.data.{col}")
# Loop over the elements of the data type.
for ob in collection: # ~ "for ob in bpy.data.{collection}.values():"
# Materials that use nodes are special since they don't store drivers
# directly on the material data itself, but on the node tree.
if isinstance(ob, bpy.types.Material) and ob.use_nodes:
ob = ob.node_tree
# Skip element if there is no animation data (no drivers).
if ob.animation_data is None:
continue
# Loop over the elements' drivers.
for driver in ob.animation_data.drivers:
# Check if armature is used in drivers
found_armature = False
# Go to each driver's variables.
for var in driver.driver.variables:
# Go to each variable's target.
for target in var.targets:
# Armature used as "primary" target,
# potential driver's variable's target to update.
if target.id == active_armature:
target_list.append(("Bone", target)) # TODO: Get data_path bone name.
# Quit Loops
found_armature = True
break
# Armature already found in driver.
if found_armature:
break
"""
Fetch driver's primary target:
bpy.data.object[0].animation_data.drivers[0].driver.variables[0].targets[0].id
Fetch driver's data_path:
bpy.context.active_object.animation_data.drivers[0].driver.variables[0].targets[0].data_path
"""
pprint(target_list)
print("Done.\n")
The second part is durring the renaming loop, changing the driver's data_path.
Maybe something like that ?
# The paring of the old name with the new ones.
names = {
"old1" : "new1",
"old2" : "new2",
"old3" : "new3"
}
# Imitation of the renaming loop.
for old_name, new_name in names.items():
# -------------------------
# - Renaming code & stuff -
# -------------------------
# Drivers update section:
# If the renamed bone was used in a driver.
if old_name in target_list:
dvr_target = target_list[old_name]
dvr_target.data_path.replace(f'["old_name"]', f'["new_name"]')
# TODO: ensuers the driver doesn't break if the user gives a bad name
# making the driver variable invalid.
# pose.bones["pose.bones"].location[0] -> new_name["pose.bones"].location[0]
As I'm not familiar with the addons' code and dabbled only recently with blender python api, thoses snippets are just drafts, not even properly tested.
I hope this is still a good starting point and thanks for your work on this amazing addon :)