Au programme donc:

  • OpenGL (Vous voulez le dessiner comment votre locator? :hehe: )
  • Changement de couleur en fonction de l'état de sélection du locator.

C'est peu mais vous allez voir qu'on a de quoi faire!

:longBar:
Le scripting ça sert à rien, c'est pour les newbies!

Non! Si vous pensez ça c'est que vous n'avez rien compris. Les deux sont intimement liés. Si j'ai choisi l'exemple du locator ce n'est pas simplement parce que c'est "cool" mais aussi parce que c'est un exemple de chose qu'il n'est possible de faire qu'avec l'API...

Mais l'API ne fait pas tout! :cayMal:

Comme vous le savez surement déjà, l'interface de Maya est entièrement en MEL, ce qui permet à un scripteur de pouvoir rapidement créer une GUI pour ses scripts. Tous les softs ne propose pas cela, (qui à dis XSI?) et Maya est un des rares à pouvoir s'en vanter.

La plupart des appels aux fonctions Maya sont scriptés elles aussi. Ainsi, vous pouvez "batcher" vos opérations (quand vous avez 50 scènes à ouvrir pour changer la valeur d'un attribut, vous faites moins le malin! :hehe: )

Je ne peut que vous inviter à lire le post de mon mentor sur le sujet (On peut y voir l'équivalent d'un code MEL en C++)

Dernière chose: Python, c'est du script! Même si il peut être "précompilé" par l'interpréteur, ça reste du script.

Cela dis, je pense que ce débat est un peu stérile car même si on "script" en utilisant le binding Python de l'API Maya, une fois les fondamentaux compris on se rend compte que, entre un code C++ et son équivalent en Python, la différence de syntaxe est minime.

Petit exemple de déclaration de variable:

C++
MFnPlugin fnPlugin(obj);
Python
fnPlugin = OpenMayaMPx.MFnPlugin(obj)

Le C++ reste plus propre à l'utilisation je trouve (je ne dis pas ça en rapport avec l'exemple mais plus de manière général), c'est logique d'une certaine façon vue qu'il s'agit de l'implémentation historique ajouté au fait qu'il reste un langage assez proche de la machine.

Après, tout dépend si vous voulez obtenir de la vitesse. Un code compilé (C++) ira surement plus vite mais je suppose que si vous voulez aller vite, soit vous êtes nul et vous faite des codes pourris qui ne vous laisse pas d'autres solutions que de passer par un langage plus bas niveau pour l'accélérer, soit vous êtes un "vrai" développeur... Si c'est le cas, je vois pas ce que vous faites sur le blog d'un codeur du dimanche! (Moi! :aupoil: ).

Allé, un de ces quatre je me fait un bench entre mel, pythonForMel, C++ et pythonForMayaApi :sourit: .

:longBar:
Les bases du code

Avant de commencer à tripatouiller du locator, il faut le code de base. C'est partie!

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)

Bam! On fait moins le malin là! (Je faisais pas le malin non plus la première fois! :baffed: ).

Bon, en fait c'est pas très compliqué. Explication:

import sys
import maya.OpenMaya as OpenMaya
import maya.OpenMayaMPx as OpenMayaMPx
import maya.OpenMayaRender as OpenMayaRender

Ici nous importons les principaux modules. Le premier ("sys") est le module système qui va nous servir à écrire les messages d'erreur d'initialisation du plugins (c'est tout).

Les trois suivants sont les modules Python de l'API Maya. Si ils ont été scindés en plusieurs modules de manière à clarifier leur utilisation:

  • OpenMaya est le module des classes de définition des nodes et des commandes, et "d'assemblage" de ceux ci en plugins.
  • OpenMayaMPx est un module spécifique à Python. Il contient les bindings des proxy (ou classe MPx) de Maya.
  • OpenMayaRender est le module des classes relatives au rendu (hardware ou software).
nodeTypeName = "myCustomLocator"

Cette variable (je la déclare au début) servira à donner un nom au type de node que l'on souhaite créer.

nodeTypeId = OpenMaya.MTypeId(0x87079)

Cette variable (je la déclare au début également) servira à donner un ID (identifiant) à notre type de node.

glRenderer = OpenMayaRender.MHardwareRenderer.theRenderer()

Là ça ce complique (surtout pour moi). Cette commande n'est pas référencé par la documentation (elle apparait pourtant dans des codes C++ d'exemple). Elle permet de récupérer un pointeur vers la classe chargé du rendu (hardware) des viewports. C'est grâce à elle qu'on va pouvoir "dessiner" notre locator (à l'aide de commandes OpenGL).

glFT = glRenderer.glFunctionTable()

La méthode glFunctionTable() renvois un pointeur vers un objet contenant toute les instruction OpenGL géré par Maya.

Alors là, on tombe en pleins dans l'OpenGL (enfin pas tout à fait, vous allez voir :sourit: ):

:longBar:
OpenGL, quelques information

OpenGL c'est quoi? (Wiki) Pour résumé, c'est un moyen de communiquer avec la carte graphique. Il s'agit de commandes simples pour (par exemple) afficher des points, des lignes, des triangles, des quad, des textures, etc...

Avant de continuer, nous allons devoir comprendre comment tout cela fonctionne. Si vous voulez "dessiner" des locators, il faut que vous sachiez utiliser le stylo qui va pouvoir le faire.

Comme un code vaut mieux qu'un long discourt, voici comment "dessiner" une ligne en OpenGL (Ceci est un code C. Pour Maya, nous allons voir qu'il y a deux trois choses à savoir avant):

