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!

:longBar:
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:

C++
MFnPlugin fnPlugin(obj);
Python
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: .

:longBar:
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: ):

:longBar:
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.

:longBar:
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!

:longBar:
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.

:longBar:
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:

CustomLocatorNode001.7z

Load it (as a plug-in), and enter:

createNode myCustomLocator;

pythonLocLineFirstLocator.png

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!

pythonLocQuad.png

Paf le locator! :baffed:

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!

pythonLocQuadColor.png

Re-Paf le locator! :aupoil:

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.

:longBar:
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

pythonLocQuadColorAlpha.png

Et voila! Better no? ;)

We are coming to the last part: the selection states.

:longBar:
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:

pythonLocSelectionState.png

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

CustomLocatorNode006.7z

pythonLocSelection001.png

pythonLocSelection002.png

pythonLocSelection003.png

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:

:longBar:
Edit - Blog wich talk about this tutorial: