Posts Tagged ‘ attribute

Pickling Python data to Maya attributes

Recently I made the connection that A:  It’s really easy to serialize (pickle) data in Python, and B: You can make string attributes on nodes in Maya.  It’s not that I was unaware of these topics, it’s just that they’d never occurred in the same mental conversation simultaneously before.  But when they did, it was sort of an ‘eureka moment’, and I wrote two functions to make use of this new-found power.

Making use of the cPickle module, I pickle the Python data to a string object and store to a string attr on a Maya node.  To retrieve the data, I unpickle the string attributes value back to Python data.
http://docs.python.org/library/pickle.html
http://docs.python.org/library/pickle.html#module-cPickle

import cPickle
import maya.cmds as mc

def pyToAttr(objAttr, data):
    """
    Write (pickle) Python data to the given Maya obj.attr.  This data can
    later be read back (unpickled) via attrToPy().

    Arguments:
    objAttr : string : a valid object.attribute name in the scene.  If the
        object exists, but the attribute doesn't, the attribute will be added.
        The if the attribute already exists, it must be of type 'string', so
        the Python data can be written to it.
    data : some Python data :  Data that will be pickled to the attribute
        in question.
    """
    obj, attr = objAttr.split('.')
    # Add the attr if it doesn't exist:
    if not mc.objExists(objAttr):
        mc.addAttr(obj, longName=attr, dataType='string')
    # Make sure it is the correct type before modifing:
    if mc.getAttr(objAttr, type=True) != 'string':
        raise Exception("Object '%s' already has an attribute called '%s', but it isn't type 'string'"%(obj,attr))

    # Pickle the data and return the coresponding string value:
    stringData = cPickle.dumps(data)
    # Make sure attr is unlocked before edit:
    mc.setAttr(objAttr, edit=True, lock=False)
    # Set attr to string value:
    mc.setAttr(objAttr, stringData, type='string')
    # And lock it for safety:
    mc.setAttr(objAttr, edit=True, lock=True)

def attrToPy(objAttr):
    """
    Take previously stored (pickled) data on a Maya attribute (put there via
    pyToAttr() ) and read it back (unpickle) to valid Python values.

    Arguments:
    objAttr : string : A valid object.attribute name in the scene.  And of course,
        it must have already had valid Python data pickled to it.

    Return : some Python data :  The reconstituted, unpickled Python data.
    """
    # Get the string representation of the pickled data.  Maya attrs return
    # unicode vals, and cPickle wants string, so we convert:
    stringAttrData = str(mc.getAttr(objAttr))
    # Un-pickle the string data:
    loadedData = cPickle.loads(stringAttrData)

    return loadedData

And here is some sample usage:

# Make some really special Python data you want to store to a Maya node:
specialData = {'red':52, 'green':63.3, 'blue':"ASDF"}
print "Python data to store to Maya obj.attr:"
print specialData
#Python data to store to Maya obj.attr:
#{'blue': 'ASDF', 'green': 63.299999999999997, 'red': 52}

# Define our node and attr name based on the selected object:
node = mc.ls(selection=True)[0]
objAttr = '%s.pyPickle'%node

# Store data to our node:
pyToAttr(objAttr, specialData)

# Later, get data back:
storedData = attrToPy(objAttr)
print "Restored Python Data:"
print storedData
#Restored Python Data:
#{'blue': 'ASDF', 'green': 63.299999999999997, 'red': 52}

How can I modify a Python attribute any time it is accessed?

Originally posted on my Python Wiki

I was designing a UI for a PyGame program I am working on, and needed a way to pass a value defining a “row height” into the functions that rendered my text to the screen. Since the UI could change, I didn’t want to have to hard-code positions into each element, later to modify it and have to redo all the positions.
What I came up with was a simple class with a single usable attribute, called val. Through using properties, I’m able to control how the attr behavies at time time its value is queried:

class Row(object):
    # class to store the current row location in the UI
    # Each time it is called, it will increment its value
    def __init__(self, val):
        self._val = 0
        self.orig = val
    @property
    def val(self):
        self._val = self._val + self.orig
        return self._val
row = Row(16)

print row.val, row.val, row.val, row.val
# 16 32 48 64

Properties have getter, setter, and deleter methods, but the default is getter, which I used above. So as you can see, each time I call to print, it accesses the val property (via the getter), and updates the internal counter.
This is a PyGame code snippet showing it in use:

overlay.blit(bubble, (8, row.val))
overlay.blit(toggleText, (8, row.val))
overlay.blit(lmb, (8, row.val))

Rather than having to specify a Y value for the last arg of the tuple, I can simply pass in my object, and it passes out the current new position, based on how many times it was called before.

I have no doubt there is probably some slicker way in Python, but it’s what I came up with on the spot :)