glBegin(GL_LINES)
glVertex3f(0.0, -0.5, 0.0)
glVertex3f(0.0, 0.5, 0.0)
glEnd()

Cette technique est la technique "de base" dite: de "dessin immédiat".

glBegin(GL_LINES)

Cette commande permet de "passer en mode dessin de primitive" (Dans un espace 3D). On pose le crayon en quelque sorte. L'argument (GL_LINES) permet de dire comment vont être interprété les commandes suivantes, ici, en ligne. Tous les deux vertices, on écrit une autre ligne. Une ligne par paire de vertice.

glVertex3f(0.0, -0.5, 0.0)
glVertex3f(0.0, 0.5, 0.0)

Ces deux commandes sont les commandes qui "place" les vertices dans l'espace. La commande est glVertex, le numero est le nombre d'argument (ici trois: XYZ), et la dernière lettre est le type des données (ici "f" pour float).

En gros, nous plaçons deux vertices dans l'espace. Et vu que nous nous somme mis en mode "dessin de ligne" (par pair de vertice), nous venons de dessiner une ligne.

glEnd()

Et bien c'est une manière de "fermer la communication" avec la carte graphique et de retourner au programme principal.

Vous voyez ce n'est pas si dur que ça OpenGL! C'est aussi comme ça que j'ai compris que c'est le processeur qui calcule toute les positions des vertices à chaque image (Bon, c'est l'ancienne méthode ça, il y en à d'autres, notamment pour les objets statiques qui eux, sont stocké dans la mémoire de la carte graphique et appelé par une seul instruction. Cela dis, une grosse partie du travail est fait par le processeur).

Avec ça, vous pouvez presque dessiner votre propre locator. Il me reste un dernier point à aborder.

:longBar:
OpenGL version Maya!

En effet, le code que je vous ai montré plus haut est un code C. Je vais maintenant vous expliquer les quelques subtilités OpenGL propre à Maya.

Maya, dans un soucis d'interopérabilité (je suppose qu'il y a d'autres raisons), dispose de sa propre implémentation OpenGL. lorsque on souhaite dessiner dans le viewport, nous n'utilisons donc pas directement l'implémentation de Windows ou Mac mais bien celle de Maya (On peut le faire en C++, cela permet de s'affranchir du wrapper de Maya mais vous devez gérer l'interopérabilité dans votre code. En Python, on doit pouvoir le faire aussi mais je n'ai pas essayé). Cela ne change pas grand chose dans le code mais les constantes (GL_LINES par exemple) ont un autre nom.

Comme dis dans la documentation de l'API de Maya:

The naming convention used in this file is to take the regular GL* definitions and add a "M" prefix

Traduisez: "On à mis un "M" devant toute les constantes OpenGL. :seSentCon:

Donc, Maya ne comprendra pas GL_LINES mais OpenMayaRender.MGL_LINES :baffed:

Et voila comment on écrira une ligne en OpenGL dans Maya:

glFT.glBegin(OpenMayaRender.MGL_LINES)
glFT.glVertex3f(0.0, -0.5, 0.0)
glFT.glVertex3f(0.0, 0.5, 0.0)
glFT.glEnd()

Pas de grandes différences donc ^^. Un habitué d'OpenGL fera ça les doigts dans le nez!

:longBar:
On l'écrit ce locator?

Non! Pas encore! Il y a encore des choses à savoir! Après cette petite introduction à OpenGL, revenons à notre script Python.

Pour pouvoir dessiner le locator, il faut dériver la méthode "draw" (oui parce que Maya vous permet de dériver des méthode... Il y en a qui ne peuvent pas en dire autant...XSI? :siffle: ). Cette méthode est utilisé pour... dessiner des géométries personnalisés en utilisant les fonction OpenGL.

