We've Moved


The blog has been retired - it's up for legacy reasons, but these days I'm blogging at blog.theodox.com. All of the content from this site has been replicated there, and that's where all of the new content will be posted. The new feed is here . I'm experimenting with crossposting from the live site, but if you want to keep up to date use blog.theodox.com or just theodox.com

Friday, March 28, 2014

New(-ish) Tools Watch: 3D Coat 4.1



Version 4.1 of 3DCoat just came out - details in the video above or at 3D-Coat.com.

3DCoat is an interesting voxel-based alternative to Zbrush. It deserves a lot of credit for really pushing the retopology business forward, and also for early PTex support. The interface is less idiosyncratic than Zbrush's but less by-the-numbers than Mudbox.

Plus, it's made in Kiev - buy a copy and stand up for Ukrainian independence!

Some cool feature videos if you're curious:

UV tools
Curve-constrained sculpts
Ptex texturing


Maya GUI II: All Your Base Classes Are Belong To Us

Updated 4/11/2015: fixing code samples that had gone missing thanks to Blogger templates....
In Rescuing Maya GUI From Itself I talked in some detail about how to use descriptors and metaclasses to create a wrapper for the Maya GUI toolkit that, er, sucks less than the default implementation. I also strove mightily to include a lot of more or less irrelevant references to Thunderbirds. This time out I want to detail what a working implementation of the ideas I sketched out there looks like.

I think this time the irrelevant thematic gloss will come from All Your Base Are Belong To Us jokes. Because (a), we’re talking about base classes, (b) what could be more retro and 90’s than Maya’s GUI system, and (c) For Great Justice, Make All Zig!
I’ve put my current stab at a comprehensive implementation up on Github, in the form of the mGui project , where you can poke at it to your heart’s content. The whole project is there, and it’s all free to use under the MIT, ‘do-what-thou-wilt-but-keep-the-copyright-notice’ license. Enjoy! I should warn you, though, that this is still W.I.P code, and is evolving all the time! Use it at your own risk – things may change a lot before its really ‘ready’.

All Your Properties Are Belong To Our Base Class


What we’re shooting for is a library that provides all of Maya;’s GUI widgets in a clean, pythonic way without making anybody learn too much new stuff. If all goes well, the result is a cleaned up and more efficient version of things most of us already know. You can also treat this an template for how you might want to to wrap other aspects of Maya – say, rendering or rigging – in cleaner code.
From last time, we know we can wrap a Maya GUI component in a class which uses descriptors to make conventional property access work. The main thing we’re going to be delivering in this installment is a slew of classes that have the right property descriptors to replicate the Maya GUI toolkit. We’ll be using the metaclass system we showed earlier to populate the classes (if none of this makes sense, you probably want to hop back to the previous blog entry before following along).
To keep things simple and minimize the boilerplate, we’ll want to derive all of our concrete classes – the widgets and layouts and so on – from a single base. This helps the code simple and ensure that the features we add work the same way for the whole library. We’ll add a second class later to handle some details specific to layouts, but that will derive from the base class.
Before we look at the details of the base class, we should think a little more about the properties. In the last installment, we treated all properties the same way - as generic wrappers around maya.cmds. In a production setting, though, we want to distinguish between 3 types of properties:
Regular properties
These are just wrapped accesses to commands, like we demonstrated last week. They use the same ControlProperty class as we used last time to call commands on our GUI widgets.
Read-only properties
A small number of Maya GUI commands are read-only. It would be nice and more pythonic to make sure that these behave appropriately. So, ControlProperty has been tweaked with a flag that allows it to operate as a read-only property; otherwise it’s just the same descriptor we talked about last time out. ]
Callbacks
This one is a bit more involved. I’ve already complained about the weaknesses of the event model in Maya GUI. Cleaning it up starts with knowing which properties are callback properties and treating them accordingly.
To differentiate between these three types of properties, we need to tweak our old metaclass so that it can distinguish between regular properties, read-only properties, and event properties. Luckily the necessary changes are super simple - basically, we’ll take out the hard-coded list of properties we used before and allow every incoming class to declare a list of properties, a list of read-onlies, and a list of callbacks. (if you want to compare, the version from last time is here):

Somebody Set Us Up The Bomb!

Before getting into the nitty-gritty of our overall widget class, I want to make a side noted about the special properties used for the callbacks. These CallbackProperty descriptors are slightly different from the familiar ControlProperty. Their job is to de-couple the maya GUI widget from the commands it fires. They create special delegate objects which will intercept callbacks fired by our GUI objects.
If you have experimented a little with last time’s code, you may already have seen that it works just as well for callbacks and commands as for other properties. So you may wonder why we should bother to treat callbacks differently. What’s the point?
There are two main reasons this is a useful complication.
First, and most usefully, event delegates make it easier to add your callbacks after you lay out your GUI, rather than forcing you to interleave your code logic with the process of building forms and layouts. De-coupling the functional code form the graphic display makes for more readable and more maintainable code. It also makes it possible for you to reuse fairly generic layouts with different back ends. In pseodo-code:

Layout
   Layout
     ButtonA
     ButtonB
   Layout
     ButtonC
     ListA

ButtonA deletes selected item from ListA
ButtonB renames selected item from ListA
ButtonC adds new item to ListA

as opposed to
Layout
   Layout 
     ButtonA. 
        I'm going to delete something from the list when it gets made
     ButtonB
        I'm going to rename something in the list when it gets made
   Layout
     ButtonC
        I'm going to add something to the list when it gets made
     ListA
Keeping the functional bits separate makes it easy to, say, split the purely visual layout details into a separate file, but more importantly makes it clear whats an administrative detail and what’s actual functionality.
On a second, more tactical level the proxies also allow you to attach more than one function to a callback. It’s pretty common, for example, that you the act of want selecting an item in a list to select object in the Maya scene, but also to enable some relevant controls and maybe check with a database or talk to source control. Using an event proxy lets you handle those different tasks in three separate functions instead of one big monster that mixes up lots of UI feedback and other concerns.

