In a previous post, I quickly explained why Python isn't secure and may be a bad guy performance wise. One option to solve the two issues at the same time is to write your code in C++ for example. And in case you still want to access the C++ functionality, you expose it to Python. There is several way to do this, you either expose your code to Python using:
- the Python SDK
- Swig
- Boost
SWIG is a software development tool that connects programs written in C and C++ with a variety of high-level programming languages. SWIG is used with different types of languages including common scripting languages such as Perl, PHP, Python, Tcl and Ruby. http://www.swig.org/
To use SWIG you construct an interface file which defines classes, functions, variables, and constants. Then pass the interface file to a command line utility which produces an additional source file with the bindings that make your C++ code accessible to the script language.
While Swig is very popular and often used by programmers, you need to teach Swig how to handle the different types. Otherwise it is fairly easy to use and do mistakes ;) like in the first Maya Python implementation (called Python 1.0). The Python SDK and Boost are more interesting and we will quickly look how to do this today and the following post.
The Python way first
In theory, this is what you would need to do for pure C++/Python and not using Maya API:
#include <Python.h>
#pragma comment (lib, "python26.lib")
static PyObject *say_hello (PyObject *self, PyObject *args) {
const char *name =NULL ;
if ( !PyArg_ParseTuple (args, "s", &name) )
return (NULL) ;
printf ("Hello %s! (from Python)\n", name) ;
Py_INCREF (Py_None) ;
Py_RETURN_NONE ;
}
static PyMethodDef HelloMethods[] = {
{"say_hello", say_hello, METH_VARARGS, "say_hello(self, str)"},
{NULL, NULL, 0, NULL}
} ;
PyMODINIT_FUNC initPyCyrille(void) {
(void)Py_InitModule ("PyCyrille", HelloMethods) ;
}
[Output the module as PyCyrille.pyd on Windows / PyCyrille.so on Linux]
But exposing directly from a Maya plug-in, you need a different approach (assuming Maya 2012 or Maya 2013, otherwise use the correct Python version number).
Note: replace $MAYA_LOCATION by your install path
- Create a new Maya C++ plug-in project
- In the “C++” -> “General” -> “AdditionalIncludeDirectories” include $MAYA_LOCATION\include
- In the “Linker” -> “Input” -> “AdditionalDependencies” add $MAYA_LOCATION\lib\python26.lib
- In your code add
#include <python2.6/Python.h>
- in the plug-in initializePlugin()method, initialize the Python kernel and register your Python functions - see code below.
But what is important to know at this point is that since Maya main thread is not a Python thread you need to register the GIL (thread state structure) towards that thread so the Python interpreter can see it and execute your code without crashing Maya. You do this using the pair PyGILState_Ensure()and PyGILState_Release().
Last, do not call the PyFinalize() method from the uninitializePlugin() method since Maya will do that only when it will be necessary.
The complete sample code
#include <python2.6/Python.h>
#pragma comment (lib, "python26.lib")
#include <maya/MGlobal.h>
#include <maya/MSelectionList.h>
#include <maya/MFnPlugin.h>
static PyObject *say_hello (PyObject *self, PyObject *args) {
const char *name =NULL ;
if ( !PyArg_ParseTuple (args, "s", &name) )
return (NULL) ;
PyGILState_STATE state =PyGILState_Ensure () ;
MSelectionList list ;
MGlobal::getActiveSelectionList (list) ;
unsigned int nb =list.length () ;
PyGILState_Release (state) ;
printf ("Hello %s, %d object(s) selected! (from Maya)\n",name,nb);
Py_INCREF (Py_None) ;
Py_RETURN_NONE ;
}
static PyMethodDef HelloMethods [] ={
{ "say_hello", say_hello, METH_VARARGS, "say_hello(self, str)" },
{ NULL, NULL, 0, NULL }
} ;
PLUGIN_EXPORT MStatus initializePlugin (MObject obj) {
MGlobal::displayInfo ("Initializing PyCyrille") ;
MStatus stat ;
MFnPlugin plugin (obj, "vendor", "version", "any", &stat) ;
if ( !stat )
return (MS::kFailure) ;
if ( !Py_IsInitialized () )
Py_Initialize () ;
if ( Py_IsInitialized () ) {
PyGILState_STATE state =PyGILState_Ensure () ;
PyObject *module =Py_InitModule ("PyCyrille", HelloMethods) ;
Py_INCREF (module) ;
PyGILState_Release (state) ;
}
return (MS::kSuccess) ;
}
PLUGIN_EXPORT MStatus uninitializePlugin(MObject obj) {
MFnPlugin plugin (obj) ;
// Do not call Py_Finalize()
//Py_Finalize();
return (MS::kSuccess) ;
}
[Output the module as a standard Maya plug-in]
Note on MacOS, I'll recommend to use distutils to compile your plug-in, instead of Xcode. Specifically, the problem is that you probably link to a different instance than the one you tihnk. distutils solves this problem for you because it knows which Python to link to and which flags to use by way of the Python interpreter you invoke with setup.py
OK, that's it for the hardcore technical details on exposing C++ to Python. In the next post we'll see how to do the same thing using Boost (and you can trust me when I say that's going to be a whole lot simpler than this post, honestly! :-)
Comments