Creating Custom Locator with Maya's Python API (English Translation)
Par Narann le vendredi, 12 février 2010, 22:36 - Script et code - Lien permanent
This post is a english translation of this post. I'd wrote it after post this on CGTalk. Great thanks to Daisy C. Lyle. She translate all the french version to english and make a great work! She save me hours doing this! :sourit: . Hope that this tutorial will be usefull.
As I mentioned before, I’m starting to use the Maya API Python binding. So I had a look at Rob Bateman’s sources (which I found incomprehensible a year ago) and “translated” them (not without difficulty) into Python…So I’ve created a little script with a custom locator. It’s obviously not the simplest thing in the world to begin with (I did have some grounding thanks to other tutorials, the Python “plug-ins” already incorporated into Maya and the OpenGL tutorials I’d done) but once the code is up and running, it’s quite fun to modify it to make your own locator…
So, on the menu we have:
- OpenGL (Well, how else are you going to draw your locator? :hehe: )
- Color change as a function of the selection state of the locator.
It’s not much but you’ll see it’s enough to be getting on with!
Scripting is pointless, it’s for newbies!
No! If you think that, then you haven’t understood anything. The two are closely related. If I chose the example of the locator, it’s not just because it’s “cool” but also because it’s an example of a thing it is only possible to do with the API…
But the API doesn’t do everything! :cayMal:
As you doubtless already know, the Maya interface is entirely written in MEL, which allows a scripter to quickly create a GUI for his or her scripts. Not all software packages offer this (did anyone say XSI?) and Maya is one of the few that boasts it.
Most Maya function calls are also scripted. This means you can “batch” your operations (when you have 50 scenes to open to change the value of an attribute, you won’t be laughing so hard! :hehe: )
I can only invite you to read my mentor’s post on the subject (displaying the C++ equivalent of a MEL code).
And finally: Python is a scripting language! Even if it can be “pre-compiled” by the interpreter, it’s still script.
That said, I think that this argument is a bit fruitless as even if you script using the Maya API Python binding, once the basics are understood you realize that the differences in syntax between a piece of C++ code and its equivalent in Python are minimal.
A little example of variable declaration:
MFnPlugin fnPlugin(obj);
fnPlugin = OpenMayaMPx.MFnPlugin(obj)
C++ is still the most user-friendly, I think (I’m not saying that because of this examples but in a more general way), which seems logical because that it's the historically used implementation, added to the fact that it remains a language fairly close to machine.
Then it all depends on whether or not you want to go fast. Compiled code (C++) will obviously go faster (about ten time) but I'm supposing that if you want to go fast, either you’re not good and you’re writing rotten code which leaves you no other solution than to resort to a lower-level language to speed it up, or you’re a “real” developer… If that’s the case, I don’t see what you’re doing on the blog of a Sunday coder! (Me! :aupoil: ).
What the Hell, one day I’m going to make a bench between MEL, pythonForMel, C++ and pythonForMayaApi :sourit: .
Code basics
Before we start messing around with the locator, we need the base code. Here we go!
import sys import maya.OpenMaya as OpenMaya import maya.OpenMayaMPx as OpenMayaMPx import maya.OpenMayaRender as OpenMayaRender nodeTypeName = "myCustomLocator" nodeTypeId = OpenMaya.MTypeId(0x87079) glRenderer = OpenMayaRender.MHardwareRenderer.theRenderer() glFT = glRenderer.glFunctionTable() class myNode(OpenMayaMPx.MPxLocatorNode): def __init__(self): OpenMayaMPx.MPxLocatorNode.__init__(self) def nodeCreator(): return OpenMayaMPx.asMPxPtr(myNode()) def nodeInitializer(): return OpenMaya.MStatus.kSuccess def initializePlugin(obj): plugin = OpenMayaMPx.MFnPlugin(obj) try: plugin.registerNode(nodeTypeName, nodeTypeId, nodeCreator, nodeInitializer, OpenMayaMPx.MPxNode.kLocatorNode) except: sys.stderr.write( "Failed to register node: %s" % nodeTypeName) def uninitializePlugin(obj): plugin = OpenMayaMPx.MFnPlugin(obj) try: plugin.deregisterNode(nodeTypeId) except: sys.stderr.write( "Failed to deregister node: %s" % nodeTypeName)
Bang! Not acting so clever now are we? (I wasn’t acting clever the first time around either! :baffed: ).
Right, in fact it’s not very complicated. Explanation:
import sys import maya.OpenMaya as OpenMaya import maya.OpenMayaMPx as OpenMayaMPx import maya.OpenMayaRender as OpenMayaRender
Here we are importing the main modules. The first (“sys”) is the system module which we will use to write the plug-in initialization error messages (that’s all.)
The next three are Maya API Python modules. They have been divided into several modules in order to clarify their use:
- OpenMaya is the module for classes relating to node and command definitions and their “assembly” into plug-ins.
- OpenMayaMPx is a Python-specific module. It contains the Maya’s proxy (or MPx class) bindings.
- OpenMayaRender is the module for classes relating to render (hardware or software).
nodeTypeName = "myCustomLocator"
This variable (I declare it at the beginning) will be used to give a name to the type of node you wish to create.
nodeTypeId = OpenMaya.MTypeId(0x87079)
This variable (I also declare it at the beginning) will be used to give an ID (identifier) to our type of node.
glRenderer = OpenMayaRender.MHardwareRenderer.theRenderer()
This is where it gets complicated (especially for me.) This command is not referred to in the documentation (even though it appears in the example C++ codes.) This command makes it possible to retrieve a pointer to the class in charge of the render (hardware) of the viewports. It’s thanks to this that we are going to be able to “draw” our locator (using OpenGL commands.)
glFT = glRenderer.glFunctionTable()
The glFunctionTable() method returns a pointer to an object containing all the OpenGL instructions handled by Maya.
Now here we’re really getting stuck into OpenGL (well perhaps not quite, as you’re about to see :sourit: ):
OpenGL, some informations
What is OpenGL? (Wiki) To summarize, it’s a way of communicating with the graphics card. It involves simple commands, e.g. to display points, lines, triangles, quads, textures, etc…
Before we go any further, we’re going to have to understand how this all works. If you want to “draw” locators, you have to know how to use the pen that is going to do it.
As a piece of code is worth more than a long lecture, here is how to “draw” a line in OpenGL (this is a C source. For Maya, we will see that there are two or three things you have to know first):
glBegin(GL_LINES) glVertex3f(0.0, -0.5, 0.0) glVertex3f(0.0, 0.5, 0.0) glEnd()
This technique is the “basic”, or “immediate mode” technique.
glBegin(GL_LINES)
This command allows us to “go into primitive draw mode”(In a 3-D space.) We’re putting the pencil to the paper, if you like. The argument (GL_LINES) enables us to say how the following commands will be interpreted, inline here. Every two vertices, we write another line. One line per pair of vertices.
glVertex3f(0.0, -0.5, 0.0) glVertex3f(0.0, 0.5, 0.0)
These two commands are the commands which “place” the vertices in space. The command is glVertex, the number is the number of arguments (here, three: X,Y and Z) and the last letter is the data type (here, “f” for float.)
Basically, we’re placing two vertices in space. And given that we have put ourselves into “line draw” mode (per vertex pair), we have just drawn a line.
glEnd()
And that’s just a way of “shutting down communications” with the graphics card and returning to the main program.
As you see, OpenGL isn’t as hard as all that! This was also how I understood that it’s the processor that calculates all the vertices’ positions in each image (well, that’s the old method; there are others, especially for static objects which are stored in the memory of the graphics card and called by a single instruction. Having said that, a large part of the work is done by the processor).
With this, you are almost ready to draw your own locator. I have just one point left to address.
OpenGL, the Maya version!
In fact, the code that I have shown you is a C program. I am now going to explain to you some of the few OpenGL peculiarities specific to Maya.
Maya, out of concern for interoperability (I suppose there are other reasons) has its own OpenGL implementation. So, when we wish to draw in the viewport, we don’t directly use the Windows or Mac implementation but in fact Maya’s own. (This can be done in C++, which makes it possible to dispense with the Maya wrapper, but you have to handle the interoperability in your code. It should also be possible to do this in Python but I haven’t tried). That doesn’t change much in the code but the constants (GL_LINES for instance) have another name.
As written in the Maya API documentation:
The naming convention used in this file is to take the regular GL* definitions and add a "M" prefix
Translation: “We put an ‘M’ in front of all the OpenGL constants”. :seSentCon:
So Maya won’t understand GL_LINES, only OpenMayaRender.MGL_LINES :baffed:
And here is how we write a line in OpenGL in Maya:
glFT.glBegin(OpenMayaRender.MGL_LINES) glFT.glVertex3f(0.0, -0.5, 0.0) glFT.glVertex3f(0.0, 0.5, 0.0) glFT.glEnd()
So no big differences then ^^. Any experienced OpenGL user should be able to do it with their eyes closed!
So shall we write this locator then?
No! Not yet! There are still things to learn! After this little introduction to OpenGL, let’s go back to our Python script.
To be able to draw the locator, we have to derive the “draw” function (yes, because Maya allows you to derive functions…Not everyone can say as much, eh, XSI? :siffle: ). This function is used to…draw custom geometries using OpenGL functions.
Here is the basic code for the “draw” method:
def draw(self, view, path, style, status): view.beginGL() view.endGL()
The beginGL( ) and endGL( ) functions make it possible (well, this is my interpretation) to tell Maya that we are going to use OpenGL commands between them. According to the documentation, if they aren’t included in the right place, the program will exceed its allocated memory the program will therefore crash.
I’m deliberately going to forget a few functions for the moment, we’ll look at them later.
Go Pawaaaah!
So our code now looks like this:
import sys import maya.OpenMaya as OpenMaya import maya.OpenMayaMPx as OpenMayaMPx import maya.OpenMayaRender as OpenMayaRender nodeTypeName = "myCustomLocator" nodeTypeId = OpenMaya.MTypeId(0x87079) glRenderer = OpenMayaRender.MHardwareRenderer.theRenderer() glFT = glRenderer.glFunctionTable() class myNode(OpenMayaMPx.MPxLocatorNode): def __init__(self): OpenMayaMPx.MPxLocatorNode.__init__(self) def draw(self, view, path, style, status): view.beginGL() glFT.glBegin(OpenMayaRender.MGL_LINES) glFT.glVertex3f(0.0, -0.5, 0.0) glFT.glVertex3f(0.0, 0.5, 0.0) glFT.glEnd() view.endGL() def nodeCreator(): return OpenMayaMPx.asMPxPtr(myNode()) def nodeInitializer(): return OpenMaya.MStatus.kSuccess def initializePlugin(obj): plugin = OpenMayaMPx.MFnPlugin(obj) try: plugin.registerNode(nodeTypeName, nodeTypeId, nodeCreator, nodeInitializer, OpenMayaMPx.MPxNode.kLocatorNode) except: sys.stderr.write( "Failed to register node: %s" % nodeTypeName) def uninitializePlugin(obj): plugin = OpenMayaMPx.MFnPlugin(obj) try: plugin.deregisterNode(nodeTypeId) except: sys.stderr.write( "Failed to deregister node: %s" % nodeTypeName)
You can download it here:
Load it (as a plug-in), and enter:
createNode myCustomLocator;
We’ve just made out first locator! :sourit: With this we can already do a few things… If you give it some elbow grease, you can get this:
import random
glFT.glBegin(OpenMayaRender.MGL_LINES) glFT.glVertex3f(random.uniform(-0.5, 0.5), random.uniform(-0.5, 0.5), random.uniform(-0.5, 0.5)) glFT.glVertex3f(random.uniform(-0.5, 0.5), random.uniform(-0.5, 0.5), random.uniform(-0.5, 0.5)) glFT.glEnd()
Le code: CustomLocatorNode002.7z
You’ll have a wicked locator! ^^
Right, it’s all very well making lines but that’s not all there is to it… I suggest we do… a quad! :laClasse:
glFT.glBegin(OpenMayaRender.MGL_LINES) glFT.glVertex3f(0.0, -0.5, 0.0) glFT.glVertex3f(0.0, 0.5, 0.0) glFT.glEnd() glFT.glBegin(OpenMayaRender.MGL_QUADS) glFT.glVertex3f(-0.5, 0.0, -0.5) glFT.glVertex3f(0.5, 0.0, -0.5) glFT.glVertex3f(0.5, 0.0, 0.5) glFT.glVertex3f(-0.5, 0.0, 0.5) glFT.glEnd()
The code: CustomLocatorNode003.7z
Paf le code!
Paf le locator!
OK, it’s a bit flashy but we’re going to change that. :reflechi: We’re going to change the color and add transparency.
First, the color. As default, Maya uses the interface colors (blue when unselected, green when selected, pink when ‘templated’ etc….) To change the color, all you have to do is add the command glColor3f( )
glFT.glBegin(OpenMayaRender.MGL_LINES) glFT.glVertex3f(0.0, -0.5, 0.0) glFT.glVertex3f(0.0, 0.5, 0.0) glFT.glEnd() glFT.glColor3f(1, 0, 0) #on change la couleur glFT.glBegin(OpenMayaRender.MGL_QUADS) glFT.glVertex3f(-0.5, 0.0, -0.5) glFT.glVertex3f(0.5, 0.0, -0.5) glFT.glVertex3f(0.5, 0.0, 0.5) glFT.glVertex3f(-0.5, 0.0, 0.5) glFT.glEnd()
The code: CustomLocatorNode004.7z
Re-Paf le code!
Re-Paf le locator!
Still just as flashy, huh? You’ll notice that only the line changes color as a function of the selection state. To ‘lighten up’ our locator a bit, we’re going to add transparency to our quad.
The Alpha
We are going to “enable” one of OpenGL's features: GL_BLEND (Don’t forget to disable it at the end):
#We activate feature glFT.glEnable(OpenMayaRender.MGL_BLEND) glFT.glBegin(OpenMayaRender.MGL_LINES) glFT.glVertex3f(0.0, -0.5, 0.0) glFT.glVertex3f(0.0, 0.5, 0.0) glFT.glEnd() glFT.glColor3f(1, 0, 0) #Change color glFT.glBegin(OpenMayaRender.MGL_QUADS) glFT.glVertex3f(-0.5, 0.0, -0.5) glFT.glVertex3f(0.5, 0.0, -0.5) glFT.glVertex3f(0.5, 0.0, 0.5) glFT.glVertex3f(-0.5, 0.0, 0.5) glFT.glEnd() #Don't forget to unactivate in the end! glFT.glDisable(OpenMayaRender.MGL_BLEND)
When this feature is enabled, OpenGL blends the given color with the resulting color (the background) according to a final factor (the Alpha.) If you run the code as it is, you won’t see any change. So we must add one last component to our color:
glFT.glEnable(OpenMayaRender.MGL_BLEND) glFT.glBegin(OpenMayaRender.MGL_LINES) glFT.glVertex3f(0.0, -0.5, 0.0) glFT.glVertex3f(0.0, 0.5, 0.0) glFT.glEnd() glFT.glColor4f(1, 0, 0, 0.5) #Change color and add alpha glFT.glBegin(OpenMayaRender.MGL_QUADS) glFT.glVertex3f(-0.5, 0.0, -0.5) glFT.glVertex3f(0.5, 0.0, -0.5) glFT.glVertex3f(0.5, 0.0, 0.5) glFT.glVertex3f(-0.5, 0.0, 0.5) glFT.glEnd() glFT.glDisable(OpenMayaRender.MGL_BLEND)
Le code: CustomLocatorNode005.7z
Et voila! Better no? ;)
We are coming to the last part: the selection states.
Selection
In fact we can change aspects of our locator depending on its selection state. Here, we're going to change... The color! (very original... -_-).
First of all, there are several "states" of a "drawn" object (Listed in M3dView::DisplayStatus). I'm going to give you the two main ones, the ones we’re going to be using:
- kActive -> The active (selected) objects. (Be careful, this is tricky!)
- kLead -> The last object selected.
- kDormant -> The inactive objects.
So, let’s explain the subtle difference::
In Maya, when you select an object, it becomes green. And when you add another object to the selection, this second object becomes green and the previous, white. In fact the green object is “in DisplayStatus” kLead, and the white object in kActive. Here’s a picture to explain (or remind you of?) the principle:
So we are going to be able to change the color as a function of the selection state.
As seen above, the derived function is draw, and the argument we use to know the state of the locator is "status". And nothing could be easier than using all this:
import maya.OpenMayaUI as OpenMayaUI # on top
if status == OpenMayaUI.M3dView.kLead: glFT.glColor4f(1, 0, 0, 0.3) #rouge if status == OpenMayaUI.M3dView.kActive: glFT.glColor4f(1, 1, 0, 0.4) #jaune if status == OpenMayaUI.M3dView.kDormant: glFT.glColor4f(1, 0, 1, 0.5) #mauve
OK, it’s not that pretty but it works… :baffed:
I hope that this tutorial will shed some light on a few areas of the Python Maya API... Note however that I didn't choose the easiest thing to start with (if you know nothing about the API... Otherwise, it's quite easy I think.) But with this, you've already got a first toolkit that will enable you to take your first steps... :sourit:
Don’t hesitate to ask if you have questions on this tutorial (if the examples lack clarity or you have trouble making them work…)
See you soon!
Dorian
PS: As you could see, I'm not english so if I'd made redundant errors, don't hesitate to notice me! :sourit:
Commentaires
Thanks for the great info, sorry I don't parle francais.
One your final plugin script I had to add:
import maya.OpenMayaUI as OpenMayaUI
Cheers,
Shawn
Oups! Exactly! o_O
I change this quickly!
Thanks for feedbacks! :)
EDIT: That's done!
Hi Dorian,
Do you have any idea how you could pass arguments to the node initializer? Example,the user can define colors or transparency on creaion.
Cheers,
Shawn
No you can't. When you register the plugin. No argument is passed.
When you create the node with "createNode myCustomLocator", you can't give argument.
Anyway, you can create attributes in your node and modify this with MEL, is possible, easy AND recommended! :)
To do that, I think the best way is to see how this is done in the scripted plugin there:
C:\Program Files\Autodesk\Maya2010\devkit\plug-ins\scripted\sineNode.py
You have a good exemple from how to use attributes. After, it's only a "if else" statement question! ;)
I hope I've been clear enough. ^^
I too would like to allow the user to set the color of a locator. I have added r,g,b attributes to the node, but can't figure out how to plug those into the draw function. The example you suggest, "sineNode.py", uses only the compute function.
Can you comment on the difference between the compute and the draw function? Do you have any simple examples of updating the draw function based on user inputs?
Thanks so much
Hi! :)
MPxLocatorNode is a "child" of MPxNode, so it can have a "compute" method.
I suppose the best way is to use the compute method to store the value of your color attribute in a "class variable" of your MPxLocatorNode class and to access this value in your draw method.
I hope this can help because I'd never try that but this should work.
Good luck!
Dorian
Wouah! Tres Tres bon article que tu nous a ecris la. A quand la suite ?
La version française est ici:
http://www.fevrierdorian.com/blog/p...
:)
Merci pour ces excellents tutoriaux.
Je me demandais si, maintenant que maya 2011 est sorti, de nouveaux tutoriaux simples, orientés API, Python, Mel, PyMel, PyQt allaient voir le jour. Juste histoire de donner une vue d'ensemble des nouvelles fonctionnalités ...
R.
Hello "Un passant"
En effet, Maya 2011 amène pas mal de choses d'un point de vu scripting.
Je ne m'y suis pas penché pour l'instant mais je compte le faire dès que j'en aurai l'occasion (ça peut vouloir dire jamais si la-dite occasion ne se présente pas...).
J'aimerai tenter des GUI avec PyQT dans Maya, voir ce que ça peut donner...
PyMEL est assez enfantin à utiliser pour qui connait un peu le python et le MEL... Python for Maya, c'est vraiment pas terrible, sauf pour les API si on n'a pas besoin de beaucoup de vitesse.
Voici le tuto qui m'a permis d'apprendre Python de zéro (je connaissais le MEL):
http://python.developpez.com/cours/...
Pour le MEL:
http://n.pastrana.free.fr/mel/cours...
Merci pour ses réponses. :)
R.
"ces" pas "ses". -_-
Hey - I'd love to get ahold of you to find out how much you'd charge for a few custom locators. Pop me an email if your interested. I need a custom spotlight locator and a locator for gobos or reflection cards.
Thanks,
Tom
Awesome!! It's a real detailed explanation, congratulations and thanks for sharing!
hello, thank you very much for introducing me to the Maya API, now i am making cool locators with editable attributes.
now my problem is transparency. it has the same problem as your locator.
when an object is created after the locator, it will be having problems showing through the transparency. however, if an object is created before the locator, it has no problem.
another problem is that Maya lists the locators as "unknown", is there a way to fix this so that we can write it in ASCII and also remove the annoying warning messages? thank you very much
Mmmh... Maybe a screenshot could help me. I don't know what you mean by transparency problems. :)
The fact Maya list the locator as unknown is maybe you did'nt give it a name+id. But if you open the scene on a Maya which doesn't have you Python locator plugin, this is normal that it is write: "unknown".
That's why, sometime, peoples prefer made locators direclty with curves. This way your scene is not dependent of a external plugin.
Thank you, Dorian.
You can see the problem here:
http://forums.cgsociety.org/showthr...
The cone was created BEFORE the locators, and the sphere and box were created AFTER the locators. this also happen with other locators,too and also with joints.
nodeTypeId = OpenMaya.MTypeId(0x88088)
is this where I put the name ID?
I would like to thank you again, now I can put my python to better use and I can give my animators better controllers like what we use in 3dsMAX.
I was waiting for that python Maya API book from amazon, but this tutorial is OK. I will write my own tutorial so that I can help others,too. Thanks
The problem seems to be solved with:
No?
About that, my main source for locators was: http://nccastaff.bournemouth.ac.uk/jmacey/RobTheBloke/www/mayaapi.html#2
Maybe you could download the pack of 37 locators and see how OpenGL code work in it.
From what I know, Maya was not attended to use "surface" for his locators so I suppose rasterizer work differently than on Mesh. So if you find the solution, don't hesitate to get back, I'm interested about it. :)
Good luck!
http://www.youtube.com/watch?v=7WkM...
Hello, this is what I came out with. The openGL blending issue seems to be how openGL wants it to be, this may be due to the fact that this is an added node and not part of Maya's original classes. So it is still declared as an "unknown" node.
I was also reading the openGL docs and it stated that this has to do with the BSP and ordering. So objects created after the transparent objects will have trouble drawing when under the transparent object. This problem was fixed in my previous job, I dont know how but they did it. I shall share it as soon as I know how, I will ask. Thank you again.
MMh.. Very interesting. You made great locators here. Have you got a page with Python file so I could give your URL in my post? :)
About the transparency bug, I was suspecting it must have a link to the Maya scenegraph but it was something I didn't spend time on.
If you find the solution, don't hesitate to tell me! I will update my blog post to improve it! :)
Also, the OpenGL way I give is not efficient at all. Create many many locators and your performances will fall down a lot: Use glVertex() is deprecated and very heavy, even in C... So in Python... -_-
I have to find time to post another "OpenGL with Maya locators" post (why not using yours if they are avaible!) and Use the VBO objects which are (much) more efficient than "direct rendering": You prepare vertex positions and colors just at update of input attributes, push everything in the GPU memory. And in the draw() method, you just say to render what is already in the GPU memory. Much Much more efficient actually!
Hello,
I can email you the code with comments once I finished adding one more thing to the code, just so that the attributes are not messy and ugly.
The openGL part is inevitable, i will share to you the solution once I found out about it.
I have not yet tested this on a large scale so I dont know if it will slow, but so far, it seems OK.
Thanks a lot for this! :)
About OpenGL, the solution is really Vertex Buffer Object (VBO)! This is the most effective way. :D
http://playcontrol.net/ewing/jibber...
(This tutorial is OpenGL ES but it work without too much modification and the principle is the same).
that's great!!
any chance you do a example using a glDrawElements() call?
Hello Luiz,
As I said, I need to find time to write about this... :(
But I suppose this is not something very difficult to do. Just go in the link I gave, everything is here. :D