Author Archive

Using JSON to store node information in Maya

Written by mattanimation. Posted in JSON, Maya, pyMel, Python

Recently I have been looking into ways to optimize a pose library that I made. The pose information was stored using XML. This worked great and was all dandy, but my implementation always seemed a bit clunky, having to open and close the file read write and change the information, mainly having to traverse a hierarchy then store it in as a list to make changes before re-writing the pose. I read that others have used JSON which had heard of many times and since pretty much every coding language out there has support for it, it must must be a win right? I gave it a shot and came up with this example today:

import json
import pymel.core as pm

def store(name, objs):

    pose={}
    ctrls=[]
    for o in objs:
        data={}
        for attr in pm.listAttr(str(o), keyable=True, unlocked=True):
            value = pm.getAttr(str(o)+'.'+attr)
            data.setdefault(str(attr),value)

        ctrls.append([str(o),[data]])

    pose.setdefault(name, {"ctrls":ctrls})

    f=open('/Users/yourname/Desktop/' + name + '.rp', 'w')
    f.write(json.dumps(pose))
    f.close()

def load(poseToLoad):
    f=open('/Users/yourname/Desktop/' + poseToLoad +'.rp', 'r')
    data=f.readlines()
    f.close()
    j_data = json.loads(data[0])

    ctrls = j_data[poseToLoad]['ctrls']

    for c in ctrls:
        ctrl = c[0]
        attrs = pm.listAttr(ctrl, keyable=True, unlocked=True)
        for a in attrs:
            at = c[1][0]
            val = c[1][0][a]
            pm.setAttr(ctrl + '.' + a, val)

if len(pm.ls(sl=True)):
objs = pm.ls(sl=True)
#   store("pose1",objs)
load("pose1")
Explaination

In the block above there is an example of using JSON to save the attributes of the selected nodes in maya, the node names, and a pose name associated with it. You can paste this code in a python tab in Maya and run it(make sure you insert your own file path where needed) to see the results. Basically JSON uses a few common data structures that easily recognizable even to the beginner python user, they are: dictionaries, lists, strings, and numbers. At first the structure looked way harder to read than XML but after a second glance it literally was just looking at a nested dictionary or list, and then I realized something even cooler, when I read the information back from the file, that’s exactly what they were coming in! No more traversing elements and storing into local lists, just a simple clean load right into the pyMel commands.

Now the thing that I am still working on is the actual data structure and what would be most desirable to work with, this works for now, but it would be awesome to just reference everything by the key and not worry about the indices of an array. Needless to say it’s worth looking into if you haven’t already.

*Update*

After actually implementing this and testing with my pose library I was able to shorten the code significantly. The data structure itself now looks like this:

{"poseNameHere": {"ctrls": {"pCube1": {"translateX": 2.5280296598674887, "translateY": 1.4508343627372717,
"translateZ": 11.414172610952747, "scaleX": 1.0, "scaleY": 1.0, "visibility": true, "rotateX": 0.0, "rotateY": 0.0,
"rotateZ": 0.0, "scaleZ": 1.0}, "pSphere1": {"translateX": -8.2447907269338465, "translateY": -1.2861870433449312,
"translateZ": 12.463030765724765, "scaleX": 1.0, "scaleY": 1.0, "visibility": true, "rotateX": 0.0, "rotateY": 0.0,
"rotateZ": 0.0, "scaleZ": 1.0}, "pCylinder1": {"translateX": -6.3732975936497738, "translateY": 2.1936347842880819,
"translateZ": 7.2561024617979939, "scaleX": 1.0, "scaleY": 1.0, "visibility": true, "rotateX": 0.0, "rotateY": 0.0,
"rotateZ": 0.0, "scaleZ": 1.0}}}}

you might be like wtf is that? Let’s break it down. As you might already know a dictionary is created using a key and a value, they are separated by a colon between them.

 myDict = {"keyName":value}

If I want to get the value, I refer to the key like this:

newVar = myDict["keyName"]

Now looking at the above example again we can see that the poseName’s value is actually a dictionary, that dictionary has a key called “ctrls”. The “ctrls” dictionary holds all the controls for the pose. Each ctrl name is a key as well, and thier value is a dictionary that has attributes for the key and the attr values for the values. Make sense?

