Update function for a Custom Property?

Hi! Currently in my rigs I use drivers to move bones from layer to layer, based on the slider of a custom property.
In other words, this is an auto-hiding feature for IK/Fk. So, dependeing on the state of the IK/FK slider, certaint bones appear or dissapear from certain layers.
So, to make this work, I just add these drivers to the layer properties of each bone. But well, this has several fallbacks, as poor performance (as this kind of driver updates are a killer), and blender saying that this driver causes a cyclic dependency when I link the rig.
Therefore, I’d like to do this bone hiding with python, but… My python knowledge is not so cool, hehe.

So, for example, here I have an operator :


class Operator_Torso_HIDE(bpy.types.Operator):    
    
    bl_idname = "torso_hide"     
    bl_label = "BlenRig Torso HIDE"      
    
    def execute(self, context):  
        arm_data = bpy.context.active_object.data
        ob = bpy.context.active_object
        cust_prop = ob.items()
        for prop in cust_prop:           
            if prop[0] == 'ik_torso' and prop[1] == 0.0:                         
                arm_data.bones['torso_ctrl'].layers[0] = True                 
            elif prop[0] == 'ik_torso' and prop[1] == 1.0:                          
                arm_data.bones['torso_ctrl'].layers[0] = False   

So, when I call this operator, the custom property called ‘ik_torso’ gets evaluated and then it hides or unhides the torso controler bone, by moving in or out of layer 0.

Now, what I want to do is not using an operator, but somehow evaluate the ‘ik_torso’ property when it is changed, so that depending on the result, the torso controler is displayed or not. That’s what I guess the update funtion is used for, but I really don’t know how to implement it.

If you want to help me with this, I’d really appreciate that you copy full scripts, not part of scripts, as I may be too noobie to make it work if I only get pieces of code! :smiley:

Thanks!

Hi jp.

I’m assuming you have a property on a bone, but here is an example of the update prop on

all pose bones
all armatures
all objects


import bpy
from bpy.props import FloatProperty
print("-----------------------------")


def update(self, context):
    print(self.rna_type.identifier)
    # update(posebone, context)
    print(self.prop)
    
bpy.types.PoseBone.prop = FloatProperty(default=0.0, description="Change to Update", update=update)


bpy.types.Armature.prop = FloatProperty(default=0.0, description="Change to Update", update=update)


bpy.types.Object.prop = FloatProperty(default=0.0, description="Change to Update", update=update)

Once you initialize these properties they will show up in the custom properties panel for bone/ armature / object.

Either initialise using obj.prop = value or obj[“prop”] = value. If the value changes it calls the update function.

Going by your script a BoolProperty may be a better choice to turn things on / off rather than a floatprop.


import bpy
from bpy.props import BoolProperty

def update(self, context):
    # assuming self is Arature type in this case
    self.bones['torso_ctrl'].layers[0] = self.prop


bpy.types.Armature.prop = BoolProperty(default=False, description="Change to Update", update=update)

Also when iterating thru the items() dictionary, it’s a little more readable to use


for key, value in ob.items():    
    if key == 'ik_torso' and value == 0.0:                         
        arm_data.bones['torso_ctrl'].layers[0] = True                 
    elif key == 'ik_torso' and value == 1.0:                          
        arm_data.bones['torso_ctrl'].layers[0] = False


Thanks batFINGER!

Well, as I said, I’m too noobish to apply that… So, my property is in object level, what I would need is a script that when I run it in the text editor, it starts making the update and consecuent hiding. So, I don’t have a clue on how to apply the lines you posted :smiley:

I need to get this working in combination with you update function:

arm_data = bpy.context.active_object.data
    ob = bpy.context.active_object
    cust_prop = ob.items()
    for prop in cust_prop:           
             if prop[0] == 'ik_torso' and prop[1] == 0.0:                         
                     arm_data.bones['torso_ctrl'].layers[0] = True                 
             elif prop[0] == 'ik_torso' and prop[1] == 1.0:                          
                     arm_data.bones['torso_ctrl'].layers[0] = False

So, in my blend I’ve got an armature that has a custom property in object level called ‘ik_torso’, and a bone called ‘torso_ctrl’. So, my script lines say that when ik_torso = 0, the bone appears in layer 0, but when ik_torso = 1, the bone dissapears from layer 0.
So well, I don’t know how to make all this work together! ehehe, my problem is that I kind of understand python things when I see them, and I may be able to modify them, but I really don’t have the knowledge to setup a script from scratch, I would need a working script! :smiley:

For instance, I don’t know if these IF conditions I’m pasting should be precided by a def function, or how to combine all that with the update function you posted.

Thanks!

To set up the update method you need to define a bpy.props property rather than just an ID property (ob[“prop”])
Select your armature object and look for the hello world panel in the object properties. Toggling the ik_torso button will toggle the layer of a bone named torso_ctrl on the armature of the active object.


import bpy
from bpy.props import BoolProperty




def update(self, context):
    self.data.bones['torso_ctrl'].layers[0] = self.ik_torso
    
bpy.types.Object.ik_torso = BoolProperty(default=False, description="Change to Update", update=update, name="IK TORSO")


class HelloWorldPanel(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "Hello World Panel"
    bl_idname = "OBJECT_PT_hello"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "object"


    def draw(self, context):
        layout = self.layout


        obj = context.object


        row = layout.row()
        row.prop(obj, "ik_torso", toggle=True)




def register():
    bpy.utils.register_class(HelloWorldPanel)




def unregister():
    bpy.utils.unregister_class(HelloWorldPanel)




if __name__ == "__main__":
    register()


This bit defines a custom property named ik_torso on all Objects in the blend, and uses the update function named “update” … (call it something more appropriate than update.)


    
bpy.types.Object.ik_torso = BoolProperty(default=False, description="Change to Update", update=update, name="IK TORSO")

This bit is the update function, where self is the Object that has called the update function and context well that’s the context.



def update(self, context):
    self.data.bones['torso_ctrl'].layers[0] = self.ik_torso


I used the BoolProperty (True / False) as it appears to suit your needs better than Float or int.

Thanks a million batFINGER! That is just working fine!! I just changed it to a FloatProperty, cause I needed it that way

Now, I’ll just drop here a couple of questions, while I’ll try to figure it out myself.

A)

I commented all the panel stuff, just to have the property in the custom properties panel. The ik_torso property already existed, but I see it was replaced correctly with the one of the script. Nevertheless, two issues arise

  1. I have a panel in 3dview where I created a slider for the original ik_torso property.
    armobj = context.active_object
    col.prop(armobj, ‘[“ik_torso”]’, “Torso”, slider=True)

The thing is that if I move the slider from the panel, the visibility thing of the bone does not occur. But if I move the slider of the custom properties panel it works. Shall it be related to the context stuff???

  1. I see this works when you move the slider manually, but not when you animate it… I tried adding the “animatable” option to the FloatProperty but it didn’t do the trick. Is this solvable?

B)

If I needed the inverse behavior in some bones, so that when the property is 0 the bone appears and when it is 1 it disappears, how would that be?

cuase I tried doing:
self.data.bones[‘torso_ctrl’].layers[0] = -(self.ik_torso)

But that didn’t work.

On A,

when the property is defined with bpy.props as in this case, use


layout.prop(ob, 'prop')

#as opposed to

layout.prop(ob, '["prop"]') 


The latter will only work once the prop has been initialised, the former will work off the default value.

Seeing as you have changed it to a float you will need to adjust your update function accordingly


def update(self, context):
    if self.ik_torso == 1.0:
        #do something
    elif self.ik_torso == 0.0:
        #do something else


& for the inverse behaviour swap them. However you may need to have the property on the bones to distinguish which object is firing the update. Now you understand updates a little better the example in my frist post will make more sense on adding the prop to PoseBone, Armature and / or Object.

And yeah, update and animate don’t play together AFAICT, you may need to reinvestigate using drivers. Surely simple drivers like this don’t kill your performance? How did you have them set up to get the cyclic redundancy?. In speaker tools I have had 1000s of complicated drivers running at once without too much performance issue.

Thanks, the gui slider now works!

Well, about the performance, for some reason these kind of drivers always have a negative impact. In my experience, when you put drivers in armature layers, bone layers or in visibility properties, performance is reduced a lot. I would say that almost 0.5 fps per dirver, hehe, and with an i7! On the other hand, drivers in constraints and such stuff doesn’t impact performance at all. I guess it must be because of loops blender has to do for the dependency graph or something like that… You also have to consider this is a very complex rig. But anyway, the rig without these drivers runs at 60 fps.

About the cyclic redundancy, with these driver you get this message, but just when you link the rig, no in the source blend file

Dependency
cycle detected:
Baker_blenrig depends on Baker_proxy through Proxy.
Baker_proxy depends on Baker_blenrig through Driver.

Really weird. The bad thing is that this used to work in 2.69, but in GIT, the armature gets locked… I’ve already reported the bug though.