Wednesday, September 28, 2011

Driver "Variables" - Why they exist

I'm aware that there has been a bit of angst among some users of the driver system in 2.5 over the need to define "variables" for drivers to recognise their dependencies correctly and update accordingly (assuming that this is one of the few cases the depsgraph allows us to safely achieve).



To paraphrase one obviously annoyed and/or confused user who commented on this recently:
"... 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"

Code:
186.13-(bpy.data.objects["Switch"].rotation_euler[0]*360/3.1416)
That should be sufficient by itself shouldn't it ?"

In order to understand why things are the way they are, let's firstly take a look at where "variables" come from, aka their "natural habitat". Of course, I'm referring here to programming languages. There, variables basically give us a way to calculate/grab some value and give it some name or identifier which we can use to refer to that value again later.
1) Variables provide us with a way to store some value for referring to later using a simple name/identifier
By using descriptive, yet not overly verbose names to our variables, we can use this to assign meaning to the values that we calculate or grab from somewhere, within the context of the problem for which we're defining the variable for. So, for example, if we're using the z-value of some object to act as a input for some shapekey influence value (e.g. for a 3D slider widget), if we grab that value and store it in a variable "sliderval", we're now describing the quantities we're dealing with. This serves to document our setup, so that others may be able to look at it and figure out what the heck is going on (or perhaps even for yourself to figure out how an old rig of yours worked several years into production, and something breaks/needs to be added).
2) Appropriately named variables provide us with a name to document our driver setups
As you gain experience writing code and/or developing software, the ideas of abstraction and reuse come up time and time again.

If just considering performance, then a common optimisation (especially in a dynamic language like Python) is to fetch values that you're going to use often once at the start, instead of calculating ad infinitum everytime you need it. You might also be interested to know that in a dynamic language like Python, where attribute lookups are done at runtime (usually using string-lookup-in-hash-table or similar) instead of perhaps just reading the value of some address determined at compile-time (for C-like language), looking up those long data paths can add up. (Admittedly though, this is NOT really such a big deal, though it is something to keep an eye on. The anim system does this constantly these days in order to achieve its necessary flexibility - animate everything, but having said that, it tries to avoid doing this unless it has to, i.e. not scanning the paths willy-nilly but rather only when it needs to actually affect those values.)

Sure, today I might just refer to that single property once. But that's not to say that later today, tomorrow, or some time in the future I'm not going to end up wanting to use this variable again somewhere else in that expression. Or perhaps add an even longer property getting string to get some other property as well. Or perhaps I decide that this value isn't actually doing what I want it to do, and I now need to change it to some other property.

By explicitly defining how to get a specific property inline like this, you're explicitly tying implementation details to the definition of what you're trying to achieve - i.e. you're mixing semantics and mechanics.

Getting back to the point about abstraction, by spraying around long-winded data paths all around the place, you're less able to focus on the big picture of how you're combining several inputs which serve certain purposes, and more about low-level grovelling about in the database for some property that only you know what they're meant to stand for.

(Incidentally, thinking about this a bit, I guess this is one of the reasons why I despise the "concatenation" style of preparing a string for printing to stdout. Things like, "some text"+varA+"more text"+varB[0]+"even more text"..., as it becomes unclear what format the final string will take, with all that extra syntax and data-access cluttering things up. IMO, the printf() style is far superior)

3) Use of variables allows us to focus on what we're doing better, by abstracting away "getting data" from "using data" - separating the semantics of what you're trying to achieve from the inputs that you need to achieve this

For example, take a look at the following (maybe contrite) example:
WITHOUT:
bpy.data.scene["Scene"].gravity[2]*1000.0+(2.8+34*bpy.data.objects["Cube"].location[1]*bpy.data.objects["Cube"].location[1])) +3.2832*bpy.data.scene["Scene"].current_frame/235.23
WITH:
zpull*1000.0 + (2.8f+34*dist*dist) + 3.2832*cfra/235.23

zpull = Scene || gravity.z
dist = Cube || location.y
cfra = Scene || current_frame

In which of these two expressions can you clearly see how we're trying to combine these values together? And in which of these was there a syntax error? How long did it take you to notice that?

Now, what if you're constrained to only be able to see a relatively narrow text box with these expressions displayed in them, where perhaps you could only see a third of this page width at a time? In which of these are you now going to be able to easily navigate around, be able to get the "big picture" of what you're trying to calculate and/or how? How long would it take to locate the syntax error, keeping in mind that when things are scrolled out of view, you'll only be able to check the rest of the text by moving forward 1 character at a time.