Voici le code minimal de la méthode "draw":

def draw(self, view, path, style, status):
 
	view.beginGL()
 
	view.endGL()

Les fonctions beginGL() et endGL() permettent (enfin ça c'est mon interprétation) de dire à Maya que nous allons utiliser, entre celles ci, des commandes OpenGL. D'après la documentation, si elles ne sont pas placés, le programme peut déborder de sa mémoire et donc, planter.

Je vais volontairement oublier quelques fonctions pour l'instant, nous les verrons plus tard.

:longBar:
Go pawaaaa!

Notre code donc pour l'instant ceci:

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)

Vous pouvez le télécharger ici:

CustomLocatorNode001.7z

Chargez le (comme un plugins), tapez:

createNode myCustomLocator;

pythonLocLineFirstLocator.png

Nous venons de créer notre premier locator! :sourit: Avec ça on peut déja faire deux trois choses... En s'enflammant un peu, ça peut donner ça:

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

Vous aurez un locator fou! ^^

Bon, c'est bien beau de faire des lignes mais il n'y a pas que ça... Je vous propose de faire... Un 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()

Le code: CustomLocatorNode003.7z

Paf le code!

pythonLocQuad.png

Paf le locator! :baffed:

Bon, ça flash un peu mais on va changer ça. :reflechi: Nous allons changer la couleur et ajouter de la transparence.

D'abord, la couleur. Par défaut, Maya utilise les couleurs de l'interface (bleu quand non sélectionné, vert quand sélectionné, rose quand "templaté" etc...). Pour changer la couleur, il suffit d'ajouter la commande 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()

Le code: CustomLocatorNode004.7z

Re-Paf le code!

pythonLocQuadColor.png

Re-Paf le locator! :aupoil:

Toujours aussi flash hein? Vous remarquerez qu'il n'y a que la ligne qui change de couleur en fonction de l'état de sélection. Pour "aérer" un peu notre locator, nous allons ajouter de la transparence à notre quad.

:longBar:
L'Alpha

Nous allons "activer" une des capacités d'OpenGL: GL_BLEND (Ne pas oublier de la désactiver à la fin):

#On active la capacitée
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)	#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()
 
#Ne pas oublier de la désactiver à la fin
glFT.glDisable(OpenMayaRender.MGL_BLEND)

Quand cette capacité est activé, OpenGL mélange la couleur donné par la couleur résultante (le fond) suivant un dernier facteur (l'alpha). Si vous lancez le code telle quelle, vous ne verriez pas de changement. En effet, nous devons donc ajouter un dernier composant à notre couleur:

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)	#on change la couleur et on ajoute l'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! C'est déjà mieux ;)

Nous arrivons à la dernière partie: Les états de sélection.

:longBar:
La Selection

En effet nous pouvons changer des choses dans notre locator suivant l'état de selection de celui ci. Ici, nous allons changer... La couleur! (mega original...).

Tout d'abords, il y a plusieurs "états" d'un objets "dessiné" (Énuméré dans M3dView::DisplayStatus). Je vais vous donner les deux principaux, ceux qu'on va utiliser:

  • kActive -> Les objets actifs (sélectionnés). (Attention, trompeur!)
  • kLead -> Le dernier objets sélectionné.
  • kDormant -> Les objets Inactifs.

Alors, expliquons la subtilité:

Dans Maya, quand vous sélectionnez un objet, il devient vert. Et quand vous ajoutez un autre objet à la sélection, ce dernier deviens vert et l'ancien: Blanc. En fait l'objet vert est "en DisplayStatus" kLead et l'objet blanc, en kActive. Une image pour expliquer (rappeler?) le principe:

pythonLocSelectionState.png

Nous allons donc pouvoir changer la couleur en fonction de l'état de sélection.

Comme vu plus haut, la méthode dérivé est draw, et l'argument qui nous sert à connaitre l'état du locator est "status". Et pour utiliser tout ça, rien de plus simple:

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

Bon, ok, c'est moche mais ça marche... :baffed:

En espérant que ce tutorial vous éclairera sur quelques points de l'API Maya en Python... Sachez cependant que je n'ai pas choisi le plus simple pour commencer (si on ne connait rien à l'API... Sinon, c'est assé facile je pense). Mais avec ça, on a déjà un petit bagage qui vous permet de faire vos premiers pas... :sourit:

N'hésitez pas si vous avez des questions sur ce billet, (si les exemple manque de clarté ou que vous avez du mal à les faire fonctionner)

A bientôt!

Dorian