"Doing something every frame" is a situation that happens a lot when creating animation, in fact it's fundamental to animation itself. Field (and the framework behind it) has an idea of an 'update cycle' that's typically associated with a render loop (for example, the rendering loop of Processing or some other canvas. When you option click on a visual element, its 'updates' are interleaved with every other running visual elements updates, and these rendering updates. This is how 'animation' (or music, or motor control, or ...) gets done in Field.
This animation-is-a-loop actually turns out to be quite a poor fit for most languages, including Java. These languages want you to define a method (in Java) or a function (in Python) that, say, draws something:
def drawTheFrame():
global x
x += 1
drawALineFromLeftToRightAt(x, x/2)
or in hypothetical-Java:
public class AnimationDrawer implements Drawer
{
float x = 0;
public void drawTheFrame()
{
x += 1;
drawALineFromLeftToRightAt(x, x/2);
}
}
Animation usually ends up more complex than this. Once we've swept the line from left to right for a bit, we might want to sweep it from top to bottom, for another 100 frames, and then back to left and right. In pure Java we end up with horrible looking things like:
public class AnimationDrawer implements Drawer
{
float x = 0;
public void drawTheFrame()
{
x += 1;
if (x< 100)
drawALineFromLeftToRightAt(x, x/2);
else if (x<200)
drawALineFromTopToBottomAt(x-100, (x-100)/2);
else
drawALineFromLeftToRight(x-200, (x-200)/2);
}
}
_This high-school math review isn't going anywhere! _ To drive this point home, a) just look at the amount of code you'd have to touch if you wanted to change the length of the first part of the animation. The equivalent in Python or Processing's Java-Lite is a few lines shorter, but no better. In Field we always have the option that we could start arranging visual elements in a timeline. And sometimes this is exactly what we'll do, especially when the chunks are large, intricate and self-contained. But for the example above? overkill.
Enter Python's "Generator". It's a lot like a function, but with a crucial difference. When a function "returns", or falls off the end of it's definition, the next time you call it, it starts at the top again — this is what's happening with the drawTheFrame
function: each time it's called, we start again at the top, with a slightly different x
variable and we keep passing through enough high-school math and decision logic to get to mainly where we were before or the next step of the animation.
In a generator, when a generator "yields" (rather than returns), subsequent calls of this generator pick up just where it left off. If it returns (or runs out of definition) that's it, it's "over", you need to go an get a new generator from somewhere.
Look again at the code above: the key problem is that we have to keep returning control back to the thing that called us — specifically the main animation loop. This problem isn't ever going to go away (unless we get to turn everything on it's head and call the main animation loop itself, which we don't get to do).
Using generators we can rewrite the code above (in Python) as:
def drawTheFrame():
for x in range(0, 100):
yield drawALinefromLeftToRight(x,x/2)
for x in range(0, 100):
yield drawALinefromTopToBottom(x,x/2)
x = 0
while 1:
yield drawALinefromLeftToRight(x,x/2)
x+=1
This code is easier to read, easier to write and easier to maintain. It has less math, it has less control logic. As you begin to combine generators together, you'll realize that it's hard to overstate it's importance for people who are looking to animate things. As we venture below we'll also find even better (terser or easier to maintain) ways of writing this kind of code.
Since generators are undeniably useful, then, Field has incorporated them in as many places as we can find, the rest of this page is some cross cutting documentation that lists these places and features.
Let's start with a generator:
for n in range(0, 10):
print "n = %i " % n
yield n
In Field's text editor if you highlight this and press command-return to execute it, you'll get an error. It isn't quite valid Python. What you really need to do is to write something like:
def myGenerator():
for n in range(0, 10):
print "n = %i " % n
yield n
for n in myGenerator():
pass
But that's long, tedious and doesn't quite achieve what we're looking for, which is to have calls to our generator be interleaved with all of the other updating things in Field (other visual elements, renderers and so on). The code above just "drains" myGenerator
of all of it's work in a single update cycle.
A better approach: back to our original code:
for n in range(0, 10):
print "n = %i " % n
yield n
Select this code and press command-shift-return. This gets Field to do what you'd expect.
Under the hood, the code you run with this approach is getting wrapped up in something a lot like the longer version of the code, and then passed to Field's update cycle as a work item. It gets called for as long as it keeps yield
'ing (and not returning). Once it's finished, it's finished. There's nothing to stop you from command-shift-return'ing a piece of code twice, and have two versions of it execute simultaneously.
Select, command-shift-return is good for a quick little piece of code, but becomes little laborious fast, especially now that you have to remember to push shift, what are the other options? There are at least four:
Field offers an 'execution ruler' that creates annotations based on things that you've previously selected and run. Generators are no exception. Down the left hand side of the text editor you'll have something that looks like:
Option-clicking on this bracketed area is equivalent to selecting the area in the editor and hitting command-return. Guess what? holding down shift and option-clicking on this area is the same as selecting it and hitting command-shift-return.
Still, this means that you have to remember to hold down shift at the crucial point. And, even worse, what you have is still a 'snippet' of code: it wont run by itself in a free-standing Python environment — it isn't really valid Python.
The next idea: introduce a "Text Transform" into the editor (see MayaIntegration and EmbeddingGui).
Select your proto-generator and...
... make a new text transform around it.
By default this 'transform' doesn't actually do anything. Let's select a more interesting one:
This changes the top generator to read:
(Note that these transform markers are actually text fields, you could have just typed insideGenerator
into it as well)
Now we have a Text Transform that takes the text inside it and, wraps it up in the right kind of environment, and submits it to Fields central update cycle. We can execute this text transform by hitting command-1 while the cursor is inside it (or near it) in the Text Editor (the '1' in command-1 comes from the annotation 1. insideGenerator
\1. insideGenerator
, you could change that to read 2
if you wanted a different keyboard shortcut. You can also, following convention, option-click the top transform.
Even better, this Text Transform works just fine if you select the whole thing and hit command-return. The text inside is transformed into valid freestanding Python code.
_r
and the time-line / option-clickingWhen you option-click on a visual element's box (or when a time-line crosses the frame of a box) you are conceptually "starting" and "stopping" this element.
More specifically two things happen: 1) all of the code in the box (the code that is, that's in the main property) gets executed 2) if that execution resulted in a variable _r
being set to something then the contents of _r
are used to determine what it means to "keep executing". All of this convention is documented in VisualElementLifecycles.
But to focus on what this means for generators:
def myGenerator():
for n in range(0, 10):
print "n = %i " % n
yield n
_r = myGenerator()
This code gets you a generator with it's 'updates' interleaved with everybody else (and the renderers). Note the ()
on the last line: myGenerator()
is a generator, myGenerator
(no brackets) is a function that just happens to return a generator.
Finally, here's a short line that takes a real generator and hands it to Field's update loop:
u.stack(myGenerator(), _environment)
You can stop this with u.stop( ... )
(although you have to remember to keep a reference to the generator you made by calling myGenerator()
). (In case you are wondering, that reference to _environment
makes sure that when your generator eventually gets called, variables like _self
are correctly set up, and print
goes to the right place). We'll see why this method is called stack
below.
Strictly speaking generators are a way of playing with the flow of execution through code. Viewed this way, they open up some interesting possibilities.
Rather than just yielding something, you can 'yield' a button. If you are not familiar with Fields exotic mixing of text and GUI elements, you should probably take a look at EmbeddingGui. This kind of button is, right now, called a 'stepper':
This gives you an (inactive) button:
When execution gets to the yield
statement, it effectively pauses waiting for you to push the button:
Push it with the mouse and execution will continue, until that yield
is encountered again. You'll be prompted: the stepper turns back from grey to red again. Option-click (and hold) to let flow continue through this blockage (the stepper will turn Field's 'execution' olive green color).
One final flourish, the button has a 'set' method on it, so you can set the caption on the button, for just a little interactive feedback:
You might be wondering what happens if you can't see these buttons (because you haven't selected the visual element that they are in in the canvas, or because you've started Field in a mode where it has no UI at all, so there's not even a text editor to put a button into). In this case, these buttons act as if they are not there at all — nothing ever gets paused.
One final note, now that in the latest version of Jython (the Java-python that Field uses), yield
is an expression not a statement, that is: yield
can return a value. We're on the verge of extending the idea of a Stepper to include just a little bit of UI: a slider, a text box, things like that. The value you select will be the value 'returned' by the yield
expression.
This, most recent version of Jython, has also brought with it another language feature from Python: Generator expressions. Rather than writing:
def aGeneratorFactory():
for n in range(0, 10):
yield n
aGenerator = aGeneratorFactory()
You can write:
aGenerator = (n for n in range(0, 10))
This is really just like a list comprehension only there's no list ever made, it's a generator that's returned.
If you want to see the results of evaluating a generator, you can always use the pretty-print keyboard shortcut command-⌃/.
_ex
Generator expressions are tremendous fun, just in the same was as generators themselves are. Can Field provide a little lubrication to let them become really useful?
Enter _ex
(for EXtended assignment). _ex
is an object that rewrites the rules of assignment for global variables. It uses the techniques discussed in LazyFunctionalHelpers and KeyframingInCode to weave it's magic.
Let's start simply:
v = Vector3()
v.x = 5
print v.x
gives the vector (5.0, 0.0, 0.0)
.
Say that this vector here was actually painstakingly obtained from deep within a piece of Java, or it's a global variable that's being used by another piece of Field somewhere, to set the color of an object. How to animate this color? It's natural to wonder if generators can be used to make little expressions that vary this Vector3
over the render cycles.
This, is legal, but tedious:
def colorAnimator():
for n in range(0, 100):
v.x = 255*n/100.0
yield
u.start(colorAnimator(), _environment)
Or you can use any of the other techniques above to 'start' the generator. No matter, it's still a little long winded if all you want is to ramp v.x from 0 to 255 over 100 frames.
Of course, it would be nice to be able to write:
v.x = (255*x/100.0 for x in range(0, 100))
But that isn't legal — you can't 'assign' a generator expression to a Java field and expect anything other than an error. In Field, the following is legal:
_ex.v.x = (255*x/100.0 for x in range(0, 100))
While we're here, this is also legal:
_ex.v.x = dict(value=255, over=100)
Which takes v.x
from wherever it is to the value 255 over 100 frames of animation. We've spent an incredible length of time finding numbers, tuning parameters, animating them a little, transitioning between sets of them; it's always nice to have as tight and as terse a syntax for doing this as possible.
The source of this 'extended assignment' is SimpleExtendedAssignment and you can use this to add handlers for various otherwise mismatched assignments.
Finally, one concept that's universal to Field's treatment of Generators — their execution environment. Rather than just calling a generator each 'frame' Field pays attention to what that Generator yields
. If it yields something 'interesting', like another generator, Field forms a stack and adds this new generator to the head of it. Next time around that generator gets called; until it's done, the stack is popped and 'control' is passed back to the original generator. This is such a good idea that many people have invented it on their own, not least of all the Python people themselves (it's examples 3 in the Generator documentation).
What this means is that you can create animations by chaining generators together in a truly modular fashion — just by having a generator yield another generator to get part of it's job done.
To get a sense of this composition, the nonsense code:
def sinewavey(f, a, duration):
for n in floatRange(0, 1, duration):
x = a*Math.sin(f*n)
print x
yield x
def randomSine(num):
for n in range(0, num):
yield sinewavey(Math.random()*4, Math.random()*3, 100)
_r = randomSine(2)
This uses a simple generator sinewavey
to make a more complex animation (a set of skipping chunks of sinewaves). This is truly modular: the specifics of sinewavey
, even its duration, is kept outside randomSine
.
Finally, we note that this works just fine as well:
_ex.v.x = randomSine(2)
As does the easier to write, and harder to read kind, of code that would organically evolve under your fingers as you search for something:
_ex.v.x = ( (a*Math.sin(f*n) for n in floatRange(0, 1, duration)) for a,f,duration in [ (Math.random()*4,Math.random()*3, 100), (Math.random()*4,Math.random()*3, 100) ] )
Generator stacks are used every time that Field introduces a generator into the top-level update cycle (specifically, they are used for interpreting _r
and the various shift command--returns discussed above.