PyDrivers : what's the point of Variables

Hello everyone.

Here i am again with more questions concerning PyDrivers. I’ve posted some threads already and made some progress, but there are still greay areas…

To sum it up:
With 2.49, i used a PyDrivers.py external file to define fonctions that were used in various drivers across the .blend. Everything smooth and real-time interactive.

With 2.5x, this is much more complex.
• There is this

 bpy.app.driver_namespace['function'] = function

instruction that allows one to use full functions written in any text datablock of the file.
• The text datablock, if its name ends with “.py” can be made “auto-loadable” by clicking on “register” at the end of the text-editor panel.

With this 2 steps, the function is available in drivers when the file starts up.

But what is the need for “variables”?

Let us say, i don’t use a text file, but specify a function in the field for “scripted expression”

186.13-(bpy.data.objects["Switch"].rotation_euler[0]*360/3.1416)

That should be sufficient by itself shouldn’t it ?

But no, that is not interactive in the viewport: if i move my “Switch” object, the driver doesn’t do its job and the relevant object isn’t affected by the changes.

Do the variables have to be use in that particular case ?

No one with a better understanding of that matter ?

I don’t see the point in variables… can someone help ?

To quote aligorith from his blog http://aligorith.blogspot.com/2010/09/rigging-faq-basics-of-new-driver-system.html#more

ALL PROPERTIES used by the Driver MUST BE SPECIFIED AS VARIABLES

So in your little example you would set a variable rx as the x euler rotation transform channel of your Switch object and then put it in your scripted expression as

radians(186.13) - 2*rx

Note it expects radians not degrees.

OK batFINGER, I get your point.

But consider this: i you re-consider the small example above, I DON’T HAVE TO really specify a variable ROT to replace bpy.data.objects[“Switch”].rotation_euler[0].

In fact everything becomes interactive if i just create an arbitrary Variable (let it be called “var”) that is defined as “single property”
as “Prop” I have selected my “Switch” Object, and i leave the Path empty, as seen below:


So to sum it up: I haven’t really declared a variable in the sense that i haven’t specified anything in the var panel apart from the object and i haven’t even USED the var in my scripted expression !

How does that make sense ???

Let’s take that further: I have a file where an object controls the rotation of 48 winglets.
I don’t want to type my scripted expression 47 times, so i use a custom function defined in a text.py file. But i still have, for the 48 winglets to create variables that are useless as the one above ?

the var(iables) in the pydriver are not only to provide an easy way to address those values,
they are there to tell blender to watchout for those objects/bones… and to update on changes.
This could be necessary if you want to view some kind of realtime-animation.

If you have your written script and it is not always running and using cpu-cycles(computer-power), than a change of an object will not force the update if it is not used in the driver as a explicite variable reference.
(edit: i have to say, it looks that a driver in an object can update other objects too
if there are other dependencies - but i dont know those things, it only looks to me from trial and error, that some constraint-settings may update changes, even if there is no variable of that object-property used in the driver-setting)

Blender does not check the scripted expression what kind of objects are used there and if you would use a registered function and use only the function call, then its the same.
If you dont need the realtime update, then you can ignore the usage of such variables. If you dont need to see it in the animation in the 3D-view-window, then you even dont need it to insert in a driver-scripting - you can use a function in the bpy.app.handler part, render_pre or render_post to update things of some objects.
(and take care, the “context” is not always available and you may need to construct your routines without any references to the context, active-object … and so on…)

Alright, in my particular example, the vars are just here to tell Blender that it has to update them ?

I’ve just checked, and deleting the variable altogether, ie leaving only the scripted expression :

6.13-(bpy.data.objects["Switch"].rotation_euler[0]*360/3.1416)

didn’t work. There is no real-time evaluation in the 3D view, and if i animate a rotation of my reference object (“Switch”), nothing happens with the “driven” object.

Open the .blend:
rotating the “Switch” cube moves the other one.

Now if you delete the var of the driver controlling “Cube”, this is lost, and not only real-time: the driven object doesn’t respond at all…

Attachments

untitled.blend (448 KB)

No it doesn’t make sense. Firstly your expression will always be zero. Secondly with no path specified, if you look in the console you will get “Error : driver has an invalid target to use”… so I’m confused as to what has become interactive?

Edit… posted this and then saw new post downloading it now.

Oh ok looks like the new driver system does accept bpy expressions, but expects there to be at least one variable, which makes sense I suppose. In which case you may as well use a var , If you want to use the x euler rotation property then put rotation_euler[0] as the path. alternatively use a transform constraint.

If winglets are going to have different influences then perhaps a driver function is the go, passing an argument to the function… eg

function(rot) for usual cases, and function(rot,0.4) where 0.4 is an influence in the def function(rot,influence=1.0) for example… or pass the winglet number (perhaps write a script to create the driver expressions) and do something in special cases.

ps in my last post i didn’t see the full expression. sorry for being terse.

@batFINGER:

If you want to use the x euler rotation property then put rotation_euler[0] as the path
It is not even necessary: just selecting the object, but no path will do oO ! That why i asked if it made sense !

Now for the function:
Look at the modified .blend below. There is now a function, defined in the auto-loadable .py text-block on the left.
It STILL needs the definition of the var, although it is not used !

Attachments

untitled.blend (463 KB)

It’s really a moot point… plus on further investigation it only works if you add a variable that points to the object you have referenced in your driver’s scripted expression … hence it’s perhaps more of a quirk… It will however work if you assign a variable the way it’s intended.

You are right, it will.

The problem is not that i have to select the “source object” , but that i have to do it for every driver channel (and i have these 48 winglets… and 48 others on the other side of the object).

All that was not necessary and worked like a charm in 2.49.

I really hope some coder will stumble upon that thread and answer me why Blender behaves so !

