Mixing Maya Mpoints and Python Lists Together

A colleague of mine came across an oddity when working with the Maya Python API the other day, which serves as a poignant reminder that again, no matter what language we’re working with, or however many layers of abstractions there are between us, ultimately, we are dealing with memory, and we would do well to remember as such.

(For obvious reasons, the following code sample provided is a simplified version of the problem set.)

def test():
   selList = om.MSelectionList()
   selList.add('pSphereShape1')
   dagPath = om.MDagPath()
   selList.getDagPath(0, dagPath)
   pointArray = om.MPointArray()
   om.MFnMesh(dagPath).getPoints(pointArray, om.MSpace.kWorld)
   pointList = []
   for idx in xrange(pointArray.length()):
       pointList.append(pointArray[idx])
   return tuple(pointList)

With a simple polygon sphere in the scene, he then attempted to retrieve and verify the positions of the points of the mesh.

a = test()
for e in a:
   print e[0], e[1], e[2], e[3]

Upon printing the first few times, all seemed well.

2.56925402499 1.63650207796 3.00179863855 1.0
2.40539809979 1.63650207796 3.08528746053 1.0

After a few seconds, however, things started to go haywire. Nothing else had been done in the scene other than executing the print statements over and over.

0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0
0.0 0.0 0.0 7.72799262084e-315
0.0 0.0 0.0 4.94065645841e-324

So what’s going on here?

Thanks to Serguei Kalentchouk, the answer lies in the documentation for MPointArray itself.

    MPoint & operator[] (unsigned int index)
    Returns a reference to the element at the given index.

The signature tells us that it returns a reference to the MPoint. Therefore, when we add those MPoint objects to the Python list, we are really only adding references of them to the list. Thus, after the test() function exits, the memory of the MPointArray gets destroyed from the stack at an arbitrary point in time and hence the values of the MPoints that were stored in it are now completely invalid.

The fix, assuming we really wanted to return a Python list instead of an MPointArray? To make a copy of the MPoint object and store that in the Python list instead, rather than storing references to the MPoints themselves.

def test():
   selList = om.MSelectionList()
   selList.add('pSphereShape1')
   dagPath = om.MDagPath()
   selList.getDagPath(0, dagPath)
   pointArray = om.MPointArray()
   om.MFnMesh(dagPath).getPoints(pointArray, om.MSpace.kWorld)
   pointList = []
   for idx in xrange(pointArray.length()):
       pointList.append(om.MPoint(pointArray[idx]))
   return tuple(pointList)

(Another point raised by Cesar Saez: you can also use OM2 to side-step this problem entirely, since it doesn’t rely on SWIG-wrapped C++ objects and everything is a PyObject in the first place.)