Namespace packages for shared DCC’s code & tools

We do quite a few shared Python DCC tools and lib’s. With 3dsMax in the mix, this has recently gotten way easier with official Python and PySide support (though MaxPlus is pretty immature, but that’s another story).

One thing we wave have been using is a “namespace package”. This allow’s several folders on disk to merge into one logical namespace, using package utils (pkgutil). So we have something like the following folder structure:


Here Maya’s Python Path will include both “shared_code/my_package” and “maya_code/my_package”.

This way, anything in “shared_code/my_package” and “maya_code/my_package” is available in the same namespace and can import freely from one another etc. Works for sub packages too and PyCharm totally get’s it, unlike with other methods I’ve tried, where introspection and code completion broke down.

If something is defined in both folder locations, it’s the order of these folders on the Python Path, that dictates which module takes precedence. You can use this to create “local overrides” and I’ve set set it up, so the application specific folder overrides the shared folder. This detail has turned out to be pretty useful.

The only issue I found, is that a *.pyc file in one folder might “override” a *.py file in another folder. This can can cause unintentional “local overrides”, say you have removed a *.py file from source control, but the user still has the *.pyc file on disk. The solution was to delete any *.pyc files from disk as each application launches. Seems a little dirty, but has worked so far.

I’m pretty pleased with this setup.

Tuesday, March 31st, 2015 Main No Comments

MaxPlus (3dsMax Python) equivalence test issue

Two Python objects that wrap the same Max Node, should be considered equal, but not identical.

Following this “==” and “in” should successfully test for equality of two or more Python objects that wrap the same 3dsMax Node.
However “is” should fail, as this checks if two Python objects are the same, not equivalent.

In MaxPlus for 3dsMax 2014 Extension, this is unfortunately not the behavior your get (2015 and forward seems to addresses that). Something to be aware of, or you might think you have Gremlins in your code.

This means you have to find some way to check for equivalence yourself (In Max we obviously cannot rely on naming). I’ve monkey patched INode, with the following code, but a solution that handles all wrapped Max nodes would be preferable. Let me know if you have suggesting for how to do that (I’m a Max & MaxPlus noob).

import MaxPlus

def __eq__(self, other):
----if self.GetHandle() == other.GetHandle():
------return True
----return False
----return False

MaxPlus.INode.__eq__ = __eq__

Hashing is also “broken”. If you want to be able to use INodes as dictionary keys you might want to implement __hash__ as well:

def __hash__(self): return self.GetHandle()

MaxPlus.INode.__hash__ = __hash__

According to this dictionary key insertion and retrieval is based upon Hash; then Equivalence, so should be good.

All in all a nasty hack to a somewhat nasty Python implementation. You could of course simply do equality by comparing Object Handles and also use these as dictionary keys, but what’s the fun in that? :-)

Thursday, September 11th, 2014 Main No Comments

MotionBuilder 2014 for download is missing from MotionBuilders 2014 instalation. It provides contextual help in MotionBuilders Python editor and allows for pyfbsdk code introspection/completion with external IDE’s – like Wing or my personal favorite PyCharm. Autodesk were kind enough to provide it and to let me re-distrubute it

I had some trouble uploading a .py/.txt file – so I just pasted the whole thing over at tech artists org:

Friday, January 3rd, 2014 Main No Comments

MotionBuilder 2014 Take Swap save bug Fix

MotionBuilder 2014 has a save bug where the index of takes (FBX anim stacks) will swap around on save. At work our game animation export setup relies on the index of the takes and all that breaks once someone presses save.. Not great :-)

Extension/service pack time has come and gone for the other 2014 media and entertainment products. So I think it’s fair to assume that nothing is coming for MotionBuilder and that we are on our own, in dealing with this (unless our Subscription level covers us).

As it turns out, the fix is really straightforward – once you find it. Simply make sure you create a take before you save. Meaning, after opening a scene with multiple takes in it, if you save before creating a new take, you will get the bug. If you create a new take prior to saving, you’ll be fine. You can even delete it, before you save. Obviously we cannot be counted on to remember to do this rather awkward procedure, so here is how to make it happen automatically.

## Define take fix and add/remove pre-save callback methods somewhere ##
def prevent_take_save_bug():
    print 'Take fix is running...'
    new_take = FBTake("Take_Save_Bug_Temp_Take")
def onFileSave(control, event):
    """Run this function every time a scene is saved"""
def fileSaveRegister():
    """Register file pre-save events"""
