0
$\begingroup$

I have a script for retargeting shape key drivers, but when I use it and then copy/paste it, I get this error:

Shapekey KEKey.001 has an invalid 'from' pointer (0000000000000000), it will be deleted

except I can't find "KEKey.001" ANYWHERE in the blender file or data API. The function in question is this:

   # Retarget To Mesh
def retarget_to_mesh(self, obj):
    if obj.data.shape_keys and obj.data.shape_keys.animation_data:
        for driver in obj.data.shape_keys.animation_data.drivers:
            # Get the target name from the data path
            parts = driver.data_path.split('"')
            if len(parts) > 1:
                target_name = parts[1]

                # Skip drivers with target names starting with '*'
                if target_name.startswith('*'):
                    continue

            if driver.driver.variables:
                for var in driver.driver.variables:
                    if var.targets and var.targets[0]:
                        if var.targets[0].id_type == 'KEY':
                            target_id = "default_target"
  
                            selected_key_block = obj.data.shape_keys.name.lower()
                            
                            # Get the active key block
                            active_shape_keys = bpy.context.active_object.data.shape_keys
                        
                            # Check if the target ID matches the selected object's key block (case-insensitive)
                            if target_id == selected_key_block:
                                continue
                            else:
                                # Store driver information
                                driver_type = driver.driver.type
                                driver_expression = driver.driver.expression
                                driver_id_type = var.targets[0].id_type
                                driver_data_path = var.targets[0].data_path

                                # Delete the existing driver on the active object
                                bpy.context.active_object.driver_remove(driver.data_path, -1)

                                try:
                                    # Try adding the driver to the shape key
                                    new_driver = obj.data.shape_keys.driver_add(driver.data_path)
                                    print(f"New driver created for {obj.name} with data path {driver.data_path}")
                                except:
                                    # Handle the case where the shape key doesn't exist
                                    print(f"Shape key '{driver.data_path}' does not exist on object '{obj.name}'. Skipping...")
                                    continue

                                # Set the new driver's properties
                                new_driver.driver.variables[0].targets[0].id_type = 'KEY'
                                new_driver.driver.variables[0].targets[0].id = active_shape_keys
                                new_driver.driver.variables[0].targets[0].data_path = driver_data_path
                                new_driver.driver.type = driver_type
                                if driver_expression:
                                    new_driver.driver.expression = driver_expression

This normally wouldn't be a problem, but is causing issues with some of my other scripts. I am using blender 4.1. This was not a problem in 3.6, but it is in 4.0 and 4.1. Where is KEkey.001 coming from, and how do I get rid of it?

For reference, the entire script is below. although I doubt that the problem is in execute or the retarget-to-armature function. Don't worry its not very long:

bl_info = {    
    "name": "Retarget Mesh Drivers",
    "author": "Krayne",
    "blender": (3, 6, 5),
    "location": "View3D > N-panel > Krayne's Tools",
    "description": "Retargets mesh shape key drivers to active armature object, or retargets shape key drivers driven by keys to active mesh object",
    "category": "Object",
    "bl_idname": "kRetarget",
}

import bpy