---

So, by this point you may/may not be still wondering: How does this all relate to why we must define variables when creating drivers in Blender?

Well, anyone who's done a bit of rigging will start to realise that actually, it's a lot like programming. You're defining a program or tool (i.e. "the rig") which acts as the interface that some end user (i.e. "the animator(s)") interacts with.

In fact, anyone who's played with the Nodes editor - be it Compositing Noodles, or Cycles Material Noodles - has actually been doing programming. In the academic literature, it's referred to as "visual programming". Basically, instead of writing lines of code, you're joining up blocks; sequencing the order in which data flows between these blocks, getting operations performed on them.

There are several reasons why I've brought up nodes here:
1) Drivers and constraints implicitly form such a node graph already
2) People have talked about their desire for "node based rigging" for years, maybe after watching a few too many Houdini demo vids at Siggraph :P
3) Nodes present this very separation of inputs from operations

Take the "mix" node for example. It defines an operation: combine two values using the chosen method, but as for the value that it combines, well those are fed into it via its "input sockets". The mix node DOES NOT go out and say: "I'm going to grab ...data.property1 and combine it with ...other.data.property2", but it says, "I'm going to combine the two inputs I have, whatever they were".
4) Even the nodes separate out their inputs from their operations...
By setting out the dependencies like this, it's trivial to figure out what values a node depends on, simply by tracing what inputs it was getting. The same applies for drivers.
5) Use of variables allows Blender to easily identify and/or check on whether the dependencies of a driver have changed, and hence whether the driver needs updating
You may wonder why it "worked" in 2.4 in the past. Well, as it turns out, the way used back in 2.4 was (as many other things) an almighty ugly hack. For instance, IIRC, the old PyAPI code used to specifically override one of the Object class methods so that it would dump the objects accessed in the expression into a list if a global flag was turned on when specially running the driver to grab dependencies.

Apart from only working for objects, this implied that everytime you wanted to know what dependencies such a driver had, you'd have to run it to extract the relevant dependencies. This was hardly productive at all...
6) Specifying all your inputs separately and cleanly using some predefined template format (such as variables), means that Blender can figure out the dependencies involved with much less effort
---

So, in conclusion, why are driver "variables" necessary?
A) They make life easier for you, as they help to document your setups, and reduce cluttering your expressions with low-level details, obscuring the greater goals
B) It makes life easier for Blender too, making it easier to identify what dependencies a driver has
C) I decided to get pedantic about this, and try to force the user population into developing "good habits" for the good of everyone (including the software) involved, which I believe ultimately boasts better overall outcomes than if we let everyone go ahead and run around using stone-age clobbled hacks.

3 comments:

  1. great post, and totally relevant to good(advanced) rigging practices. When oh when will we get a decent depsgraph? The issues with the updating of drivers and constraints is a fairly major weakness with blender ATM, and its odd the number of years that its gone, imo, mildly unaddressed. I too end up using variables and/or writing pyScripts to force frame changes & updates just to have my rigs actually pose as they should on a given frame.

    back to the post, regarding your point on houdini, you can AFAIK use variables and functions(in code) to define input sources for nearly any type of node, rig or otherwise (even changing them dynamically on a per-frame-basis, in code, or with "switch" triggers), so not sure what you meant there.

    ReplyDelete
  2. Oh, regarding Houdini, I just meant that people had just been lusting after using node trees to define their rigs for years.

    As for the depsgraph: well for a few years I've been kindof reluctant to touch the thing, even though I know it does need some work, just due to the difficulties in the task.

    A few years ago, some of the frontend tools were more important to improve than the depsgraph, which comparatively few would run into troubles with. Furthermore, tackling the depsgraph issues is a project in itself - something that would have meant possibly delaying the introduction of improved tools that bring us up to speed with the rest of the world first.

    ReplyDelete
  3. Bravo, Aligorith!

    On top of everything you've just said, I would also point out that driver variables allow the user to easily express more complex ideas via different variable types. So, for example, the distance between two bones in either local or world space, which would be an enormous pain to express directly in the driver expression!

    All-in-all, this is an extremely useful and powerful system, and I am very grateful for your work on it!

    ReplyDelete