Consume, don't compete

One principle behind Field is that we like to consume other applications (by wrapping, linking and bridging) rather than compete with them (by re-implementing their features and interfaces). This is the principle that led to the ProcessingPlugin, the MaxPlugin and it's part of the idea with our interest in including other languages into Field. Field is a cake-having-and-eating framework.

The page documents how Field can reach out to other applications.

Integration with Maya

Obviously one way is through file formats — and Field's graphics system can read FBX files. But too much emphasis is placed on "standard file formats". There are more interesting forms of integration. One set of examples are from Autodesk: Maya and MotionBuilder now have Python. This can only be a good thing — 3d graphics programs have for a decade or more labored on with badly designed and badly implemented languages (there was a moment in time when MaxScript couldn't make up its mind if its arrays were 0- or 1- indexed).

Of course, in keeping with what, to our eye at least, is an incredibly flakey and ugly user interface, Maya's script editor leaves a lot to be desired. So there's been some interest in using Field — mainly in its capacity as a glorified text editor with a strong desktop metaphor — to execute commands in Maya. I'm very interested to see where this will be taken by people who are much more invested in Maya than we have.

Fortunately, "executing code somewhere else" is something that I've needed to do a lot in Field. And as of this writing there are three main ways for doing this in Field. The first are plugins — for example the MaxPlugin — which is useful for communicating between Field and Max/MSP instances and OnlinePlugin for doing browser hosted Javascript. But the other two work with Maya (and MotionBuilder) just fine.

Text transform area

Causing some part of the text editor to end up executing somewhere else in Field is easy — you can use a block transformation to achieve this result.

First, you insert into your text editor a "block transform begin" and a "block transform end" (right-click and select these from the popup menu). These are just embedded labels: give the begin the name 1. mel and the end the name \1. mel. Text transform begin's look for identically named 'ends' to pair up with to demarcate an area in the text editor. The mel refers to a function we're going to define that's going to be the text transform. The 1 tell's field that we're going to use command-1 as the keyboard shortcut for executing this text block.

You can right-click on the top text transform to get a list of pre-defined transforms (many of the ones written about here are available from this list, and you can add your own, see below).

Now we need our function mel that takes a string and causes it to be sent over a socket to Maya:

import telnetlib
_self.host_ = _self.host_ or telnetlib.Telnet("localhost", 8999)

def mel(scope, commands, outerGlobals):
    _self.host_.write(commands)

So now we have something that looks like:

And in in Maya itself you run the command:

commandPort -name ":8999"

runs the telnet MEL-server. By the way, adding -prefix "python" to that, gets you a fairly useless python-server (see below for some better ideas).

In MotionBuilder, you are looking for this dialog:

which will start a telnet python server on port 4242.

Now we have an area in the Field text editor that, when it's executed, is actually executed in Maya or MotionBuilder instead. The code looks like any other piece of code to the rest of Field: that is, it can be executed in timelines, it can hold embedded UI elements, it's version controlled &c. It just happens to execute remotely.

Sending things over to a telnet socket is so useful (above and beyond Maya integration), that it's included in Field's standard library — just use the text transform telnet(portnumber) — no further work necessary.

While we're here we can note two things about the block transform system. Firstly, by convention the string passed into the telnet(port[,host]) transform is automatically %'d with globals(), so we can include variables from the enclosing scope by saying %(myVariable)s. Secondly, it works just fine with Python's indentation. So we can also have the following, slightly odd looking mix of Python and MEL:

telnetMaya — straight Python into Maya

Recently added to Field is a "telnetMaya(port=8999)" text transform that hacks out a multiline telnet python console from Maya. Again, you'll need to execute commandPort -name ":8999" in MEL in Maya. Then the telnetMaya transform lets you write multi-line python directly inside Field.

SimpleXMLRPCServer

Another, more conventionally-python way, is also perfectly possible inside Field: use Python's SimpleXMLRPCServer, which, as the name suggests makes an XML-RPC server. Because there's Python at either end of this communication, this is very easy:

In Field:

import DictXMLRPC
cmds = _self.cmds_ = _self.cmds_ or DictXMLRPC.dictXMLRPCClient("http://localhost:8999")