def fileSaveUnregister(control=None, event=None):
    """Unregister file pre-save events"""
## Add the callback, most likely on MotionBuilder startup ##
Monday, September 30th, 2013 Main No Comments

UPDATED: Project relative paths for external file references

If you work with multiple branches, you might be experiencing issues with Mayas preference for resolving Absolut paths before Relative ones for external file references (textures, audio, other Maya files, etc).

If I create a Maya scene in “Branch_A”, integrate that into “Branch_B” and then open the file, Maya will resolve all external file references to their absolute path if possible. This means that all my external file references will point to files in “Branch_A”, even though I’m opening the file in “Branch_B”. Only if Maya can’t resolve the absolute paths, it will try to resolve relative to the current Maya project/workspace.

I wanted to find an easy way to make the file paths relative without:
• Creating custom tools for the creating of each external file reference type
• Adding callbacks to handle when each external file reference type was created
• Updating the file path’s with the Maya scene loaded (this would cause Maya to reload them)

Doing a text search and replace within the Maya file (.ma), post save/export, seemed like the simplest idea. I tried it out and it turned out to be easy to do and very fast performing. So here is some of the code involved:

Code that replaces path in the last saved scene:

 import re
import paths

def make_file_references_relative(path):
    Make external file references project relative, in the last saved file

    The Maya file has to be an ASCII file and the external file
    references have to be within the current Maya project/workspace.

    D:/P4/My_Project/dev/sprint_tech//Construction/texture.tga -> Construction/texture.tga

    if path:
        # Here we must check that the file is type ascii
        if path.lower().endswith(".ma"):
                f = open(path, "r")
                text =
                text = remove_old_project_paths(text)
                text = remove_project_path(text)
                print "make_file_references_relative: Failed to replace paths in file!"
                f = open(path, "w")

