1

enter image description here I have written a small addon that takes a mesh with its shapekeys and adds a custom property per shapekey to a selected bone of an armature and connects the two with a driver, so that in the end, all shapekeys on the mesh can be controlled through the armature.

The addon works and all elements are created, the trouble is that for the drivers in order to work, I need to manually refresh the dependencies.

Is there an error in how I add them or do I have to add some kind of update function to make this work?

I am posting the code and a sample file with an armature object and a cube with two shape keys on it to test things.

The normal mode of operation is to select the shapekey mesh, then the armature object and, in Pose Mode, selecting the target bone and then running the operator from the N-Panel > Item > ShapeKeys to Driven Properties.

The .py file can be installed like an addon or run from the text editor.

### Create Custom Properties on an Armature that drive all Shape Keys on its Child Mesh ###

Select the Mesh with the Shape Keys on it, then select the Armature that is to hold

the Custom Properties with which you are going to drive the ShapeKeys and run

bl_info = { "name": "ShapeKeys to Driven Armature Properties", "author": "", "version": (1, 0), "blender": (2, 90, 0), "location": "View3D > Property Panel > Item Tab", "description": "Adds Shapekeys from a Mesh Object as Driven Custom Properties to an Armature", "warning": "", "doc_url": "https://github.com/theoldben/ShapekeysToCustomProperties", "category": "Rigging", }

import bpy

Error in case Selection does not meet requirements

def error_selection(self, context): self.report({'WARNING'}, 'Incorrect Object Selection: Please Select a Mesh Object followed by an Armature Object') return {'FINISHED'}

def main(context): pass

class OBJECT_OT_ShapeKeysToDrivenProps(bpy.types.Operator): """ShapeKeys to Driven Armature Properties""" bl_idname = "object.shapekeystodrivenprops" bl_label = "ShapeKeys to Driven Armature Properties" bl_space_type = "VIEW_3D" bl_region_type = "UI" bl_category = 'Item' bl_options = {'REGISTER', 'UNDO'}

@classmethod
def poll(cls, context):
    return context.active_object is not None



def execute(self, context):
    main(context)

    # Variable Definition
    context = bpy.context
    obj = bpy.context.object
    active_obj = bpy.context.active_object
    selected_obj = bpy.context.selected_objects
    other_obj =  [obj for obj in bpy.context.selected_objects if obj.type == 'MESH']
    active_bone = bpy.context.active_pose_bone


    try:
        active_bone.name
        active_bone_name = active_bone.name
        key_number = len(other_obj[0].data.shape_keys.key_blocks)


        # Check for Object type: Active Object: Armature, Selected Object: Mesh
        if len(selected_obj) == 2 and active_obj.type == 'ARMATURE' and other_obj[0].type == 'MESH':

            # If other objects are in selection: return Error
            for things in selected_obj:

                # Iterate over ShapeKey List, Store Names, Add Properties to Active Object, 
                for i in range(1, key_number):

                    # Store ShapeKey Name
                    name_shapekey = other_obj[0].data.shape_keys.key_blocks[i].name

                    #Strip current Shapekey Name of all alpha-numerical characters
                    alphanumeric_filter = filter(str.isalnum, name_shapekey)
                    alphanumeric_string = "".join(alphanumeric_filter)
                    clean_name_shapekey = alphanumeric_string

                    # Access RNA_UI Dictionary of PoseBone, create if it does not exist
                    if "_RNA_UI" not in active_bone.keys():
                        active_bone['_RNA_UI'] = {}
                    act_bone_rna_ui = active_bone.get('_RNA_UI')

                    # If there is already a property with the current shapekey's name, skip
                    if name_shapekey in act_bone_rna_ui:
                        continue

                    # Add Property with ShapeKey's name to Active PoseBone and set Value to 0
                    active_bone[name_shapekey] = 0.0
                    # active_bone['_RNA_UI'].update({name_shapekey : 0.0})

                    # Add Custom Properties to RNA_UI dictionary, fill values
                    active_bone['_RNA_UI'].update({name_shapekey : 
                                                        {
                                                        "default": 0.0,
                                                        "min":0.0,
                                                        "max":1.0, 
                                                        "soft_min":0.0,
                                                        "soft_max":1.0,
                                                        "description":"Shape Key Influence",
                                                        }
                                                    }
                                                )
                    act_bone_rna_ui[name_shapekey]["min"] = 0.0
                    act_bone_rna_ui[name_shapekey]["max"] = 1.0

                    # Add the Drivers to the ShapeKeys Value Field, so ttheir influence can be controlled via Custom Properties                  
                    driver = other_obj[0].data.shape_keys.key_blocks[i].driver_add("value").driver
                    # Create new input Variable for driver
                    driver_var = driver.variables.new()
                    driver_var.name = clean_name_shapekey
                    # Set number of Variables in the driver stack, since there is only one, that is 0
                    target = driver_var.targets[0]
                    # Set the Target object for the Driver's Variable Input, i.e. the Armature Object, where the input sliders are
                    target.id = active_obj
                    # Set the drivers Data Path, i.e. the RNA Path to the Property it uses as input
                    target.data_path = "pose.bones"+"["+"\""+active_bone.name+"\""+"]"+"[" +"\""+ name_shapekey + "\"" +"]"
                    # Set the Expression Field of the Driver, in this case with the sanitised variable name
                    driver.expression = driver_var.name
                    # Jiggle the driver to force update
                    driver.expression += " "
                    driver.expression = driver.expression[:-1]

            # Set ShapeKey Startup Value
            other_obj[0][name_shapekey] = 0.0

            # property attributes.for UI
            # redraw Properties panel
            for window in bpy.context.window_manager.windows:
                screen = window.screen

                for area in screen.areas:
                    if area.type == 'PROPERTIES':
                        area.tag_redraw()
                        break

                for area in screen.areas:
                    if area.type == 'UI':
                        area.tag_redraw()
                        break

            return {'FINISHED'}

        else:
            error_selection(self, context)

    except:
        error_selection(self, context)
    return {'FINISHED'}

class PANEL_PT_ShapeKeysToDrivenProps(bpy.types.Panel): """ShapeKeys to Driven Armature Properties""" # bl_idname = "panel.shapekeystodrivenprops" bl_label = "ShapeKeys to Driven Armature Properties" bl_space_type = "VIEW_3D" bl_region_type = "UI" bl_category = 'Item' bl_options = {'DEFAULT_CLOSED'}

def draw(self, context):
    self.layout.operator("object.shapekeystodrivenprops", text="ShapeKeysToDrivenProps")


def register(): bpy.utils.register_class(OBJECT_OT_ShapeKeysToDrivenProps) bpy.utils.register_class(PANEL_PT_ShapeKeysToDrivenProps)

def unregister(): bpy.utils.unregister_class(PANEL_PT_ShapeKeysToDrivenProps) bpy.utils.unregister_class(OBJECT_OT_ShapeKeysToDrivenProps)

if name == "main": register()

Ben
  • 1,821
  • 1
  • 13
  • 33
  • 1
    Related re shapekey -> custom props. https://blender.stackexchange.com/a/164834/15543 and similar question https://blender.stackexchange.com/questions/137692/custom-float-property-used-in-a-driver-need-manual-ui-modification-to-work – batFINGER Sep 10 '20 at 17:05
  • I read through the first question's answer and the code there works, I am not sure why it does, though. I would have to plug in the variables from the active pose bone and see if it still works. The second question's answer, which is just to refresh the file seems like a cheap way out. Can you point out why the first script has no dependency issues? Could it be because it applies the drivers on the same object? – Ben Sep 10 '20 at 17:53

0 Answers0