class RetargetDriversOperator(bpy.types.Operator):
    bl_idname = "object.retarget_drivers"
    bl_label = "Retarget Drivers"
    bl_description = "Retargets mesh shape key drivers to active armature object, or retargets shape key drivers driven by keys to active mesh object"

    def arm_target(self):
        # Check if there is an active object.
        if bpy.context.active_object is None:
            print("No active object selected.")
            return

        # Iterate through the selected objects.
        for obj in bpy.context.selected_objects:
            if obj.type != 'MESH':
                continue  # Skip non-mesh objects.

            # Find the Armature modifier.
            armature_modifier = None
            for modifier in obj.modifiers:
                if modifier.type == 'ARMATURE':
                    armature_modifier = modifier
                    break

            # If an Armature modifier is found, set its target to the active object.
            if armature_modifier:
                armature_modifier.object = bpy.context.active_object
                print(f"Set Armature modifier on '{obj.name}' to target '{bpy.context.active_object.name}'.")
    
    def switch_select(self):
        # Check if there are at least two selected objects
        if len(bpy.context.selected_objects) >= 2:
            obj1 = bpy.context.active_object
            obj2 = None

            # Find the first selected object that is not the current active object
            for obj in bpy.context.selected_objects:
                if obj != obj1:
                    obj2 = obj
            
            bpy.context.view_layer.objects.active = obj2

    # Retarget To Armature
    def retarget_to_armature(self, obj, active_obj):
        # Reparent the selected object to the active object
        bpy.ops.object.parent_set(type='OBJECT', keep_transform=True)
        self.arm_target()
        if obj.data.shape_keys and obj.data.shape_keys.animation_data:                   
           for driver in obj.data.shape_keys.animation_data.drivers:
                # Get the target name from the data path
                parts = driver.data_path.split('"')
                if len(parts) > 1:
                    target_name = parts[1]

                    # Skip drivers with target names starting with '*'
                    if target_name.startswith('*'):
                        continue
                    
                if driver.driver.variables:
                    for var in driver.driver.variables:
                        if var.targets and var.targets[0].id_type != 'KEY':
                            target = var.targets[0]
                            current_var_rna = var.targets[0]
                            data_path = current_var_rna.data_path                 
                            if target.id is not None:
                                expression = driver.driver.expression                                                    
                                # Copy the target ID to the existing driver
                                var.targets[0].id = active_obj
                                var.targets[0].data_path = data_path  # Set the data path
                                driver.driver.expression = expression
                                print(f"Driver: {driver.data_path} +++ Variable: {var.name}, Retargeted to: {active_obj.name}")
                            else:
                                print(f"Driver: {driver.data_path} !!! Variable: {var.name}, No target found. Adding target to {active_obj.name}")
                                # Set the target ID to the active object and reset the data path when no target is found
                                var.targets[0].id = active_obj
                                var.targets[0].data_path = data_path
                        else:
                            print(f"Driver: {driver.data_path} ---- Variable name: {var.name}, Skipping driver with ID type 'KEY'")
                else:
                    print(f"Object: {obj.name}, Driver name: {driver.data_path}, No variables found. Adding target to {active_obj.name}")
                    data_path = "pose.bones[\"BoneName\"].location"  # Set a default data path
                    # Set the target ID to the active object and the default data path
                    var.targets[0].id = active_obj
                    var.targets[0].data_path = data_path
                    
    # Retarget To Mesh
    def retarget_to_mesh(self, obj):
        if obj.data.shape_keys and obj.data.shape_keys.animation_data:
            for driver in obj.data.shape_keys.animation_data.drivers:
                # Get the target name from the data path
                parts = driver.data_path.split('"')
                if len(parts) > 1:
                    target_name = parts[1]

                    # Skip drivers with target names starting with '*'
                    if target_name.startswith('*'):
                        continue

                if driver.driver.variables:
                    for var in driver.driver.variables:
                        if var.targets and var.targets[0]:
                            if var.targets[0].id_type == 'KEY':
                                target_id = "default_target"
      
                                selected_key_block = obj.data.shape_keys.name.lower()
                                
                                # Get the active key block
                                active_shape_keys = bpy.context.active_object.data.shape_keys
                            
                                # Check if the target ID matches the selected object's key block (case-insensitive)
                                if target_id == selected_key_block:
                                    continue
                                else:
                                    # Store driver information
                                    driver_type = driver.driver.type
                                    driver_expression = driver.driver.expression
                                    driver_id_type = var.targets[0].id_type
                                    driver_data_path = var.targets[0].data_path

                                    # Delete the existing driver on the active object
                                    bpy.context.active_object.driver_remove(driver.data_path, -1)

                                    try:
                                        # Try adding the driver to the shape key
                                        new_driver = obj.data.shape_keys.driver_add(driver.data_path)
                                        print(f"New driver created for {obj.name} with data path {driver.data_path}")
                                    except:
                                        # Handle the case where the shape key doesn't exist
                                        print(f"Shape key '{driver.data_path}' does not exist on object '{obj.name}'. Skipping...")
                                        continue

                                    # Set the new driver's properties
                                    new_driver.driver.variables[0].targets[0].id_type = 'KEY'
                                    new_driver.driver.variables[0].targets[0].id = active_shape_keys
                                    new_driver.driver.variables[0].targets[0].data_path = driver_data_path
                                    new_driver.driver.type = driver_type
                                    if driver_expression:
                                        new_driver.driver.expression = driver_expression


    def execute(self, context):
        print("---------------------------------------------------------------------")        
        # Get the selected objects
        selected_objects = bpy.context.selected_objects

        # Check if at least one object is selected
        if len(selected_objects) >= 1:
            # Use the active object as the target
            active_object = bpy.context.active_object

            # Check if the active object is a mesh or an armature
            if active_object.type == 'MESH':
                # Handle mesh object retargeting
                for obj in selected_objects:
                    if obj != active_object:
                        self.retarget_to_mesh(obj)
            elif active_object.type == 'ARMATURE':
                # Handle armature object retargeting
                for obj in selected_objects:
                    if obj != active_object:
                        print(f"Object: {obj.name}")
                        self.retarget_to_armature(obj, active_object)
                        print()
            else:
                self.report({'INFO'}, "Please select a mesh or armature object as the active object.")
        else:
            self.report({'INFO'}, "Please select at least one object to retarget drivers.")
        # Call the switch_select function to set the active object
        self.switch_select()
        print("***********************************************************************")
        return {'FINISHED'}
    
def menu_func(self, context):
    self.layout.operator(RetargetDriversOperator.bl_idname, text="Retarget Drivers")

def register():
    bpy.utils.register_class(RetargetDriversOperator)
    bpy.types.VIEW3D_MT_object.append(menu_func)

def unregister():
    bpy.utils.unregister_class(RetargetDriversOperator)
    bpy.types.VIEW3D_MT_object.remove(menu_func)
    
if __name__ == "__main__":
    register()```
$\endgroup$
6
  • $\begingroup$ Thx for posting the whole script, which makes it easier for us to find a problem. If you wanna make it really easy for us (and then i would take a deeper look at it too), you would provide a blend file, add some description like "step 1: select armature, step 2: run script and the error occurs". thx. $\endgroup$
    – Chris
    Commented Feb 24 at 7:07
  • $\begingroup$ I would post but theres no button to click to attach file $\endgroup$
    – Krayne
    Commented Feb 24 at 8:33
  • $\begingroup$ here I have it hosted on dropbox: dropbox.com/scl/fi/ka6ynbrub3ahwmh4qp7vj/… 1. Run the script. 2. Select the red (top) plane, then the blue one. 3. Run the operator 4. Copy/paste the red plane $\endgroup$
    – Krayne
    Commented Feb 24 at 9:19
  • $\begingroup$ EDIT: I updated the register functions to make the op searchable $\endgroup$
    – Krayne
    Commented Feb 24 at 9:28
  • $\begingroup$ i did what you said. Result was: [1]: i.sstatic.net/ySEBh.gif - but i didn't get the error you described $\endgroup$
    – Chris
    Commented Feb 24 at 9:42

0

You must log in to answer this question.

Browse other questions tagged .