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