If you’re familiar with QT you’ll rexognize that event delegates are basically QT “Signals”
So that’s why the extra complexity is worth it.
The actual workings of the proxy class are documented in the events.py file in the Github project; I’ll get back to how those work in a future post. Details aside, they key takeaway for right now is that this setup helps us move towards GUI code that’s more declarative. That’s the other reason why ‘button.label = “Reset”’ is better than cmds.Button(self.activeButton, e=True, l=’Reset’ – it’s not just less typing, it’s real value comes from treating the GUI layout as data rather than code,. That means you can concentrate on the actual work of your tools rather than the fiddly details of highlighting buttons or whatever.
Last but not least - by standardizing on the event mechanism we have an easy way to standardize the information that comes with the callback events for very little extra works. So, for example, all of the callbacks include a dictionary of keyword arguments when they fire - and the dictionary includes a reference to the widget that fired the event. That way it’s easy to write a generic event handler and not have to manually bind the firing control to a callback function.
While we’re on the topic of de-coupling: Wouldn’t it be nice to separate out the details of the visuals (“what color is that button?”) from the structure of the forms and layouts?. Spoiler alert! This is a topic for a future post – but the curious might want to check out styles.py in the GitHub

Think ahead

How the hell are you going to explain THAT to your grandchildren?
The obvious lession is THINK AHEAD

.
So, we’ve covered our improved property descriptors, and now it’s time to set up our base class.
This is a great opportunity to do some plumbing for more efficient coding. However it’s also a temptation – when the desire to sneak everything under the sun into your base classes is a recipe for monster code and untraceable bugs. This design should be as simple as we can make it.
Still, there are a couple of things that it would be nice to put into the base class - they are all very general (as befits base-class functions) and they are all common to any GUI tasks.

Tags

In most GUI systems, you can attach any arbitrary data you want to a widget. For example, you might want to have an array of buttons that all did the same thing with slightly different values, say moving an object by different amounts. In Maya you have to encapsulate the data into your command call:. With a tag attached to the buttons, on the other hand, you can write a script that just says ‘move the target by the amount in this button’s tag’, which is much easier to maintain and more flexible. And as we just pointed out, the event mechanism always sends a reference to the control which owns an event when it fires, so it’s easy to get to the right Tag when you want it.

A real name

Having explicit names for your pieces is very handy, particularly in large, deeply nested systems like a GUI..
In conventional maya coding the names are critical, since they are your only way of contacting the GUI after it’s built. They are also unpredictable, because of Maya’s habit of renaming items to give them unique path names. Luckily for us we don’t need to rely on the widget names from Maya, since we’re managing the GUI items under the hood inside our wrappers. This gets us off the hook for creating and managing variables to capture the results of every GUI command under the sun.
That said, names are still useful in a big complex system. So, to make it really clear how to find one of our wrappers inside a GUI layout it makes sense to ask for an explicit name passed in as the first argument - that way it’s totally clear what the control is intended to be. There are, of course, plenty of control you don’t really care about once they’re made: help text, spaces, separators and so on. To avoid making users have to invent names for those guys, we should let users pass in 0 or False or None as a succinct way of saying “I don’t care about the name of this thing”.
One minor note: I used Key as the name of the property so my IDE did not bug me for using in the Python reserved word ‘id’. Little things matter :)
Speaking of little things: there are some great tools in the Python language to make classes more efficient to work with. The so called ‘magic methods’ allow you to customize the behavior of your classes, both to make them feel more Pythonic and to express your intentions more clearly. Here are a couple of the things we can do with the magic methods in our base class:

__nonzero__

Speaking of that pass-in-zero-to-skip-names gimmick, one simple but super-useful thing we can do is to implement the __nonzero__ method. That’s what Python calls when you try the familiar

if something:
    doSomething()

test. In our case, we know that all Maya GUI controls have the exist flag, and therefore all of our GUI classes will too. So, if our __nonzero__ just returns the exist property of our class instances, we can elegantly check for things like dead controls with a simple, pythonic if test.

__repr__

__repr__ is what Python calls when you need a printable representation of an object. In our case, we can pass back our underlying Maya GUI object, which is just a GUI path string. This way, you can pass one of our wrapper classes to some other python code that works on GUI objects and it will ‘just work’ – This is more or less what PyMel does for nodes, and it’s a great help when integrating a new module into an existing codebase. Like PyMel’s version there will be some odd corner cases that don’t work but it’s a handy convenience most of the time.
As a minor tweak, the __repr__ is also tweaked to display differently when the GUI widget inside a wrapper class has been deleted. This won’t prevent errors if you try to use the widget, but it is a big help in parsing error messages or stack traces.

__iter__

The next magic method we want to add is __iter__. It is the what python calls when you try to loop over a list or a tuple.
Now, a single GUI object obviously is not iterable. A layout like columnLayout, on the other hand, can be iterated since it has child controls. By implementing __iter__ here and then over-riding it when we tackle layouts, we can iterate over both layouts and their children in a single call. This makes it easy to look for layout children :
for child in mainlayout:
    if child.Key == 'cancel': #.... etc