(In order to fully understand this idomatic use of the Field special variable _self you might want to look at SelfAndProperties).

In Maya, the slightly ungainly:

import thread
from SimpleXMLRPCServer import *
from maya.utils import *


class mainThreadHandler(SimpleXMLRPCRequestHandler):
    def _dispatch(self, method, params):
        x = getattr(cmds,method)
        kw = {}
        p = []
        for n in params:
            if (isinstance(n, type({}))):
                kw = n
            else:
                p.append(n)
        p = tuple(p)
        y =  executeInMainThreadWithResult(x, *p, **kw)

        return y
    def log_message(self, format, *args):
        print format % args

def dispatchForever(x):
    server = SimpleXMLRPCServer(("localhost", 8999), mainThreadHandler, allow_none=1)
    print "registering"
    for n in dir(cmds):
        xx= getattr(cmds, n)
        if (callable(xx)):
            server.register_function(getattr(cmds, n))
    print "registering complete, serving"
    server.serve_forever()

thread.start_new_thread(dispatchForever, (None,))

A more competent Maya person could perhaps assign this to a button, or make it run on startup. But most of this code (including the Field standard library class DictXMLRPC) comes about because of SimpleXMLRPCServer's unwillingness to handle keyword arguments (and Maya's reliance on them). There's also some messing around with Maya's threading model.

In any case, now we can just write:

mySphere = cmds.sphere(radius=20)

in Field and it's magically executed in Maya. As is:

spheres = []
for n in range(0, 4):
   spheres.append(cmds.sphere(radius=20+n*10))
for s in spheres:
   cmds.move(10,20,30,s)

And so on — it's simply Python code. Return values are 'correct' (which would be more exciting if Maya made better use of them), auto-completion works &c. And again, the code looks like any other piece of code to Field.

AppleScript & everybody else

Apple makes "forced entry" into proprietary applications quite easy with its AppleScript support.

Field includes the applescript text transform:

Executing this causes the Finder to show a file.

Based on this, we can begin to sneak our way into other applications. Recently, a frustrating journey into the depths of After Effects's JavaScript scripting got a shade less frustrating when we realized that we could supply JavaScript via AppleScript (!). The aftereffects text transform was born:

Yields

Presumably we could run a JavaScript XML-RPC client inside After Effects and do a similar trick as we did with Maya above...

So, Maya, MotionBuilder, After Effects, Photoshop (by extension) ...

"Raw" (C)Python

Finally, some people have asked for a more direct way of talking to "foreign" Python runtimes — specifically just running normal everyday /usr/bin/python python from inside Field. A quick first pass at this is easily achievable with a text transform:

from java.io import FileWriter
from java.io import BufferedWriter

def rawPython(a,b,c):
    """Execute the text as a new CPython process"""
    tmpFile = File.createTempFile("fieldRawPython", ".py", None)
    writer = BufferedWriter(FileWriter(tmpFile))
    writer.write(b, 0, len(b))
    writer.close()
    com = ExecuteCommand(".", ("/usr/bin/python", tmpFile.getAbsolutePath()),1)
    com.waitFor(1)
    print com.getOutput()

#add this to the right-click menu for text transforms.
TextTransforms.rawPython = rawPython

Now we can do things like this:

Obviously, once you've written a rawPython function that you are happy with once, there's no need to include it every time you use the text transform — just add it to your Field startup. This is a good example of how to create your own text transforms. So good, in fact, that we've included it in the standard library.

Also the above code is a little simple — it executes the Python synchronously (waiting for it to finish) and its error handling is non-existent. We're thinking about better and more general ways of connecting the Field runtime to a (perhaps long-running) Python environment.

The WrapInTransform Plugin

But this has also got us thinking about less visually intrusive ways of specifying Text Transforms. Right now you have to explicitly wrap some part of your text editor in a text transform. This can be confusing for new users — embedding strange non-text elements inside a text editor is, well, a little strange. The WrapInTransformPlugin adds a button to the toolbar of the text editor that pops up the Text Transform menu.

The transform selected here becomes the default wrapper for that element. Should you want this functionality, turn it on using the plugin manager. Unlike normal transforms here the transform wraps all executions — even things like command-enter and other properties associated with this element. This is excellent for turning Field, a python environment at heart, towards another languages.