Thursday, August 21, 2008

How to access to Maya python objects from a plug-in written in C++.

This is a modified copy of my old document (you can still see the original somewhere on the net).
When I wrote this, Maya8.5 was latest, and it is still my latest unfortunately, so in this doc Maya8.5 is used. If you use Maya2008, you need to use Python2.5 (e.g. Python25.dll). And I was using windows at that time. I will add to this post how to do it on Linux and Mac soon. (Encouraging me to do so by posting a comment will become a good motivation :)

------------------------------------------------------------------------------------

Maya8.5 comes with Python. It is really not a must to know how to access to it from a plug-in written in C++ since you can make a plug-in in Python, but for some reason e.g. performance, you may want to know the way to access to it directly from your Maya .mll/.so/.bundle plug-in.

Python itself is a shared library which you can find at Maya8.5/bin/python24.dll (on windows) so if Maya can access to Python, our plug-ins can get access to it.

First you need to include Python.h before every includes like any other python programs. The version of Python must be 2.4 since Python inside Maya is version 2.4.3. Other version of Python e.g. 2.5 uses different shared library e.g. python25.dll and you cannot use the same Python as the one Maya uses.

Maya8.5 has already initialized Python interpreter so you don't need to (and you shouldn't) call Py_Initialize(), but in the doIt() (in case of a command plugin) of your plug-in, you need to call
PyGILState_Ensure()
Python interpreter is not thread safe and this function call locks the interpreter properly so that only the thread that is running your plug-in can access to Python. In the test plug-in code show below, I used PyRun_SimpleString() to access to Python. See this post to know how to use Python C/API. When you leave doIt(), you need to call
PyGILState_Release()
It is needed even if you don't use Python thread modules. Forgetting this freezes or crashes Maya easily.



#include <Python.h>
#include <maya/MGlobal.h>
#include <maya/MSimple.h>
DeclareSimpleCommand( maya85py, "", "8.5");

MStatus maya85py::doIt( const MArgList& args )
{
PyGILState_STATE state = PyGILState_Ensure();
PyRun_SimpleString("tamtam = 1");
PyGILState_Release(state);
return MS::kSuccess;
}

Here's the result of above test plug-in.

tamtam
# Error: name 'tamtam' is not defined
# Traceback (most recent call last):
# File "<maya console>", line 1, in ?
# NameError: name 'tamtam' is not defined #

import maya
maya.mel.eval("maya85py")

tamtam
# Result: 1 #


Debug build:
On windows, the compiler tries to link python24.lib or python24_d.lib automatically (on Linux and Mac you need to link the appropriate one manually). python24_d.lib is a debug version and you need to build python itself to get it. I surfed the web but couldn't find it, and tried to build python but full build was a little bit bothering (install tcl/tk, brabrabra...), so I just modified pyconfig.h in my Python24/include directory. It's all what you need to debug build your plug-in (You don't need this if you don't use the debugger). Here I just used Python header files and import library that comes with standard Python distribution, since Maya doesn't come with them. Recently I heard one of my friend made an import library using dumpbin command. It'll be better if you have time to do it (still not safe though).



//# pragma comment(lib,"python24_d.lib")
# pragma comment(lib,"python24.lib")
# else
# pragma comment(lib,"python24.lib")



Finally, in an actual plug-in PyRun_SimpleString("tamtam = 1"); can be as complicated as you want, missing PyGILState_Release() execution by some exception is quite dangerous. I would use try-catch clause.

16 comments:

greatRGB said...

Hello! Wonderful post, I'm looking to do this exact thing...I was wondering can I get a return from this? For example say I have a python method that returns objects in a scene:

string objects[] = PyRun_SimpleString("getObjects()");

would that be possible? Thanks again!

tony

hohehohe2 [at] gmail.com said...

