Pickled Points in Python

A wonderful feature of the Python language is its built-in method of serialization and de-serialization, called pickling. Pickles can be used to easily store (dump) data as efficient byte streams for storage, transfer, and retrieval. This is a great way to save state or pass complex objects around.

Many types of things can be pickled right out of the box; unfortunately, this powerful feature runs into issues when working with the OpenMaya API. Since these classes are wrapped by SWIG, you will get an error when trying to pickle one of these objects:

my_point = om.MPoint(1.0, 2.0, 3.0)  
pickle.dumps(my_point)
# Error: can't pickle SwigPyObject objects

A solution that I particularly like is to define a __reduce__ function, which allows you to tell Python how to serialize the object. Subclass the object in Maya you want to pickle, and in __reduce__, return a tuple of the class and the attributes you would give to the constructor to create the OpenMaya object.  So, in the case of MPoint:

class PickledPoint(om.MPoint):
    def __reduce__(self):
        return(self.__class__, (self.x, self.y, self.z, 
          self.w))

Now, create a point using your subclass and pickle away!

my_point = PickledPoint(1.0, 2.0, 3.0)     
unpickled_point = pickle.loads(pickle.dumps(my_point))
my_point == unpickled_point
# Result: True #

Neat!

You can extend this solution for working with function sets (like meshes and NURBS) in OpenMaya. In addition to adding the __reduce__ function in your subclass, define __init__ to take the name of the object in the scene you are pickling. In the constructor, you can use the name to retrieve the DAG MObject, which is passed along to the parent class:

class PickledMesh(om.MFnMesh):
    def __init__(self, name):
        selection_list = om.MSelectionList()
        shape = selection_list.add(name)
        dag_path = om.MDagPath()
        selection_list.getDagPath(0, dag_path)
        dag_node = dag_path.node()

        super(PickledMesh, self).__init__(dag_node)

    def __reduce__(self):
        return(self.__class__, (self.name(),))

Now, create your MObject with your new subclass, and…

my_mesh = PickledMesh('pSphereShape1')
my_mesh.numVertices()
# Result: 382 # 

pickled_mesh = pickle.loads(pickle.dumps(my_mesh))
pickled_mesh.numVertices()
# Result: 382 #

…voilà!