def remove_old_project_paths(text):
    Remove all valid project paths in input text. Also change Maya .mb type file references to .ma type
    removeDict = dict()

    # Remove old project paths (not current
    pattern = re.compile('"[^"]*"')
    for m in re.finditer(pattern, text):
        for token in ['/Construction/', '/Assembly/', '"Construction/', '"Assembly/']:
                tokenIndex =
                replaceString = '"' +[tokenIndex+1:].replace('.mb', '.ma').replace('.Mb', '.ma').replace('.MB', '.ma')
                if not removeDict.has_key(
                    removeDict[str(] = replaceString

    for key in removeDict.iterkeys():
        text = text.replace(key, removeDict[key])

    return text

def remove_project_path(text):
    Return current project path from input text.

    # Remove current project project
    projectPath = paths.project()
    removeString = '//'.join((projectPath.replace("\\", "/"), ""))
    text = text.replace(removeString, "")

    return text

Adding callbacks, for this to happen automatically. I’m using the API, since I could not get scriptNode/Job to happen both on save and export:

 import maya.OpenMaya as OpenMaya
import repath

userCallbacks = []

class _Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(_Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class Callback:
    __metaclass__ = _Singleton   
    def __init__(self):
        self.idList = []
    def add(self):
            if self not in userCallbacks:
    def remove(self):
        for _id in self.idList:
class Save_Relative_Callback(Callback):
    def __init__(self):
    def create_callbacks(self):
            self.idList.append(OpenMaya.MSceneMessage.addCallback(OpenMaya.MSceneMessage.kAfterSave, make_file_references_relative_save))
            self.idList.append(OpenMaya.MSceneMessage.addCallback(OpenMaya.MSceneMessage.kAfterExport, make_file_references_relative_export))
            print "Failed to create callback"
class Last_Save_Callback(Callback):
    lastSave = None
    lastExport = None
    def __init__(self):
    def create_callbacks(self):
            self.idList.append(OpenMaya.MSceneMessage.addCallback(OpenMaya.MSceneMessage.kBeforeSave, store_last_save))
            self.idList.append(OpenMaya.MSceneMessage.addCallback(OpenMaya.MSceneMessage.kBeforeExport, store_last_export))
            print "Failed to create callback"
def make_file_references_relative_save(*args):
    if Last_Save_Callback.lastSave:
def make_file_references_relative_export(*args):
    if Last_Save_Callback.lastExport:

def store_last_save(*args):
    Last_Save_Callback.lastSave = OpenMaya.MFileIO_beforeSaveFilename()

def store_last_export(*args):
    Last_Save_Callback.lastExport = OpenMaya.MFileIO_beforeExportFilename()

def add(callback):

Adding the callback in a Maya start up script:

 import callback

I opted to just store the project relative part of the file path. I tried using a environment variable (E.G. %MY_CURRENT_PROJECT%something/ While this worked great, it does not play nice with 3dsMax or MotionBuilder (who both seem oblivious to the idea of using environment variables in paths), when scenes are FBX’ed across. Using the project relative bit works better.

Of course if your pipeline is setup so that your current project/branch is in a folder that you have mounted to a consistent drive letter (with something like SUBST), you might not have these issues in the first place.

Tags: , ,

Sunday, April 7th, 2013 Main 1 Comment

Speed: Maya API vs. CMDS vs. PyMel

If you have to deal with attributes on a large set of objects, learning a little API or at least avoiding PyMel, might be worth a try. Here are some test results and the code run to acquire them:

Searching 44.000 transforms, to see it they have a specific custom attr (where every fourth node has it..)

With API:
Time Taken: 0.839416478551

With CMDS:
Time Taken: 14.0579282427

With PyMel:
Time Taken: 26.2123000353

import maya.OpenMaya as OpenMaya

import maya.cmds as mc
import pymel.core as pm
import time

customAttr = "MyTag"

class Timer():
    def __init__(self):
        self.start = None
    def __enter__(self):
        self.start = time.clock()
    def __exit__(self, type, value, traceback):
        print 'Time Taken: {0}'.format(time.clock() - self.start)

print "\nSearching 44.000 transforms, to see it they have a specific custom attr (where every third node has it..)"

print "\nWith API:"
with Timer():
    dagIt = OpenMaya.MItDag(OpenMaya.MItDag.kDepthFirst, OpenMaya.MFn.kTransform)
    nodesWithAttrAPI = list()
    while not dagIt.isDone():
    	depNode = OpenMaya.MFnDagNode(dagIt.currentItem())
    	depNodeAttr = depNode.hasAttribute(customAttr)
    	if depNodeAttr:
    	    path = OpenMaya.MDagPath()

print "\nWith CMDS:"
with Timer():
    nodesWithAttrCMDS = list()
    for transform in"transform"):
        if mc.attributeQuery(customAttr, node=transform, exists=True):
print "\nWith PyMel:"
with Timer():
    nodesWithAttrPyMel = list()
    for transform in"transform"):
        if pm.attributeQuery(customAttr, node=transform, exists=True):

Tags: , , , ,

Sunday, January 27th, 2013 Main No Comments

Hitman Absolution E3 demo gameplay

Tuesday, October 11th, 2011 Main No Comments

Rigging System sandbox

Created a tiddlywiki to have a place to have some process about how to create a rigging system, very much from a code/system development perspective I think – but let’s see where it goes.  It’s really just intended for myself and is not a professional display, so it will be unsensored and full of “late nights in the couch at the laptop” spelling errors and giberish. This blog and that little project might merge at some point though, time will tell..

Friday, May 7th, 2010 Main 1 Comment

UK Living

So I’ve made the Big Plunge and moved over to the UK. I’ve recently started work as a Senior Technical Character Artist at EA, the studio behind the Harry Potter games series. Very exciting stuff.

I have a pretty central position here that’s very character oriented, so that suits me just fine. At this point I’m quite looking forward to going full circle on a product, that is pretty sure to get to market, as I’ve my share of projects canceled at the end of pre-production. So all in all I’m a happy camper :-)

I’m pretty busy and a bit overwhelmed, being in a new country and all, but hopyfully I will get a chance to post some odds and ends, regardless.



Tuesday, December 1st, 2009 Main 3 Comments

Code Snippet: Setting a matrix attribute with maya.cmds

Seems you just can’t do it.. So use mel,PyMel or maya.OpenMaya!

However if can’t or won’t do neither, you can use this tiny procedure that will do it for you (using maya.mel):

# The proc
import maya.cmds as mc
import maya.mel as mel

def setMatrixAttr(obj, attr, matrixList):
    matrixString = ''
    for element in matrixList:
        matrixString = matrixString + str(element) + ''
    mel.eval("setAttr -type \"matrix\" " + obj + "." + attr + matrixString)

# Example of usage
matrix = mc.getAttr(’LeftLeg.worldInverseMatrix’)
setMatrixAttr('skinCluster51', 'bindPreMatrix[103]', matrix)
Saturday, October 3rd, 2009 Main 2 Comments