Like I stated in the original post, I was working on the data structure, before I had arrays for the ctrls but I wanted to not have to reference an index and just use the names as keys for everything so here is what the read and write functions look like now:


def writePoseFile(self, objs, path, thumb, name):
    """
    Write a file that stores all the keyable attributes on a list of objects passed in.

    @type alist: list
    @param alist: the list of objects(ctrls) to iterate through and get the attr:value pair
    @type string: str
    @param string: the path that the file will be written to
    @type string: str
    @param string: the thumbnail image name
    @type string: str
    @param string: the name of the pose to be stored

    """
    ctrls={}
    for o in objs:
        data={}
        for attr in cmds.listAttr(str(o), keyable=True, unlocked=True):
            data.setdefault(str(attr),cmds.getAttr(str(o)+'.'+attr))

        ctrls.setdefault(str(o),data)

    pose = {name:{"ctrls":ctrls}}
    f=open(path, 'w')
    f.write(json.dumps(pose))
    f.close()

def setPose(self, fpn, *args):
    """
    Read a pose file and set the ctrls attr's values

    @type string: str
    @param string: the ui path to the button that was clicked

    """
    #get the name of the button clicked
    poseName = cmds.shelfButton(fpn, q=True, ann=True) #annotation of btn
    currentTab = self.tabList[cmds.tabLayout(self.UIElements['mainTabLayout'], q=True, selectTabIndex=True)-1]
    poseFilePath = self.currentCharDir +'/'+ currentTab + '/' + poseName +'.pose'

    #read the pose file
    f=open(poseFilePath, 'r')
    j_data= json.loads(f.readline())
    f.close()

    for c in j_data[poseName]['ctrls'].keys():
        attrs = cmds.listAttr(c, keyable=True, unlocked=True)
        for a in attrs:
            cmds.setAttr(c + '.' + a, j_data[poseName]['ctrls']1[a])

    #end

QStringList in Python3.x

Written by mattanimation. Posted in pyQT, Python

I decided to start going through Yasin’s awesome MVC programming tutorials in PyQt(actually doing it instead of just watch) and ran into a little snag when on the first lesson. I entered in all the same code but in the IDE I was using got an error that said:
'module' object has no attribute 'QStringList'

So after digging around and searching I found the answer and wanted to share it. I was using python ver 3.1 and according to what I read on stack overflow here , if you are using PyQt4 and python 3 then you don’t need to use QStrings, in fact that aren’t in the library which is what that error means. so if you are getting that same error simply use a python string list like this:

data = ['one', 'two', 'three', 'four', 'five']

and all should be well and dandy.

Python, Recursive Functions and Returns

Written by mattanimation. Posted in Python, Uncategorized

So, I found out the other other day that if you want to have a value returned when using a recursive function you must include the return line in each instance of the return. Example:

Incorrect way:

 def cleanMyString(self, url):
      returnURL = ''
      if ':' in str(url.encode('utf-8')):
           fixMe = url.replace(':', '_')
           self.clean(fixMe)
      elif '$' in str(url.encode('utf-8')):
           fixMe = url.replace('$', '-')
           self.clean(fixMe)
      else:
            returnURL = url
           return returnURL

The result of this method is None unless the string did not match any of the conditions to re-curse.

Correct way:

 def cleanMyString(self, url):
      returnURL = ''
      if ':' in str(url.encode('utf-8')):
           fixMe = url.replace(':', '_')
           return self.clean(fixMe)
      elif '$' in str(url.encode('utf-8')):
           fixMe = url.replace('$', '-')
           return self.clean(fixMe)
      else:
          returnURL = url
          return returnURL

 

so we can see that instead of calling the function directly we need to put return in front, otherwise we get a return value of None.

Ignore what I said about QT in Maya

Written by mattanimation. Posted in Uncategorized

Well I was reading this post by Nathan Horne of Naughty Dog and read this :

How to load a designer file
  • DO NOT USE cmds.loadUi!!!
    • seriously, don’t.. forget it exists, now.
    • Who ever wrote it needs to be punched in the mouth.

 

This abruptly caught my attention. The article itself had already convinced me to want to use PySide and PyQt to code UIs but left me on the fence wether to use the QTDesigner or not, until I read this. Either way it is a very good read, take a look and listen to the experts!