So with all those methods added the base Control class looks like this:
class Control(Styled, BindableObject):
    '''
    Base class for all mGui controls.  Provides the necessary frameworks for
    CtlProperty and CallbackProperty access to the underlying widget.

    NOTE this is not exactly identical to the code on github - more advanced stuff is removed to make the progression clearer
    '''
    CMD = cmds.control
    _ATTRIBS = ['annotation', 'backgroundColor', 'defineTemplate', 'docTag',  'enable', 'enableBackground', 'exists', 'fullPathName', 'height',  'manage', 'noBackground', 'numberOfPopupMenus', 'parent', 'popupMenuArray', 'preventOverride', 'useTemplate', 'visible', 'visibleChangeCommand', 'width']
    _CALLBACKS = ['dragCallback', 'dropCallback', 'visibleChangeCommand']
    _READ_ONLY = ['isObscured', 'popupMenuArray', 'numberOfPopupMenus']
    __metaclass__ = ControlMeta


    def __init__(self, key, *args, **kwargs):


        self.Key = key
        self.Widget = self.CMD(*args, **_style)
        '''
        Widget is the gui element in the scene
        '''
        self.Callbacks = {}
        '''
        A dictionary of Event objects
        '''
        Layout.add_current(self)


    def register_callback(self, callbackName, event):
        '''
        when a callback property is first accessed this creates an Event for the specified callback and hooks it to the gui widget's callback function
        '''
        kwargs = {'e':True, callbackName:event}
        self.CMD(self.Widget, **kwargs)



    def __nonzero__(self):
        return self.exists

    def __repr__(self):
        if self:
            return self.Widget
        else:
            return "<deleted UI element %s>" % self.__class__

    def __str__(self):
        return self.Widget

    def __iter__(self):
        yield self
You’ll notice that it is inheriting from two classes we have not touched on, Styled and BindableObject. Those don’t interact with what we’re doing here - they’ll come up in a later post. You can pretend it just says ‘object’. If you’re reading the code carefully you’ll probably spot a little bit of code I haven’t described. register_callback is there to support event proxies – we’ll talk about the details when we get to event proxies in the future.
Despite my rather verbose way of describing it all, this is not a lot of code. Which is what exactly you want in a base class: simple, common functionality, not rocket science. Hopefully, though, adding those pythonic behaviors will save a lot of waste verbiage in production work.
Damn, the internet has a lot of time on its hands

All Your Children Are Belong To Parent Layout

There’s one little bit of plumbing in Control that is worth calling out:
Layout.add_current(self)
That’s way of making sure that we can store references to our control wrappers in our layout wrappers - that is, when you create a wrapped button inside a wrapped columnLayout, the columnLayout has a handle to the wrapper class for the button. Which brings us around neatly to the wrapper class for layouts - called… wait for it… Layout.
To support nesting, we want our Layout wrapper class to be a context manager. The idea is that you when you start a Layout, it declares itself the active layer and all GUI controls that get created add themselves to it; when you’re done with it control is return to whatever Layout was active before. As Doctor Who says of bow ties, “Context Managers are cool.”
If you’ve done a lot of Maya GUI you know it’s also nice to have the same functionality for menus as well. So, to avoid repeating ourselves let’s start by creating a generic version of Control that works as a context manager so we can get identical functionality in windows, layouts and menus. Then we can inherit it into a wrapper class for layouts and another for windows and voila, they are all context managers without cutting and pasting. Here’s the abstract base class for all ‘nested’ classes: menus, windows, layouts etc:
class Nested(Control):
    '''
    Base class for all the nested context-manager classes which automatically parent themselves
    '''
    ACTIVE_LAYOUT = None

    def __init__(self, key, *args, **kwargs):
        self.Controls = []
        super(Nested, self).__init__(key, *args, **kwargs)

    def __enter__(self):
        self.__cache_layout = Nested.ACTIVE_LAYOUT
        Nested.ACTIVE_LAYOUT = self
        return self

    def __exit__(self, typ, value, traceback):
        self.layout()
        Nested.ACTIVE_LAYOUT = self.__cache_layout
        self.__cache_layout = None
        cmds.setParent("..")

    def layout(self):
        '''
        this is called at the end of a context, it can be used to (for example) perform attachments
        in a formLayout.  Override in derived classes for different behaviors.
        '''
        return len(self.Controls)

    def add(self, control):
        path_difference = control.Widget[len(self.Widget):].count('|') - 1
        if not path_difference:
            self.Controls.append(control)

        if control.Key and not control.Key[0] == "_":
            if control.Key in self.__dict__:
                raise RuntimeError('Children of a layout must have unique IDs')
            self.__dict__[control.Key] = control

    def remove(self, control):
        self.Controls.remove(control)
        k = [k for k, v in self.__dict__.items() if v == control]
        if k:
            del self.__dict__[k[0]]

    def __iter__(self):
        for item in self.Controls:
            for sub in item:
                yield sub
        yield self

    @classmethod
    def add_current(cls, control):
        if cls.ACTIVE_LAYOUT:
            Nested.ACTIVE_LAYOUT.add(control)
All that really does is pop the current Nested onto a stack and make it possible for other controls to add themselves to the instance on top of the stack.
Here’s the concrete implementation for actual Layout classes:
class Layout(Nested):

    CMD = cmds.layout
    _ATTRIBS = ['annotation', 'backgroundColor', 'defineTemplate', 'docTag', 'dragCallback', 'dropCallback', 'enable', 'enableBackground', 'exists', 'fullPathName', 'height', 'manage', 'noBackground', 'numberOfPopupMenus', 'parent', 'popupMenuArray', 'preventOverride', 'useTemplate', 'visible', 'visibleChangeCommand', 'width']
    _CALLBACKS = ['dragCallback', 'dropCallback', 'visibleChangeCommand']
    _READ_ONLY = ['isObscured', 'popupMenuArray', 'numberOfPopupMenus', 'childArray', 'numberOfChildren']