@test_dr:
very interesting, but there are problems with the file.
I have to rename the text-block so it terminates with .py and re-load.
But the file crashes my Blender after a short while. In fact it happens when i try to delete that “var”
I have to discover how you do that, that’s like black magic to me !

@test-dr

if you replace the script in gwenouille’s file with


import bpy
from math import *

def function():
    bpy.data.objects["Cube"].location[1] += 0.2
    return (6.13-(bpy.data.objects["Switch"].rotation_euler[0]*360/3.1416))

bpy.app.driver_namespace['function']=function

The cube will travel in the y direction on the timeline, however without assigning a variable to the driver expression it doesn’t move in the UI. The y movement wont render.

That said, assigning a variable to the desired channel and using it in a scripted expression works pretty well too.

@gwenoulle

here is a simple little code snippet to add a driver to all the selected objects… i added the object name in the driver function arguments if you need some conditional code in the driver function for instance


import bpy

# add a driver to selected objects collection
targetObj = bpy.data.objects["Switch"]
for obj in bpy.context.selected_objects:    
    print("adding sound drivers")
    driver = obj.driver_add('location',0).driver
 
    driver.type = 'SCRIPTED'
    var = driver.variables.new()
    var.targets[0].id_type = 'OBJECT'
    var.targets[0].id = targetObj.id_data
    var.targets[0].data_path = "rotation_euler[0]" 
    var.name = "ROT" 
       
    
    driver.expression =  'function(ROT,"%s")'%obj.name

and the driver


import bpy
from math import *

def function(rot,name):
    
    return (6.13-(rot*360/3.1416))

bpy.app.driver_namespace['function']=function

1 Like

hi batFinger,
you are right with this example.
The driver will never be executed because there are no changes.
There is no way without an animated object,
my sample with the robot-walking guy works, because the driver runs
in an already animated object. And if its not animated any more, it does not harm
that the driver is not executed because the robot is not walking any more … …

There is something i don’t grasp, and none of these examples help.

In the case of my example file, what s actually that “var” thing ?
I mean, it isn’t even properly defined ! Just a “single property” of “Switch”, no path given… It isn’t used in the espr. above, and yet it appears to be necessary !

It is necessary, but doesn’t have to be defined ! (you can try and change from “single property” to “rotationnal difference”: it works all the same…)

you might call it a bug … or take it as a kind of “flag”, that tells the blender-update-routine to check the driver-script, because there is a reference to another object - even if this object is totally empty.
Btw. there are other, sometimes mystical things, for example you can have a constraint and even if the constaint is set to be ignored (disabled), still blender will spit out errors about dangerous recursion loops, like it does if the constraint is enabled.
But if i get it right (may change from revision to revision …) you need a reference to an already animated object to have the driver evaluated at every time this object changes its properties.

Well, if we don’t call it a bug, maybe we could question the sense of that ?

I mean all the info one need for that particular driver is included in the scripted expression, which in turn may be a function defined in a .py text-block.
There has to be a way to code that in another fashion. After all, 2.49 didn’t need so much twists: just a pydrivers.py file and off you go, real time and all.

In my blend example, the driver uses info from just a single other object. But if you try with a driver getting info from 2 objects (like Obj1.location[0] + Obj2.location[0] ), you’ll need 2 var, one specifying each object !
What if you have a driver that has to perform a sum/ mean from 25 objects’ locations for instance ? A “var” for every single one of them ?

To quote from aligoriths blog again

To ensure that everything works properly (i.e. so that Blender’s lame but still not totally incompetent Dependency Graph knows about all the dependencies brought about by the driver, so that you don’t get any lagging/not-working artifacts), ALL PROPERTIES used by the Driver MUST BE SPECIFIED AS VARIABLES. You then use the names of these variables (yes, they can be named, so please set these to convenient/short names) in the expression. By using short but legible names, you can make the expressions much more easily readable than the old-style expressions people used to have to write. Please DO NOT write stuff like bpy.data.objects[“Parent1”].location.z in the expressions, as they will not be picked up as dependencies, and make the expressions hard to read.

i wonder if you really cannot see it? Did you try to understand batFingers notes?

To say it more clearly: The drivers are not thought to handle dozens or hundreds objects/properties!

Have you ever looked into the blender-gamepart? Its the same there! No one will put in dozens or hundred of logic-bricks and connect those by hand.

Another example that you might understand if you ever played with real “lego bricks”. Maybe you have never noticed it. But building with lego bricks you find yourself very quick looking for appropriate sized bricks for your building. Nearly no one will try to build a big building with the smallest lego-bricks - Everyone will try to use bigger ones to build the structure and only will use small ones to make the details.

The blender driver system is an easy way - without a lot understanding of programming - to tweak single properties of objects. But if you want to handle a lot of objects, you have to do a lot of work on your own to get a structure to handle those complexity. If you did hope blender will take away this task, you are wrong.
With the blender driver-system someone can easy setup changes - not only for movement - there is a lot more, for example let a lamp change its color or power according to the distance of one object.

Sample work: If you want to know how limited the power of the blender-driver-system is. Then setup a moving object, maybe a little car. Create a street-lamp
and set its light-power in a driver according to the distance of the car. Then duplicate the lamp and put it at a few points of the moving-objects path. This can be done wihtout any special programming knowledge. And thats the point.

If you can program, i think you wont ask this question? (Thats, because i think for real programming one would look at things/problems from a different view)

When I made The Most Boring Video Ever I had to add a driver to the three rotation channels for 306 bones… I just got it working on one and then wrote a script to iterate over all the bones and add the vars and driver expression to them.

<whisper>Kind of a crappy system but I don’t think it’ll get ‘fixed’ anytime soon.</whisper>