Many of Maya's C++ API methods require that one or more of their parameters be passed as pointers or references. Their return values, too, can be pointers or references.
In Python parameters of class types are passed by reference but parameters of simple types, like integers and floats, are passed by value, making it impossible to call those API methods from Python as Maya the Python API was mainly generated automatically using Swig (let's call it Python 1.0). The MScriptUtil class bridges this gap by providing methods which return pointers to values of simple types and which can extract values from such pointers. These pointers can also be used wherever an API method requires a reference to a simple type or an array of a simple type.
For example, MTransformationMatrix::setShear() requires as its first parameter an array of three values of simple type 'double'. To call setShear() from Python requires using MScriptUtil to create the array of doubles.
But because of its design, MScriptUtil is very easy to misuse and doing so can lead to errors and crashes in Maya. There are three main ways in which MScriptUtil is misused:
- Failing to initialize an MScriptUtil object's internal
storage to the correct size.
If you create an MScriptUtil object using the default constructor, e.g:
su = OpenMaya.MScriptUtil()
the object will have no internal storage allocated. If you subsequently attempt to get a pointer to its internal storage, e.g:
ptr = su.asIntPtr()
you will be getting back a pointer to a zero-sized memory block, which is not capable of holding anything and will likely crash if you attempt to use it.
You must first ensure that the MScriptUtil object's internal storage is set for the number of elements you need. You can do this by either supplying initial values to the constructor:
su = OpenMaya.MScriptUtil(1, 2) # Allocates storage for 2 elements.
or calling one of the 'create' methods:
su = OpenMaya.MScriptUtil() su.createFromInt(1, 2) # Allocates storage for 2 elements. su.createFromDouble(5.7) # Allocates storage for 1 element. su.createFromList([6, 12, -5, 9, 11]) # Allocates storage for 5 elements.
- Using an MScriptUtil pointer after its corresponding
MScriptUtil object has been destroyed.
This most commonly manifests itself in the following way:
ptr = OpenMaya.MScriptUtil(0).asIntPtr() ... go on to use ptr ...
All of the as*Ptr() methods of MScriptUtil return pointers to storage internal to the MScriptUtil object. If the object is destroyed then the pointers become invalid and any attempt to use them will result in errors or crashes. In the example above the MScriptUtil object generated by the OpenMaya.MScriptUtil(0) call is destroyed as soon as the asIntPtr() has returned, so 'ptr' will be invalid.
To make this work properly, you must hang on to the MScriptUtil object until you no longer need its pointer:
su = OpenMaya.MScriptUtil(0) ptr = su.asIntPtr() ... go on to use ptr ...
- Using the same MScriptUtil object for multiple simultaneous
pointers.
Each MScriptUtil object only contains a single block of storage. All calls to as*Ptr() will return pointers to the same block of storage. So if you do something like this:
su = om.MScriptUtil(0.0) xptr = su.asFloatPtr() yptr = su.asFloatPtr() zptr = su.asFloatPtr() OpenMaya.MSomething.someFunc(xptr, yptr, zptr)
it will either crash or you'll find that all three pointers point to the same value after the call.
To make this work you must create a separate MScriptUtil object for each pointer:
xsu = om.MScriptUtil(0.0) xptr = xsu.asFloatPtr() ysu = OpenMaya.MScriptUtil(0.0) yptr = ysu.asFloatPtr() zsu = OpenMaya.MScriptUtil(0.0) zptr = zsu.asFloatPtr() OpenMaya.MSomething.someFunc(xptr, yptr, zptr)
Note that it is fine to reuse an MScriptUtil object for a new pointer so long as you no longer need the old pointer. E.g:
su = om.MScriptUtil(0.0) distPtr = su.asFloatPtr() OpenMaya.MSomething.getDist(distPtr) dist = su.getFloat(distPtr) countPtr = su.asIntPtr() OpenMaya.MSomething.getCount(countPtr) count = su.getInt(countPtr)
The good news
The good news is that there is a second version of the Maya Python API (Python 2.0) which is better written and will get rid of the MScriptUtil class.
The Maya Python API 2.0 is a new version of the Maya Python API which provides a more Pythonic workflow and improved performance. Both the new and old APIs can co-exist in the same script but objects from the two different APIs cannot be used interchangeably.
The Python API 2.0 has a number of advantages over the original API:
- Array types are full Python sequences, including slice support.
- Methods which take Maya array parameters will usually also take native Python sequences, such as arrays and tuples. Exceptions are made in some case for performance reasons.
- The outputs of methods are usually returned in their return values, not through their parameter lists. Exceptions are made in some cases for performance reasons.
- Methods which return multiple values (e.g. MFnFluid.getResolution) return them as a tuple or list, eliminating the need for MScriptUtil.
- Object attributes are preferred over rather than set/get methods. For example you can now write array.sizeIncrement=64.
- There are more types of exceptions used when methods fail. Not everything is a RuntimeError, as was the case in the old API.
- The new API is generally faster than the old. Up to three times faster in some cases.
Using the Python API 2.0
The new Python API modules are found in maya.api. For example:
import maya.api.OpenMaya as om
Module names are the same as in the old API with the exception that the proxy classes (i.e. those beginning with MPx) no longer have their own module but reside in the same modules as other related classes. This more closely resembles the C++ API.
Class names are the same as in the old Python API and the C++ API. Method names are mostly the same with some differences where it affects the workflow. Some methods which simply get or set values on an object have been replaced with Python object attributes.
New and old API classes can be used within the same script, but their objects are not interchangeable. Thus, you can do this:
import maya.api.OpenMaya as newOM import maya.OpenMaya as oldOM newAttrObj = newOM.MObject() oldNodeObj = oldOM.MObject() ... newAttrFn = newOM.MFnAttribute(newAttrObj) oldNodeFn = oldOM.MFnDependencyNode(oldNodeObj) # OKAY: Print names from old and new function sets. print("Attribute name is %s.%s" % (oldNodeFn.name(), newAttrFn.name))
but not this:
import maya.api.OpenMaya as newOM import maya.OpenMaya as oldOM newAttrObj = newOM.MObject() oldNodeObj = oldOM.MObject() ... # BAD: Passing an old API MObject to a new API method. newPlug = newOM.MPlug(oldNodeObj, newAttrObj)
Given that the class and method names are mostly identical between the two APIs there is a lot of potential for confusion so it's best not to mix them if you can avoid it.
When writing a plugin which uses the new API, the plugin must define a function called maya_useNewAPI so that Maya will know to pass it objects from the new API rather than the old one. E.g:
def maya_useNewAPI(): pass
We encourage users to provide feedback on this new Python API through the online Suggestion site available from the Maya Help Menu and through the Autodesk Developer Network.
Comments
You can follow this conversation by subscribing to the comment feed for this post.