This is just a regular mGui class (it gets all of the metaclass behavior from Control, via Nested) with added properties for common layout properties like numberOfChildren.
While we’re messing with contexts, this is also a great opportunity to do what PyMel already does and make all layouts automatically manage UI parenting. This gets rid of all those irritating calls to setParent(“..”), and lets us write GUI code that looks like real Python and not a plate of spaghetti. Compare this wordy cmds example:
win = window('main window', title="Ugly version")
columnLayout('gui', width = 256)
frameLayout("t_buttons", label = "buttons column")
columnLayout("col")
sphere_1 = button('mkSphere', label = "Make Sphere", c = make_sphere)
cone_1 = button('mkCone', label ="Make Cone", c = make_cone)
cube_1 = button('mkCube', label ="Make Cube", c = make_cube)
setParen("..")
setParent("..")
frameLayout("r_buttons", label = "buttons row")
rowLayout ("row", numberOfColumns=3)
sphere_2 = button('mkSphere', label = "Make Sphere", c = make_sphere)
cone_2 = button('mkCone', label ="Make Cone", c = make_cone)
cube_2 = utton('mkCube', label ="Make Cube", c = make_cube)
setParen("..")
setParent("..")
frameLayout("g_buttons", label = "buttons grid")
gridLayout("grid", numberOfColumns = 2):
sphere_3 = button('mkSphere', label = "Make Sphere", c = make_sphere )
cone_3 = button('mkCone', label ="Make Cone", c = make_cone)
cube_3 = button('mkCube', label ="Make Cube", c = make_cube)
circle_btn = button('mkCircle', label = "Make Circle", c = make_circle)
setParen("..")
setParent("..")
setParent("..")
showWindow(win)
To this version using context manager layouts:
from mGui.gui import *
# note the caps: all of these are wrapper objects, not maya.cmds!

window = Window('main window', title="How's this")  
with ColumnLayout('gui', width = 256) as gui:
    with FrameLayout("t_buttons", label = "buttons column"):
        with ColumnLayout("col"):
            Button('mkSphere', label = "Make Sphere")
            Button('mkCone', label ="Make Cone")
            Button('mkCube', label ="Make Cube")

    with FrameLayout("r_buttons", label = "buttons row"):
        with RowLayout ("row", numberOfColumns=3):
            Button('mkSphere', label = "Make Sphere")
            Button('mkCone', label ="Make Cone")
            Button('mkCube', label ="Make Cube")

    with FrameLayout("g_buttons", label = "buttons grid"):
        with GridLayout("grid", numberOfColumns = 2):
            Button('mkSphere', label = "Make Sphere")
            Button('mkCone', label ="Make Cone")
            Button('mkCube', label ="Make Cube")
            Button('mkCircle', label = "Make Circle")
That example also includes one other neat way to leverage contexts too. If you double check the add method in Nested you’ll see that it adds child wrapper objects to it’s own __dict__. That makes them accessible without having to explicitly store them. In this example, you could get to the last sphere-making button in this example as gui.g_buttons.grid.mk_sphere without having to manually capture the name of the underlying widgets they way the first example must. Since Maya GUI is always a single-rooted hierarchy, as long as you know the first parent of a window or panel you can always get to any of its the child layouts or controls. This saves a lot of the boring boilerplate you would otherwise need to do just keeping track of bits and pieces.
There’s one little extra bit of magic in there to let the add method discriminate between children you care about and those you don’t. If your child controls have no key set, they won’t be added to the __dict__. On a related note, you can also be tricksy and add a control which is not a direct child of the layout - for example, if you had a layout with a list of widgets in a scrollLayout, you don’t usually don’t care about the scrollbar - it’s just along for the ride. So you can add the widgets directly to the ‘real’ parent layout and keep the paths nice and trim. The goal, after all, is to make the gui layout a logical tree you can work with efficiently. There’s a practical example of this trick in the lists.py file on Github
Here’s a snippet tacked on to the end of that last sample showing how you can use the iterability of the layouts to set properties in bulk. You can see how the work of turning command-style access into property style access, combined with the extra clarity we get from context managers, really pays off:

# using the iterability of the layout to set widths 

for item in gui.t_buttons:
    item.width = 256

for item in gui.r_buttons.row:
    item.width = 85
item.width = 256  # the last item is gui.r_buttons.row itself
item.columnWidth3 = (85,85,85)  # ditto

for item in gui.g_buttons.grid:
    item.width = 128
item.width = 256  # now the last item is the grid
item.cellWidth = 128   

cmds.showWindow(window)


I don’t even want to think about the equivalent code in cmds!
One parting note about the naming scheme, It does have one, inevitable drawback: it means that the child wrappers have unique names inside a given context. Not much we can do about that. They can, however, have the same name under different parents - the example above has , gui.t_buttons.grid.mk_sphere, and gui.g_buttons.grid.mk_sphere Thats a useful thing to exploit if you want to, say, find all of the ‘Select’ buttons on a form and disable them or something off that sort.

Make All Zig!

Hopefully, the combination of some syntax sugar in our wrappers and turning layouts into context managers will make Maya GUI layout less of a pain in the butt. However, we still need to actually crank out all the wrappers for all those scores of classes in the maya GUI library. Descriptors and metaclasses are powerful tools, but few of us have the intestinal fortitude to plow through the dozens of classes in the Maya GUI library getting every flag and command correct.
In an ideal world we’d have a way of reflecting over some kind of assembly information and extracting all of the maya GUI commands with their flags and options. Of course, in an ideal world we would not have to do this in the first place, since the native GUI system would not be the unfortunate SNES-era mishmash that it is.

Mass production is a pain in the ass.
Luckily, the TA spirit cannot be kept down by adversity. In this case we don’t have a nice clean api but we do have MEL.... poor, neglected, wallflower MEL. Well, here’s a chance for the wallflower to save the party: MEL’s help command can list all of the commands and all of the flags in Maya. So, what we need to do is to run through all of the Mel commands in help, find the ones that look like GUI commands, and capture their command - flag combinations as raw material for our metaclass control factory.
See? This was getting all programmery, but now we’re back in familiar TA spit-and-bailing-wire territory. Comfier?
The actual code to build the wrappers isn’t particularly interesting (its here if you want to see it). In two sentences: Use the mel help * command to find all of the commands in Maya which share flags with cmds.control or cmds.layout. Then collect their flags to make the list of class attributes that the metaclass uses to create property descriptors. The final output will be a big ol’ string of class definitions like this:
class FloatSlider(Control):
    '''sample output from mGui.helpers.tools.generate_commands()'''
    CMD = cmds.floatSlider
    _ATTRIBS = ['horizontal','step','maxValue','value','minValue']
    _CALLBACKS = ['changeCommand','dragCommand']
