Weisl/simple_renaming

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)
Screenshot 2024-08-29 223245
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 :)