4
$\begingroup$

When creating a custom property via e.g. bpy.props.FloatProperty(), we can specify a user-defined function to be invoked every time the value of said property changes.

Once the property exists, e.g. added via the Properties Editor, how do we assign or re-assign the update function?

The Properties Editor (Blender 2.74) does not offer that, and, in Python, I don't know how to access the property itself, as opposed to merely its value.

$\endgroup$
  • 2
    $\begingroup$ Changing the callback function after registration isn't possible, unless you re-register the property. A dev once mentioned, that doing this might leak memory, but in my test that wasn't the case. You shouldn't re-register stock properties to add a callback however, it can crash Blender. $\endgroup$ – CoDEmanX Aug 9 '15 at 1:02
  • $\begingroup$ Re-register? Crash? Is this something that refactoring, and dependency graph, gurus are looking into these days? $\endgroup$ – Tom Telos Aug 10 '15 at 8:56
  • $\begingroup$ Don't think so. It crashes because you cross the boundary between Python and C code I think. It's kind of undefined behavior. What seems to happen if you re-register Object.name for instance, is that a 2nd property with same name is created and interferes with the original. $\endgroup$ – CoDEmanX Aug 10 '15 at 10:07
  • $\begingroup$ Thank you! The thing to do, then, is to register the <i>name</i> of the custom property and of its update function, as suggested by @martinlindelof, and subsequently edit the function as ideas evolve. My motivation is separation of concerns: e.g. define a custom property called "smirk", to be used by moving a slider from 0 to 1 (or, -1 to 1) without having to be, during use, aware of whether it is being implemented by shapekeys, or bones, or what. $\endgroup$ – Tom Telos Aug 10 '15 at 10:38
1
$\begingroup$

I am looking into it myself atm.

some_value = FloatProperty(default=1.0, update=onupdate)

and then I have a function that handles the update

def onupdate(self, context):

it's important that the function handling has self & context as arguments, and returns none.

You can read more here.

$\endgroup$
  • $\begingroup$ Aha! Thank you. Have you confirmed that this actually works? Has something changed between Blender 2.74 and the so-called "massive" update 2.75? $\endgroup$ – Tom Telos Jul 10 '15 at 10:07
  • $\begingroup$ The very basics I can assure you works in 2.75 I am using it, by very basics I mean, when you create your FloatProperty in your case, you can set update= a function that is called each time the value changes. but if you can re-assign it later I am not sure. $\endgroup$ – martinlindelof Jul 11 '15 at 10:27
  • $\begingroup$ <i>...if you can re-assign it later I am not sure...</i> $\endgroup$ – Tom Telos Jul 11 '15 at 18:53
  • $\begingroup$ When in "use" mode, you don't want to have to remember whether that custom property causes an empty to rotate, or what; when in "improve" mode, you try to optimize the technical details behind the scenes, and, for that, a Python script looks handy, doesn't it? But that's why you want to assign or re-assign an Update Function at any time! $\endgroup$ – Tom Telos Jul 11 '15 at 19:09
  • $\begingroup$ I'm thinking of using a custom property with a name like "smirk" to specify a complex set of adjustments to a (hopefully, realistic) humanoid figure. $\endgroup$ – Tom Telos Jul 11 '15 at 19:12
1
$\begingroup$

Use drivers!

Drivers act as an update function and are calculated in every frame of the animation.

Suppose you have a Custom Property obj["prop"] that you need to control frame by frame, and a function pyFunction(self, targetName).

To set it up, you first need to add a simple line in your py code:

def pyFunction(self, targetName):
    obj = bpy.data.objcts[targetName]
    #Your code here
    return x #where x is the value you want the driver, and therefore your ["prop"], to have

    #this line lets Blender know that the pyFunction can be used in driver expressions
    bpy.app.driver_namespace["pyFunction"] = pyFunction

then you can create a driver on the ["prop"] property, and write in the expression field pyFunction(self, targetName), or whatever your variables are.


Note that all variables in that function must be initialized in the driver itself, so for "self" you need to tick the "Use self" checkbox, and for the others you need to create variables with the driver GUI (as a tip, if you want to put an object as a variable, create a single property variable in the driver and write "name" in the data path field, then in your pyFunction remember to get the object from the name, as I did in the code snippet before).


In alternative, you can create the driver on another property or object (like a hidden null). If you set it up this way, it doesn't matter what you put in the return of the pyFunction (it only controls the driver's value), but you need to change the property in the function, something like:

target = bpy.data.objects[targetName]
control = bpy.data.objects[controlName] 

#links the Custom Property to, for example, an object's location, however this can be any number you want
target["prop"] = control.location.x

#the return is not mandatory here, the driver will just have the value 0 

In any case, the driver calculates the pyFunction every single frame as an update function, and execute the pyFunction's commands every frame

$\endgroup$
  • $\begingroup$ Thank you! Would this solution have worked in 2015? It certainly deserves that I try it. ˆ_ˆ $\endgroup$ – Tom Telos Apr 7 at 3:36
  • $\begingroup$ @TomTelos I don't know, I started coding only a few months ago ;P $\endgroup$ – Tareyes Apr 8 at 8:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.