We generate two files, one for controls and one for layouts (that’s an arbitrary design call on my part, you could of course have one file). Now they’re just sitting on disk as if we’d written them by hand. We can import our newly generated modules and away we go, with nice pythonic properties and our new functions.
There is one judgement call here that is worth mentioning in passing.
The logic in the helper modules which generate this is all deterministic, it doesn’t need human intervention so it could actually be run at module load time rather than being run and dumped out to a file. For what I want to do, I felt that physical files were a better choice, because they allow the option of hand tailoring the wrapper classes as the project evolves. Plus, the startup cost of trolling through every MEL command, while it’s not very big, is real and it seems good to avoid it. I’ve have heard enough grumbling over the years about PyMel’s startup speed that I thought it wisest to opt for speed and clarity over fewer files on disk.
One nice side effect of generating our wrappers this way: we’ve added some functionality through our base classes but fundamentally we’ve kept the same names and options we already know from plain old maya.cmds. The only changes are the mandatory names and the fact that I’ve capitalized the class names to make them more pep-8-friendly.
Hopefully, keeps the learning curve short for new user. Its hard enough to pick up a new style, making you memorize hundreds of new property names seem like a big tax on users.
In the version up on Github (and in this example) I opted to use only the long name for the properties. This is definitely a matter of taste; I’m sure that many TAs out there are sufficiently familiar with the old maya command flags that a howler like cmds.rowLayout(nc=2, cw2=(50,100), ct2=('both', 5), bgc = (.8,.6,.6), cl2=("left", "right") makes its meaning clear. for my part, though, the long names clarify the intent of the code enormously if you make a fairly small upfront investment in typing.
If you are of the opposite opinion, though, you can call the generate_helpers and generate_controls functions in mGui.helpers.tools with includeShortNames set to true make your own wrappers with the short flags too.

What You Say!!!

Now we’ve got a complete library of all the widgets. You can see the results in controls.py and layouts.py on GitHub. (The base classes are also up there for your perusal in the root of the core module). If all you want is to stop writing long commands every time you touch a GUI item, you’re done. You can write crisper layout code, tweak your properties, and so on with what we’ve covered so far. If you’re interested in making practical use of this setup – remember that WIP warning! – you should read the docs in the events.py module to make sure you know how to hook up callback events. I’ll cover that in more detail in the future.
However… Simpler syntax is just scratching the surface of what we can get up to now that we have a proper class library for our widgets. Next time out we’ll look at the event mechanism in more detail and talk about how to cleanly separate your functional code, GUI layouts, and the display styles of your widgets.
Until next time....

Wednesday, March 26, 2014

(?:regex){1} (haterz)+

If you hate regexes as much as I do - which, honestly, seems scarcely possible - you should check out these regex preview sites which make like a little easier when you're trying to work out a particularly nasty one:

Python: Pythex 

C# / dotnet : Regex Storm 

Both of these offer big productivity boost over the usual tweak-run-check cycle

Tuesday, March 25, 2014

New tools watch: MARI

Lately - particularly since the demise of the late, lamented XSI, I've been increasingly worried about getting too locked in to any one vendor for my tools.  I go way back with Photoshop - I once emailed a bug report directly to John Knoll on my Compuserve (!!) account.  I was in the audience for the Maya launch at SIGGRAPH in, I think it was 1997.

Jeez, I'm really fricking old. But I digress.

The point is, I love those tools. They've been part of my life for a long time. But I don't like being too beholden to anybody, especially not an anybody who's a big public company that has to answer to shareholders and analysts and is not particularly worried about competition.

For that reason I'm actively looking for alternatives to supplement or even supplant the old standbys.  And this GDC gave me some up-close and personal time with a very promising one: Mari, the 3-d painting app from the Foundry.

Lots of Mari work on Avatar

More after the jump.

Saturday, March 22, 2014

Back from GDC

Well, another year, another GDC.

An upbeat show - the combination of the indie/mobile tidal wave and a new console generation has breathed some much needed life into the old beast - the business center was bigger than I've seen it in years, and full of people hustling.

The floor was buzzing!

The absence of many familiar AAA names was still pretty striking - no lines around the block for the Activision/EA meat markets, no scores of kids waiting to show their portfolios to someone from Blizzard or whatnot.

There was, however, a booth from SpaceX. Someboedy told me they are hiring tech artists.  See, we're conquering the world... from above!

And, there was an amazing bustle of activity from the indie-casual-mobile side. Tons and tons of booths, more than I remember ever seeing before. Most of them will probably be gone in two years, but it's still great to see people hustling around, coming up with plans, and trying to make something big after all the gloom of the last few years.  Here's hoping the many friends and former colleagues who've been battered by business can make up some lost ground in this new gold rush.  It's probably another bubble - remember when everybody wanted to hire a teal of 800 to talk on WOW? - but it's still better than sitting around moping.

All the movement has a lot to do with all the changes in the business that have broken the stifling rut of AAA juggernauts, sequel-itis, and me-too design that was hanging over us like a fog a few years back. All sorts of fun and interesting stuff happening.

Saw some cool tech too, which I'll talk about later in the week. In the meantime: so long SF!

Update: I did not see this talk, but this rant from Greg Costikyan does summarize neatly some things I am worrying about over the long haul...


Wednesday, March 19, 2014

Worst. CG. EVAR.

Anybody who has ever had to suffer through demo reel reviews will appreciate this: This guy's Youtube channel is the Louvre of bad CG. And yes, he is in on the joke. But he is looking for donations so he can raise enough money for a PC which can render shadows.

Tuesday, March 18, 2014

Unity 5 announced

So it looks like Unity is announcing Unity 5 a bit early.   It's a bit premature, since they are not done with the much anticipated 4.6 release (I've been wrestling with the existing, awful Unitry GUI system for months and I can't freaking wait to push it over the gunwale!)


Realtime GI... On my phone?!?!?
Tech notes after the jump

If your Maya Python API is crashing

Check out this useful post from Cyril Fauvelle on the ins and outs of the dreaded MScriptUtil.

This kind of stuff is why I reserve API programming for only the knottiest of tasks.  maya.cmds won't clean-exit your Maya session if you reverse two lines by accident.  However, sometimes there's no alternative...  Here's hoping API 2.0 matures quickly and we can all forget all of this pointless distraction.

Sunday, March 16, 2014

Sliders!

Although GDMag is no more, I still occasionally get interesting press releases from PR flacks who haven't gotten the bad news. This morning,  one from MakeHuman caught my eye.

Now with realistic back flab!

MakeHuman is a new open sourced parametric modeling / body morphing program written in Python.  The overall use case is similar to Poser or Daz3d.  It seems like it also incorporates some of the underlying ideas from the venerable FaceGen - particularly having a few high level sliders that correlate changes across many different aspects of a model.


Friday, March 14, 2014

Morph targets revisited

Here's an interesting demo vid from NewTek, spotlighting a new time-based sculpting workflow: in essence, an animatable Zbrush that let you retouch big simulation cache files.  It's easy to see how this could spark a renaissance in vertex animation.....


More details on the NewTek website.

Tuesday, March 11, 2014

Descriptors and pythonic Maya properties

Updated 4/11/2015: fixing the embedded code that was busted by Blogger.
I’m still working on the followup to Rescuing Maya GUI From Itself, but while I was at it this StackOverflow question made me realize that the same trick works for pyMel-style property access to things like position or rotation. If you’re a member of the anti-pyMel brigade you might find this a useful trick for things like pCube1.translation = (0,10,0). Personally I use pyMel most of the time, but this is a good supplement or alternative for haterz or for special circumstance where pymel is too heavy.

The goal is to be able to write something like

from xform import Xform
example = Xform('pCube1')
print example.translation
# [0,0,0]
example.rotation = (0,40, 0)

The process s about as simple as it can get thanks to the magic of descriptors. This example spotlights one advantage of descriptors over getter/setter property functions: by inheriting the two classes (BBoxProperty and WorldXformProperty) I can get 4 distinct behaviors (world and local, read-write and read-only) with very little code and no if-checks.

'''
xform.py

Exposes the xform class: a simple way to set maya position, rotation and similar properties with point notation.

(c) 2014 Steve Theodore.  Distributed under the MIT License (http://opensource.org/licenses/MIT)
TLDR: Use, change and share, please retain this copyright notice.
'''

import maya.cmds as cmds

class XformProperty( object ):
    CMD = cmds.xform
    '''
    Descriptor that allows for get-set access of transform properties
    '''
    def __init__( self, flag ):
        self.Flag = flag
        self._q_args = {'q':True, flag:True}
        self._e_args = {flag: 0}


    def __get__( self, obj, objtype ):
        return self.CMD( obj, **self._q_args )

    def __set__( self, obj, value ):
        self._e_args[self.Flag] = value
        self.CMD( obj, **self._e_args )


class WorldXformProperty( XformProperty ):
    '''
    Get-set property in world space
    '''
    def __init__( self, flag ):
        self.Flag = flag
        self._q_args = {'q':True, flag:True, 'ws':True}
        self._e_args = {flag: 0, 'ws':True}

class BBoxProperty ( XformProperty ):
    '''
    Read only property for bounding boxes
    '''
    def __set__( self, obj, value ):
        raise RuntimeError ( "bounding box is a read-only property!" )

class WorldBBoxProperty ( WorldXformProperty, BBoxProperty ):
    '''
    Read only property for bounding boxes
    '''
    pass


class Xform( object ):
    '''
    Thin wrapper providing point-notation access to transform attributes

       example = Xform('pCube1')
       # |pCube1
       example.translation 
       # [0,0,0]
       example.translation = [0,10,0]

    For most purposes Xforms are just Maya unicode object names.  Note this does
    NOT track name changes automatically. You can, however, use 'rename':
       example = Xform('pCube1')
       example.rename('fred')
       print example.Object
       # |fred

    '''

    def __init__( self, obj ):
        self.Object = cmds.ls( obj, l=True )[0]

    def __repr__( self ):
        return unicode( self.Object )  # so that the command will work on the string name of the object

    # property descriptors  These are descriptors so they live at the class level,
    # not inside __init__!

    translation = XformProperty( 'translation' )
    rotation = XformProperty( 'rotation' )
    scale = XformProperty( 'scale' )
    pivots = XformProperty( 'pivots' )

    world_translation = WorldXformProperty( 'translation' )
    world_rotation = WorldXformProperty( 'rotation' )
    world_pivots = WorldXformProperty( 'pivots' )
    # maya does not allow 'world scale' - it's dependent on the parent scale

    # always local
    scaleTranslation = XformProperty( 'scaleTranslation' )
    rotateTranslation = XformProperty( 'rotateTranslation' )

    boundingBox = BBoxProperty( 'boundingBox' )
    world_boundingBox = WorldBBoxProperty( 'boundingBox' )


    def rename( self, new_name ):
        self.Object = cmds.ls( cmds.rename( self.Object, new_name ), l=True )[0]

    @classmethod
    def ls( cls, *args, **kwargs ):
        '''
        Returns a list of Xforms, using the same arguments and flags as the default ls command
        '''
        try:
            nodes = cmds.ls( *cmds.ls( *args, **kwargs ), type='transform' )
            return map ( Xform, nodes )
        except:
            return []

You may note the absence of a __metaclass__. In this case, with only a single class, a meta would be an unnecessary complication. Meanwhile the code for MayaGUI itself is up on GitHub. Comments and/or contributions welcome!

A little bit of innovation

If recent demise of XSI has you all depressed about the state of 3d software, here's a little preview of a new Modo feature that will make some modelers very happy



http://community.thefoundry.co.uk/store/plugins/meshfusion/

Tuesday, March 4, 2014

*Sigh*

It's bad enough we just lost Harold Ramis.

Now we've also lost SoftImage.

Although I've never used XSI professionally, I was incredibly impressed by the people who made it, and I've always felt that XSI was a superior product to Max or Maya: making better use of modern hardware and showing off really innovative concepts in an industry that's gotten pretty damn stale for something that's sounds so high tech and is occasionally so magical.

In honor of the passing of this great piece of software, I'm going to reprint an article I wrote for Game Developer back in 2008 when the sale of XSI to Autodesk was first announced.  I'm afraid I may have been a little too optimistic. However I do think that the basic idea of the piece - that we let ourselves in for this kind of treatment by not being more informed and flexible consumers - is still true.

In the mean time, I'm going to go have an Irish wake for a poor old XSI.  (BTW, if you're waiting on the follow-up to Rescuing Maya GUI From Itself, I'm cleaning up the code and writing tests before I go blabbing...)

Update: Came upon this interesting set of charts (espceially the next to the last one at the bottom) which explains a lot of what's going on on here. Doesn't make it hurt less, though.

Equally relevant: Autodesk's upgrade lockdown


The M-Word


If you’ve been in a crunch-time media blackout for the past month, or shut down your internet connection to avoid election news,  or are the only games artist on the planet who’s never received a youtube link via email you may have missed an interesting little tidbit of news. On October 23 we learned that Avid is going to sell SoftImage, the Montreal-based developer or SoftImage|XSI to Autodesk.  If and when the deal goes through, all three of the biggest 3d modeling and animation packages will all belong to a single company.

Even if you managed to miss the announcement, you can probably predict the immediate reactions anyway.   In the XSI community, the dominant mode was shell-shock.  The “Resistance is Futile” jokes and Borg-themed Photoshop jobs could not disguise the level of emotion in the air -- the poster on the XSI forums who simply said “I think I’ll cry” wasn’t kidding.  There was a smattering of optimists suggesting the deal would give more people access to some of XSI’s best tech.  A few pragmatists found consolation in the idea that the conglomeration would give cross-package data transfer the attention it deserves.  But the most common reactions were shock and anxiety. 

It’s hardly surprising that the possibility of being forced to abandon the comfort and security of a familiar environment would give XSI users the heebie-jeebies.  The official Pixel Pusher line has always been that any professional game artist should be competent in at least two packages. But even traditional artists are famous for being emotionally attached to their tools (never, ever venture an opinion about Kolinsky sable brushes in mixed company!) For us, who spend so much of our lives poking at one particular set of dialogs or buttons, the thought of being forced to swap them for a different, unfamiliar set of dialogs and buttons is deeply disturbing.  The fact that some XSI fans were so distraught they’d consider switching to Blender out of pique is an index of how emotional this issue can be.

What’s surprising, though, is that a similar miasma could be seen in the Max and Maya forums after the buyout announcement. Emotions ran high even for those not affected directly. Hardcore Maya fans suffered flashbacks as they relived the 2006 buyout of Alias.  More commonly, though, users were grimly pondering the future of graphics software in general, rather than the fate of any particular package.  Some naysayers worried that technology would stagnate without the underdogs like XSI striving to gain an advantage through innovation. Others fretted that consolidation in the industry means the exciting, Wild-West days of graphics are really over.  And many users of all three packages speculated that the lack of competition will lead to price gouging.

You Are Elected Chairman of the Board


Before we pronounce the graphics software business dead, we ought to look at this deal in its historical context.  These kinds of corporate dramas are unsettling for artists because they are an unsubtle reminder that we creative types are dependent on huge, impersonal corporations to get anything done. Masters-of-the-Universe style MBA analysis isn’t part of our job descriptions, so it’s hard for use mere users to figure out how to respond. A little bit of history, however, is often a good way to get some perspective; so here’s a very abbreviated walk through the life and times of SoftImage to help you understand today’s news.
XSI may be the youngest of the big three graphics packages, but SoftImage the company is one of the oldest firms in 3d graphics software.  The original “SoftImage Creative Environment” debuted in 1988, but in an economic environment very different from todays.  3D graphics was very closely akin to rocket science – for one thing, it was mysterious new high-tech discipline and for another you needed an exotic workstation that cost upwards of $50,000[1] to do either one.  It was a very esoteric, very pricey business.

SoftImage|3D  was the first commercial application to offer artist-friendly IK (1991) and it quickly became the gold standard for computer animation.  Many seminal CGI films of the early ‘90’s were animated in SoftImage, most famously Jurassic Park.  Those early days of the CG revolution were heady times. Hollywood stood ready to firehose money onto anybody who could render a good looking triangle  -- SIGGRAPH veterans still murmur nostalgically about the heydays of studio parties – and the boom times were good for the company. In 1992, the Montreal firm went public to much acclaim.
Success also changed the way the industry worked.   By the mid ‘90s, the explosion of 3d game development shifted the industry dynamic: the ranks of 3d artists and animators expanded enormously, but few games companies could afford to put the equivalent of a luxury sports car under every animator’s desk.  Affordable PCs with primitive graphics cards started stealing business from workstations and PC based packages like Autodesk’s 3d Studio started making inroads against pricey workstation software.

From Sale Of Stock You Get $45 


Microsoft, naturally, wanted to see the PC forces prevail . In 1994 they bought SoftImage for $130 million – a pretty high price given that the whole 3D software market was only around $60 million a year back then. But box sales weren’t the real goal: Microsoft needed to port a top-end workstation graphics package to Windows and legitimize the market for high end graphics on Windows. 
For many SoftImage vets, the events of last month may have an eerily familiar ring, right down to the “you will be assimilated” jokes (although,  in 1994 the reference was forgivably fresh).

 The MS acquisition was not a very pleasant experience for SoftImage users. Not only were many Unix devotees forcibly converted to a new OS, but the demands of porting and cross-platform development shunted innovation to the sidelines.  It took almost 7 years for SoftImage|3D to get from version 3.0 to version 4.0 , and the package lost a lot of its technological edge to newer platforms like 3dStudio Max and Maya.  It’s not surprising that the survivors of that first buyout react suspiciously to the latest.
By 1998, Microsoft had achieved its strategic objective: Windows had triumphed and graphics workstations were headed for the history books.  SoftImage became superfluous.  Microsoft sought out a buyer and found Avid, a rising power in the digital video editing business which coveted the company’s VFX and compositing tech.  Even if the core 3d business was losing steam, the deal still ran a cool $285 million – a price that might have been inflated by internet bubble, but it’s still pretty impressive.

Avid’s stewardship was a lot healthier for SoftImage as a tech company. SoftImage XSI, the long overdue gut-rehab of the aging SoftImage|3D, was released in 2000.  The product started out a bit slow – version 1.0 had no poly modeling tools! – but gained steam with impressive tech and clean new architecture. Many artists have lusted after XSI’s GATOR system for mapping one mesh onto another (supporting everything from texture transfers to skin weight matching) , its non-linear animation mixer, Face Robot facial animation technology, and most recently the high-perfomance ICE system for node-based custom object creation. 

Second Prize in a Beauty Contest, Collect $11


Unfortunately, the eningeering success of the product did not translate into success for SoftImage’s owners.  Avid’s core business has been hit hard by the proliferation of lower-cost video editing software like Apple’s Final Cut Pro.  Even though the SoftImage was profitable, it wasn’t profitable enough: to get a sense of the scale, you might note that the $35 million sale price for the company won’t even cover Avid’s losses for the 3rd quarter of this year.  As times got leaner, Avid needed to focus on protecting its core video editing business, so it started hunting for a buyer early this year.  Autodesk, as home to both of XSI’s a main rivals, was not the first buyer who was approached… but it was the final one, which is the one that counts.

What lessons can you learn from this little history?

First, it doesn’t provide a lot of evidence for conspiracy theories about monopoly power.  The fact is, supplying 3d software is pretty small potatoes in the grand scheme of capitalism.  It’s been said that there are only about half a million seats of full 3d packages in the world – sounds like a lot, but that’s smaller than the number of people in the beta program alone for Photoshop.  It’s not a market where achieving dominance is a huge financial win.  All three turnovers at SofImage  have been driven by strategic concerns that didn’t have to do with monopoly power or market domination.  Microsoft bought SoftImage to catalyze the switch from workstations to PCs. Avid bought it to solidify its FX and compositing business and saw modeling and animation through that prism.  The most recent sale didn’t originate with a sinister plot from inside of Autodesk, it originated with Avid’s accountants. 

The more interesting – but also more depressing – aspect of this story, though, isn’t concerned with money. You could read the whole thing as a stirring tale of steadfast devotion. It was user loyalty that sustained SoftImage during the drift of the Microsoft years, when technical sluggishness might have let Max and Maya completely marginalize the original SoftImage.  The emotional reaction to the news is proof of how viscerally loyal users are to their favorite tools.

Unfortunately, that loyalty is a two edged sword.  The last few version of XSI were consistently excellent -- but no combination of cool features and good design managed to seduce away enough users from other packages to secure SoftImage’s future.  They competed on tech and features and did a great job – but it wasn’t enough to overcome the entrenched loyalties of Max and Maya fans. Individual artists might admire this feature or that bit of UI, but collectively we’re reactionaries: we stick with what we know. On top of that, most studios have tools and processes are designed around a particular package and aren’t eager to chuck those investments for the sake of sexy icons or a cleaner interface.

The fact is, we don’t really reward tools companies for pushing the envelope.  Even when something new does break into the scene, we try to shoehorn it into our existing workflows rather than embracing the new. We’re the last people to start denouncing monopolies and phoning up the Federal Trade Commission. Most of us have already folded our hands by letting ourselves become emotionally attached or technically beholden to particular bits of software.  If you’re in the same camp as the XSI user who posted “they’ll have to pry my license from my cold dead fingers,” you live in a virtual monopoly already.

Get Out Of Jail Free?


That’s not to say that things aren’t going to change. The absence of major-league alternatives will definitely give Autodesk a much freer hand in choosing both its price points and research directions. T fact that their track record to date is pretty benign is comforting, but the knowledge that we’re dependent on their altruism from here on out should give us pause.  Autodesk has put some genuine effort into trying to explain the deal to users (there’s an interesting interview featuring the GMs of Autodesk and SoftImage up on the Autodesk website, with more info promised as the deal solidifies) but apart from reassurances that XSI isn’t going to go away overnight, the magic 8-ball is pretty cloudy.

The uncertainty is tough, particularly for anxious XSI users but for all of us. We all know the mantra, “it’s the artist not the tools” – but in practice it’s sometimes hard to say where the artist leaves off and the tool begins. The feeling that such an important part of our lives is out of our control is unnerving. 
What can we do about it? As individuals, that means being open to new software and new ways of working, so that we make an environment where companies have a real incentive to give us new and better tools. As studios we should invest in in-house tools rather than relying too faithfully on any single vendor.  As an industry we should push harder for more consistent, open standards in data formats and for open source tools so we can make our pipelines less dependent on the ups and downs of individual companies.  None of these steps will magically unwind the clock but they will give us a little more input into this critical part of our lives.

Or, we could all switch to Blender… But man, I hate the way they do their menus. It’s not like Max. You couldn’t pay me enough to switch.







[1] That’s $65,000 in today’s dollars.  For a machine less powerful than an iPhone.