Converting Processing code

In this page, we'll make a narrative walkthrough of converting some 2d, line drawing Processing code to use Field's 2d drawing package based on its FLine class. This class is reasonably well documented, both in reference documentation, here, and in the auto-complete text for the class FLine. For an introduction, see BasicDrawing.

Importantly, this page serves two functions — first, to show how I go about translating Processing/Java into Python (typically more compact); secondly, to hint at the 'style' of using Field (evaluating expressions frequently and using autocomplete a lot). It isn't an example of using the Processing Plugin — which would allow you to rewrite some Processing/Java code in Field that would still use Processing's graphics libraries. In the case here, the geometry you create ends up directly in the canvas, whereas if you use the Processing Plugin, the geometry ends up in the Processing Applet window. But other than the details of just how you draw a line, everything else in this document should still be relevant.

  • The .field sheet that contains the code for this page can be downloaded here.

A simple example found online

I'll take a fairly arbitrary example — a tree drawing example from Ira Greenberg's Processing — Creative Coding and Computational Art. This example is representatively messy and has about the right level of complexity for our purposes here. It's the kind of Processing code examples that you might find online. Since I can't find a license for their examples, I'll link to them: copies are available here, as the code from chapter 2, available from the publisher's website here: http://www.friendsofed.com/download.html?isbn=159059617X . You'll want a copy in another window anyway to follow along.

Before we start we need to make a new place to write code — a new 'visual element'. Because we'll be predominately drawing splines, we'll have a new "Spline Drawer". Right-click on an empty bit of a sheet and make one:

Now for some code translation

First, I skip the imports, the global variable declarations, and their short setup() function. They all seem like things that I can come back to. (Often in Java, you are declaring global variables simply to give them a type, Python cares much less about types). setup() has a privileged role in Processing that Field tries to avoid.

After skipping all that I start writing out the first function:

def trunk():
    for n in range(0, tru

Ah ha! a global variable, let's go back.

trunkSegments = int(random(7))+5

def trunk():
    for n in range(0, tru

After I type the first line I evaluate it (with command-return). If your only experience of programming has been Processing, this might be a little strange — after all, there's clearly a syntax error in this "file", you aren't ready to push the play button just yet. In Field, you can evaluate a line or a selection — this is a little bit like sending just that part to the compiler (and to the 'play' button). But there's an important distinction — the "Applet" never quits, you are 'sending' snippets code to the Applet to be executed as it continues to run.

By executing this line of code we accomplish two things, first it checks it to make sure that I haven't made a mistake (missed a bracket or something), and secondly it actually makes that variable trunkSegments and gives it a value.

Turns out I have made a mistake: the line turns red.

Field lacks Processing's convenience function 'random'. No matter, Java itself has a random method, we can quickly work around this:

trunkSegments = int(Math.random()*7)+5

def trunk():
    for n in range(0, tru

Since trunkSegments now exists, I can use auto-completion (pressing command-period) to finish typing the word 'trunkSegments':

The first (and in this case only) candidate completion is the correct one, I can just hit return. If I look closely at this menu of options I can see the current value of trunkSegments is 11, which is within the range I was expecting. I can 'autocomplete' almost anything in Field — Python functions and variables that I've just declared, Java classes and methods that are part of Field, classes and methods that are part of third party libraries, part of my own Java libraries that I've added to Field, and so on.

Back to the translation:

trunkSegments = int(Math.random()*7)+5

def myRand(n):
    return (Math.random()-0.5)*2*n

def trunk():
    for n in range(0, trunkSegments):
        lean = myRand(22)

I encounter a utility function that the author has made, myRand, so I insert that up above the function that I'm working on, execute it, and use completion again to insert it. Note: I'm working in very small chunks, testing syntax as I go, and typing as little as possible. If I really wanted to, I might take a break here and run a few lines like:

print myRand(10)
print myRand(-1)
print myRand(0)

Just to make sure that myRand is doing what I expect. Actually, such experimental 'execute this and print it' is so common that there's a keyboard short-cut for it: command-/ executes a line (just like command-return) but with 'print' magically put in front of it. That way, if you want to see the value of something that you've just written, you don't have to move the cursor at all.

Back to translating the Processing/Java code into Field/Python:

trunkSegments = int(Math.random()*7)+5
radius = 26

def myRand(n):
    return (Math.random()-0.5)*2*n

def trunk():
    for n in range(0, trunkSegments):
        lean = myRand(22)
        line = FLine(thickness=radius+22)

Another global variable radius, and now we start drawing. Unlike Processing, where drawing goes to the screen, drawing in Field goes to an object (which might end up on the screen after being transformed and edited, or it might not). So we'll make one (a FLine). Since it's not going to end up on the screen immediately there's nothing to stop me executing this new line line # FLine(thicknessradius+22). That way, I'll get auto-complete on line. to help me remember how to draw things to it.

Onward:

trunkSegments = int(Math.random()*7)+5
trunkLength = int(Math.random()*50)+130
radius = 26

width = 500
height = 500

def myRand(n):
    return (Math.random()-0.5)*2*n

def trunk():
    lean2 = [0]
    for n in range(0, trunkSegments):
        lean = myRand(22)
        line = FLine()(thickness=radius+22)
        line.moveTo(width/2+lean2[n], height-(trunkLength/trunkSegments)*n)
        line.lineTo(width/2+lean, height-(trunkLength/trunkSegments)*(n+1))
        _self.lines.add(line)
        lean2.append(lean)

So it takes me a moment to work out what's going on in the original code — in the Java/Processing there's a static array of floats declared. In Python there's no need to do this; we can just append the values of lean to a list.

I've added the line _self.lines.add(line) to put the pieces of the trunk that we're drawing onscreen. And I've also had to introduce two new globals — width and height. In Processing these are the width and height of the canvas. In Field, the canvas is infinite and resolution independent. So we just have to pick a width and height.

Now we can just execute this (literally, we can write trunk() somewhere in Field, selected it and execute it), and see what we have:

Now let's finish the trunk and get onto the branches:

trunkSegments = int(Math.random()*7)+5
radius = 26

width = 900
height = 900

def myRand(n):
    return (Math.random()-0.5)*2*n

def trunk():
    lean2 = [0]
    for n in range(0, trunkSegments):
        lean = myRand(22)
        line = FLine()(thickness=radius+22)
        line.moveTo(width/2+lean2[n], height-(trunkLength/trunkSegments)*n)
        line.lineTo(width/2+lean, height-(trunkLength/trunkSegments)*(n+1))
        lean2.append(lean)
        _self.lines.add(line)

    pts = [Vector2(width/2+lean2[-1], height-trunkLength)]
    branch(pts)

This function ends with setting up the variable pts. In the original, this is, again, a statically declared global array. In this translation, we again just use a variable length list. Benefits? here, it's mainly that we don't have to figure out or guess the ultimate length of the list ahead of time (the original code literally guesses 20000 somewhere).

Secondly, at the end I've used a Field Vector2 class rather than a pure Java Point2D.Float class. Vector2 comes with all kinds of helpful functions for doing Vector math that Point2D doesn't have. It also comes (as we'll see) with operators like '+' and '-' that make it easier to write this kind of twiddly little geometry.

Let's keep the code above and add to it the next function:

counter2 = 0
branchLimit = 320
g = Vector2()

def branch(pts):
    global counter2, radius, g
    stemCount=2

    line = FLine()

    if (counter2>=branchLimit): return
    if (counter2<200):
        line(thickness=radius)
        g = g - Vector2(Math.random()*0.625, Math.random()*0.54)
        if (radius>2):
            radius*=.931
    else:
        stemCount = 2+int(Math.random()*15)
        line(color=Color4(0,0,0,0.2))
        g = g - Vector2(Math.random()*1.5, myRand(0.65))
        radius*=0.91

    for j in range(0, stemCount):
        offset = Vector2(myRand(30), myRand(40))
        pts.append(pts[counter2]+g+offset)
        orgLine(line, pts[counter2], pts[-1])

        g.x *= -1

    counter2+=1
    _self.lines.add(line)
    branch(pts)

A couple of things to note. Firstly, we're starting to get into a little bit of Vector math, so I've translated code that says things like x # something; y somethingElse into v = Vector2(something, somethingElse). This lets us write that last line in a way that's much clearer. Secondly, I'm using Python's convention of list[-1] to access the last member of a list (list[-2]} would be the next-to-last, and so on). During this time I keep jabbing command-0 to execute all of this to check for syntax errors.

Finally, and this is the most important 'gotcha' for someone coming from Processing, I have to declare global variables that I modify in functions. Here global counter2, radius, g. Only the globals that I write to have to be declared (since writing to globally shared things is in some sense more dangerous than reading from them).

Now lets add to the above the final method that we need to translate, orgLine:

def orgLine(line, start, end):
    line.moveTo(*start).lineTo(*end)

Well, not quite. Actually, I've grown nervous — I haven't seen anything on the screen for a while. So I dash off a placeholder for the 'organic line' drawer and then add:

_self.lines.clear()
trunk()

To the bottom of the element and hit command-0 to execute the whole thing. (The first command clears any previous lines associated with this element, and the second actually calls trunk to get the ball rolling).

Looks like a tree! While I like those dots at the ends of the branches, where did they come from? Turns out they are only visible because the drawing (this 'spline element') has been selected. Field is showing us the nodes of the lines (just in case we want to select them and edit them with the mouse).

Finally, the 'organic line' routine (which replaces the above code):

def orgLine(line, start, end):
    sections = 8.0
    d = end-start
    twist = Vector2()
    line.moveTo(*(start))
    for n in range(1, sections):
        twist += Vector2(myRand(5), 0)
        dm = d*n/(sections-1)
        line.lineTo(*(start+dm+twist))

Here we've dispensed with another statically declared array completely.

And a, presumably more organic, looking tree:

Say, I think this is just the greatest orgLine! So good, I want all of the FLine's to have it:

FLine.orgLine = orgLine

Now all new FLines will understand if you write: .orgLine( Vector2(10,10), Vector2(100,30) ). Even Field's 'standard libraries' are up for extension by the working programmer. (Note, we haven't actually included this one in the distributions of Field).

In any case, in the final code above, we've also replaced a set of Processing line(x1,y1,x2,y2) call's with a .moveTo() followed by a number of .lineTo(). Indeed, looking back, we can do the same thing with the opening truck() function:

def trunk():
    line = FLine()(thickness=radius+22)
    line.moveTo(width/2, height)
    for n in range(1, trunkSegments):
        lean = myRand(22)
        line.lineTo(width/2+lean, height-(trunkLength/trunkSegments)*(n))
        _self.lines.add(line)

    pts = [line.position]
    branch(pts)

We've also deleted even more math — taking advantage of the data-structure already built into FLine to keep track of position rather than using another variable.

What's the difference if there is no difference onscreen? Firstly, and rather abstractly, should you want to take the line that we've made here and filter it, it's good to be able to describe your actual intention into the data-structure — a tree trunk is a single line that might wobble back and forth a bit. It isn't a set of free-floating line segments. Secondly, and much more concretely, when we export our visual element to a PDF:

The PDF renderer (here Preview) knows that we want those lines connected smoothly:

Things to watch out for

In a more summary style: things to watch out for when you are moving from Processing to Field.

  • Watch out for ways of writing less code and typing even less — exploit Python's dynamic type system (no need to declare many variables ahead of time); exploit Field's Vector classes to handle the inevitable little bits of geometry that crop up; exploit Python language features (for example list comprehensions, lambda functions). Finally make sure you exploit Field's rapid evaluation and auto-completion to let you explore without compiling, running, stopping and compiling again.

  • Watch out for missing convenience functionsrandom for example. Field errs on the side of having fewer functions in the global namespace (in Field, you are allowed to call your own function random). Of course, if you like it the other way, the Processing Plugin will happily inject Field with all of these functions for you.

  • Watch out for division — in both Java and Field 3/4 actually equals 0. Often you are expecting it to equal 0.75. If that's what you were expecting, then you wrote it wrong. But sometimes the problem goes a little deeper in Python-based environments. While in Java you can write x/4 and have somewhere else float x # 3. If you "translate" that code into python you might write just x 3 (Python doesn't need the 'float' part of that line). This gives you 3/4 again, very different from 3.0/4. What you really mean is to write x = 3.0. This is so contentious in the Python community that they've promised to change it (indeed, you can change it right now by executing the magic incantation: from __future__ import division).

  • Watch out for Processing Applet variables — Processing Applets have very important variables width, height and mouseX, mouseY and so on. Outside of the Processing Plugin these things basically don't exist or aren't relevant. Inside of the Processing Plugin these things are always referred to with explicit qualification as p.width, p.mouseX and so on.

  • Watch out for global variables — Python frowns on global variables where Processing smiles on them (they aren't really global, they are local to the Applet). In Python you have to declare the globals that you write to inside functions — it's one of the few places where Python adds syntax to slow you down. Well, careful, in Field, they really are global.