Yes, I would use boost.python.
(See http://koichitamura.blogspot.com/2008/06/boostpython-examples.html)
but compiling with boost.python needs a bit of experience so here's how to get the value without it.
There are several funcs in the PyRun_* family and PyRun_SimpleString() is the simplest one. You can use PyRun_String() instead.

For the usage of PyRun_String(), see
http://docs.python.org/c-api/veryhigh.html?highlight=pyrun_simplestring#PyRun_String
and an example here
http://www.google.co.jp/codesearch/p?hl=ja#z8lqyVo1CjA/Python-2.0b1/Python/pythonrun.c&q=PyRun_String

The returned value is a reference to a list of string if not NULL. (or tuple? I forget). After checking the reference with PySequence_Check() and make sure it is a list, PySequence_GET_SIZE() then PySequence_GET_ITEM() in the loop, then PyString_Check then PyString_AsString().
http://docs.python.org/c-api/sequence.html
http://docs.python.org/c-api/string.html?highlight=pystring_asstring#PyString_AsString

Don't forget deleting new references. You can see if a function creates a new reference or not from the manual. If Python creates a new reference, you'll see something like "Return value: New reference", otherwise you'll see "Return value: borrowed reference". Call Py_DECREF() or Py_XDECREF() after you have used the object if and only if you get a new reference. And make sure you call PyErr_Print() or PyErr_Clear()
on Python error. Usually NULL is returned on error when a reference to an object (i.e. PyObject*) is supposed to be returned but refer the manual to see how to detect an error.
PyErr_Print() outputs traceback to the standard output and clear the error state, PyErr_Clear() doesn't output anything.

See
http://koichitamura.blogspot.com/2008/06/this-is-small-python-capi-tutorial.html
for more information about Python C/API.

Good luck.

hohehohe2 [at] gmail.com said...

Oops, "make sure it is a list"->"make sure it is a sequence"

greatRGB said...

Wow, thank you very much for all the information!! I will update later my progress, One more question (which I will most likely find out later when I give this a shot) is does this run the python commands in the current maya session interpreter that runs the plugin? If so can I then can use the SWIG maya wrappers such as maya.cmds? I'm sure I will find out soon enough but I couldn't resist asking while I have your attention! Thanks again very much!

tony

hohehohe2 [at] gmail.com said...

Yeah it's the same as typing the script in the script editor so you can use maya.cmds. You can think of three things: Maya, Python, and your plug-in. Maya accesses to Python, your plug-in accesses to Python just the same way, and Python accesses to Maya API through an extension made with SWIG. So from this point of view Python is just a Maya API wrapper.

greatRGB said...

Ah perfect!! Sorry to keep bothering, but I compiled python on x64 to get the python25_d.dll I got that goin and tried the example you have on this page. When I run my command unfortunatly I get an Assertion failed! error. It says its occuring in the file pystate.c at line 561...have you seen this error before? Thank you very much for taking the time to help me with this.

tony

hohehohe2 [at] gmail.com said...

tony,

That's alright.
No I haven't. I didn't compile python25_d.dll. I just modified pyconfig.h
This is my recommendation because if the Python dll name is different, your plug-in will try to load another Python in the process, which is definitely not what you expect.

greatRGB said...

after I comment that out when I try and compile it's looking for python25.lib, is this a file I am going to need? Sorry my C++ skills are very basic :).

tony

greatRGB said...

It works!!!!!!!!!!!!!!!!!! Amazing!!!! Thanks very much for your patience with me on this. I haven't attempted to get results back or anything like that, but I have the example you posted working. This really opens up many possibilities! For one using C++/CLI to bridge between maya's CPython and C#.NET! Great stuff!!

tony

Thomas Goddard said...

Awesome stuff!!!

tommy

hohehohe2 [at] gmail.com said...

tony,

Good! Enjoy making cool stuff with the technique and have fun :)


tommy,

Thanks!

themush said...

Great post. But does anyone know how to call a mll plugin from maya. Im trying to use the rayIntersect plugin. In MEL i would call it like rayIntersect -ip 0 0 0 -di 0 1 -10 -q -p1 -i1 pCube1 but not sure if i can access this using python.

greatRGB said...

Well the mel command most likely wasn't wrapped into a python command. You could always just use:

import maya.mel as mm

mm.eval("mel command")

that will call a mel command from python.

hohehohe2 [at] gmail.com said...

Yeah using maya.mel.eval() is my recommendation too.

If you are a Maya hacker maybe you can make a python command plug-in that calls arbitrary Maya command plug-in internally (i.e. calling creator, doIt, undoIt, redoIt). It could be something like

mm.executePluginMelCommand("melcommand", param1, param2)

It'll be probably faster since We don't have to make a MEL command string in Python, and Maya doesn't parse the MEL script.
Just a tricky idea.

themush said...

great thanks

Rachat de credit said...

It has been a good guide, thank you so much, now to access to maya python objects from a plug-in written in c++. is simple and easy with your help. Thank you