Modifier sa scène

Dans ce petit chapitre, nous allons créer des nœuds et voir comment on peut interagir avec eux. Vous apprendrez aussi à transformer la sortie MEL de Maya en commande Python. :)

Créer des nœuds avec des commandes dédiées

C’est bien joli, mais nous n’avons toujours pas créer de nœuds nous-même. :P

Il existe de très nombreuses façons de créer des nœuds dans Maya :

  • soit par une commande dédiée contenant ses propres arguments tel que polySphere(), qui s’occupe des connexions pour vous suivant les arguments choisis ;
  • soit implicitement, par une commande nécessitant des nœuds déjà présents dans votre scène telle que revolve(), qui crée une nouvelle géométrie depuis une courbe donnée et s’occupe elle aussi des connexions pour vous ;
  • soit explicitement via l’utilisation de la commande createNode() qui ne crée qu’un seul nœud et vous laisse vous occuper des connexions.

Je vais vous en présenter deux. :-°

spaceLocator()

Vous le savez sûrement mais les locators sont de petits objets qui s’affichent dans le viewport Maya :

Un locator.
Un locator.

Pour le créer, rien de plus simple :

mc.spaceLocator()

Vous pouvez lui donner un nom :

mc.spaceLocator(name="monLocator")

Il y a quelques autres arguments mais je ne rentre pas dans les détails. :-°

polySphere()

On l’a déjà utilisée celle-là, mais on va creuser un peu plus cette fois. ;)

Une sphère.
Une sphère.

Pour la créer, rien de plus simple :

mc.polySphere()
# Result: [u'pSphere1', u'polySphere1'] #

Vous remarquerez que cette commande renvoie deux variables : le nœud de transform et le nœud de polySphere. ^^

On peut aussi lui donner un rayon via l’argument radius  :

mc.polySphere(radius=5.0)
# Result: [u'pSphere2', u'polySphere2'] # 

Et une subdivision à notre goût via les arguments subdivisionsX et subdivisionsY:

mc.polySphere(subdivisionsX=10, subdivisionsY=5)
# Result: [u'pSphere3', u'polySphere3'] # 

Et bien sûr un nom via l’argument name:

mc.polySphere(name="mySphere")
# Result: [u'mySphere', u'polySphere4'] # 

Par défaut, polySphere() crée un graphe de nœuds pour vous :

L’argument constructionHistory=False permet de ne pas conserver ce que Maya appelle le construction history (historique de construction en français).

mc.polySphere(constructionHistory=False)
# Result: [u'pSphere1'] # 

Remarquez comment le second nœud n’est pas présent dans la liste. ;)

Ni dans le graphe.
Ni dans le graphe.

Encore une fois, regardez la documentation si vous souhaitez aller plus loin. :-°

Et les autres

Je ne vais pas toutes les faire, il y a en a plein d’autres, mais je vous invite à jeter un œil sur la documentation de chacune d’elle. Voici une liste non exhaustive, n’hésitez pas à fouiller dans l’index.

À cela vous pouvez ajouter les commandes curve(), group() (avec l’argument empty=True), curveOnSurface(), createRenderLayer(), etc.

En général, quand vous souhaitez créer un truc, il y a une commande pour ça. :D

edit et query sont sur un bateau…

Je vais vous présenter un concept important des commandes Maya. Il n’est pas particulièrement compliqué mais revient souvent (vous l’avez déjà utilisé pour les interfaces ;) ). Il vaut donc mieux s’y être familiarisé.

On va commencer fort. Exécutez-moi ça :

mc.file(new=True, force=True)
mc.polySphere(name='pSphere1')
print mc.polySphere('pSphere1', query=True, radius=True)
mc.polySphere('pSphere1', edit=True, radius=5)

Boum ! Comme ça, sans prévenir. :pirate:

La première ligne c’est juste la commande file() avec l’argument new pour partir d’une nouvelle scène et la commande force pour ne pas demander si vous voulez sauvegarder votre scène actuelle (la doc ici).

La seconde commande vous la connaissez déjà, elle crée une polySphere nommée pSphere1 :

Une polySphere.
Une polySphere.

Ce sont les deux dernières qui vont nous intéresser :

print mc.polySphere('pSphere1', query=True, radius=True)
mc.polySphere('pSphere1', edit=True, radius=5)

Quels sont ces arguments query et edit ? Ils n’apparaissent pas dans la documentation… :euh:

Vous commencez à avoir les bons réflexes, c’est bien. ^^

En effet, ce sont des arguments spéciaux.

Comme il va de soi que vous vous servez allègrement de la documentation (n’est-ce pas ? ;) ), vous avez sûrement remarqué les trois lettres C, Q et E (et parfois M mais on ne s’en occupe par pour l’instant) à droite des arguments :

Et comme vous êtes attentif, curieux et à toujours chercher plus loin, l’encart présent dans toutes les pages de la documentation, au-dessus des exemples Python ne vous aura pas échappé : :lol:

Ces lettres indiquent que l’argument auquel elles se réfèrent peut s’utiliser suivant le mode de commande.

Un mode de commande ? Mais c’est quoi ? o_O

Ne paniquez pas, je vous l’explique immédiatement. ;)

Les modes d’utilisation des commandes

Il existe trois modes de commande. Je vais utiliser la commande polySphere() avec l’argument radius car ce dernier permet une utilisation avec les trois modes.

Create (lettre C)

C’est le mode implicite, il est inutile de le préciser. Ainsi :

mc.polySphere(radius=5.0)
# Result: [u'pSphere1', u'polySphere1'] #

Exécute la commande polySphere() en mode create. C’est le mode que nous utilisons depuis le début. ;)

Query (lettre Q)

Ce mode s’active lorsqu’on ajoute l’argument query à la commande. Ce mode query (qui peut se traduire par requête ou question) permet de récupérer des informations sur la valeur d’un argument donné (ici, radius) :

mc.polySphere("pSphere1", query=True, radius=True)
# Result: 5.0 #

Oh ! La commande renvoie la valeur flottante 5.0. :waw:

C’est l’idée ! :D

Notez que l’argument radius n’est pas un chiffre mais un bool. Dans ce contexte, la commande peut se traduire par donne-moi la valeur de l’argument radius pour la sphère "pSphere1".

Le mode query ne peut renvoyer qu’une seule valeur et implique une certaine logique dans la question qu’on pose. Ainsi, ceci n’est pas possible :

mc.polySphere("pSphere1", query=True, radius=True, subdivisionsX=True)

On ne peut questionner qu’une valeur à la fois. :)

Edit (lettre E)

Comme vous le devinez surement l’argument edit permet d’éditer/de modifier un objet déjà existant. Je vous laisse deviner ce que fait cette commande ;) :

mc.polySphere("pSphere1", edit=True, radius=2.0)

Bien sur, elle modifie le radius de "pSphere1". :P

Quelle est la différence entre cette commande et mc.setAttr("polySphere1.radius", 2.0) ? :euh:

Déjà, le fait que, quand vous avez "pSphere1" dans une variable, il faut passer par des commandes subsidiaires (que nous verrons plus tard) pour récupérer "polySphere1", le nœud qui "génère" la géométrie de la sphère (ne croyez pas qu’il suffit de remplacer la lettre p par poly pour que ça marche à tous les coups :P ).

edit est fait pour vous éviter ce genre de manipulations et fait souvent des choses que vous pourriez faire autrement, mais en plus de lignes. Je vous accorde que dans le cas de polySphere() l’économie n’est pas si évidente, mais pour certaines commandes vous verrez que c’est quand même plus simple. :)

Au-delà de ce simple exemple, certaines commandes sont vraiment pensées pour être utilisées avec query et edit (je pense notamment à xform() qui a son chapitre dédié ;) ) et nous avons également vu que les interfaces nécessitent obligatoirement de passer par query et edit.

createNode() pour créer des nœuds

La dernière commande du chapitre est la plus générique de toutes. C’est une de celle qu’on utilise le plus. ^^

Utilisation de base

Je vous ai montré comment créer des locators, des sphères, des torus, etc. Et bien ici je vais vous montrer comment créer un simple nœud ;) :

mc.createNode("transform")
# Result: u'transform1' # 

L’argument principal est le type de nœud qu’on souhaite créer. Ici, un nœud de type transform.

Mais passons vite à la suite !

Une histoire de sélection

Exécutez ceci (après l’avoir lu et compris bien entendu :-° , sinon retournez au chapitre concernant la commande ls() ;) ):

mc.select("persp")
print "selection avant:", mc.ls(selection=True)
mc.createNode("transform")
print "selection apres:", mc.ls(selection=True)

Le résultat de ce bout de code devrait vous afficher :

selection avant: [u'persp']
selection apres: [u'transform1']

Ah, en fait la commande createNode() change ma sélection ? :o

Exactement ! ^^

Comme vous l’avez sûrement remarqué, le nœud nouvellement créé (ici, transform1) est automatiquement sélectionné. Si vous ne souhaitez pas perdre votre sélection, il est possible d’empêcher la sélection automatique en utilisant l’argument skipSelect.

Maintenant voici le même code, mais en utilisant l’argument skipSelect :

mc.select("persp")
print "selection avant:", mc.ls(selection=True)
mc.createNode("transform", skipSelect=True)
print "selection apres:", mc.ls(selection=True)

Avec le résultat :

selection avant: [u'persp']
selection apres: [u'persp']

Magie ! La sélection n’est pas modifiée par la commande createNode()! :magicien:

Une année « parent »

Des jeux de mot qui tabassent quand la caisse claire fracasse. :soleil:

Viens l’argument parent qui spécifie un parent sur le nœud qu’on souhaite créer.

mc.createNode("transform", parent="transform1", name="monTransform")

Je vous ai mis l’argument name en bonus, car vous savez déjà à quoi il sert, pas vrai ? :P

Ici on vient de créer un nœud nommé monTransform en tant qu’enfant du nœud créé précédemment (transform1).

Mais qu’à cela ne tienne, j’ai envie de faire une boucle pas vous ? Comme ça, pour le fun :lol: :

current_parent = None
for i in range(10):
    current_parent = mc.createNode("transform", parent=current_parent, name="toto"+str(i))

Euh… Tu peux m’expliquer ? :euh:

Bien entendu ! :D

La première ligne initialise une variable (current_parent) contenant le nom du parent (pour l’instant, None).

Puis viens une boucle qui va s’exécuter dix fois, de 0 à 9, via la fonction range() (que vous avez sûrement dû voir lors de vos premiers tutoriels sur les boucles en Python) :

for i in range(10):
    current_parent = mc.createNode("transform", parent=current_parent, name="toto"+str(i))

Comme vous vous doutez, l’argument name (à la fin de l’appel de la fonction) va juste générer un nouveau nom à chaque itération : toto0, toto1, toto2, etc. Rien de bien compliqué pour qui connaît son chapitre sur les boucles en Python. :P

Rappel : Une itération c’est un roulement de boucle. Chaque fois que la boucle est exécutée, on dit qu’une itération a été effectuée. Il s’agit juste de terminologie, mais il est plus simple de dire une itération plutôt qu’un roulement de boucle.

Ce qui nous intéresse c’est, bien sûr, l’argument parent.

La première fois que la boucle va s’exécuter (la première itération donc), l’argument parent sera mis à None (car current_parent est à None) ce qui veut dire que c’est comme si nous ne passions pas l’argument parent. Ainsi :

mc.createNode("transform", parent=None)  # argument parent a None

Est équivalent à :

mc.createNode("transform")  # pas d'argument parent 

La première itération va donc créer le premier nœud (toto0) sans parent (ce qui a pour effet de créer le nœud à la racine) et modifier la variable current_parent qui passe de None à toto0.

À la seconde itération, le nouveau nœud (toto1) aura comme parent le nœud de l’itération précédente (à savoir, toto0), ce qui permet de fabriquer cette hiérarchie.

Si on « déroule » la boucle, ça donne la chose suivante :

mc.createNode("transform", parent=None, name="toto0")  # premiere iteration
mc.createNode("transform", parent="toto0", name="toto1")  # seconde iteration
mc.createNode("transform", parent="toto1", name="toto2")  # troisieme iteration
# etc.

J’espère que c’est plus clair. :)

Je crois que je comprends un peu mieux, mais ce n’est pas encore ça. :euh:

Ne vous inquiétez pas, il est normal de ne pas comprendre tout ce qu’on peut faire avec une boucle immédiatement. C’est à force de manipuler et d’en faire que ça deviendra une seconde nature.

Dans tous les cas, je vous invite à repasser quelques minutes sur cette boucle. Elle est simple (et un peu inutile je vous le concède) et le but de tout scripter c’est pour justement faire des boucles et automatiser son travail. :P

Utilisez des print partout pour réussir à comprendre ce qui se passe. Par exemple, le script suivant fait exactement la même chose que le script original, j’ai juste ajouté des commandes print pour disséquer ce qui se passe quand la boucle s’exécute :

current_parent = None
print "current_parent", current_parent
for i in range(10):
    print "=== iteration", i, "==="
    print "createNode with parent =", current_parent
    current_parent = mc.createNode("transform", parent=current_parent, name="toto"+str(i))
    print "next parent will be", current_parent

Ce qui nous donne :

current_parent None
=== iteration 0 ===
createNode with parent = None
next parent will be toto0
=== iteration 1 ===
createNode with parent = toto0
next parent will be toto1
=== iteration 2 ===
createNode with parent = toto1
next parent will be toto2
=== iteration 3 ===
createNode with parent = toto2
next parent will be toto3
=== iteration 4 ===
createNode with parent = toto3
next parent will be toto4
=== iteration 5 ===
createNode with parent = toto4
next parent will be toto5
=== iteration 6 ===
createNode with parent = toto5
next parent will be toto6
=== iteration 7 ===
createNode with parent = toto6
next parent will be toto7
=== iteration 8 ===
createNode with parent = toto7
next parent will be toto8
=== iteration 9 ===
createNode with parent = toto8
next parent will be toto9

Essayez de comprendre ça, ce n’est pas du temps perdu croyez-moi. ;)

C’est tout pour la commande createNode(). Un gros morceau comme vous pouvez le voir. :D

Convertir une commande MEL en Python

Arrivé ici, vous devriez déjà avoir de quoi jouer un peu. Mais quoi de mieux pour finir un chapitre sur une ouverture ? ;)

Quand vous cliquez sur un objet, que vous le déplacez, que vous modifiez la valeur d’un attribut, que vous ouvrez un fichier, que vous créez un objet, bref, que vous travaillez, la sortie de votre Script Editor affiche les équivalents, en MEL, des commandes.

Si vous êtes un peu bricoleur, vous avez sûrement déjà dû copier-coller et exécuter ces petits blocs de code qui ressemblent à ça :

En version textuelle, ça donne ça :

polySphere -r 1 -sx 20 -sy 20 -ax 0 1 0 -cuv 2 -ch 1;
select -r pSphere1;
select -tgl pSphere2 ;
select -tgl pSphere3 ;
scale -r 0.848105 0.848105 0.848105 ;
rotate -r -os -fo -14.111463 -12.135095 6.09803 ;
move -r -0.52468 0.29218 0.242561 ;
select -cl  ;
select -r pSphere2 ;
setAttr "pSphere2.visibility" 0;

Comme je vous le dis juste au-dessus, ces commandes sont en MEL, mais elles sont en fait très facile à convertir en leur équivalent Python, et c’est ce que nous allons faire ensemble. ^^

Comparer les commandes MEL et leur équivalent Python

Je viens de reprendre les commandes du dessus et j’ai fait un tableau avec leur équivalent en Python.

J’aimerais que vous les regardiez attentivement pour que vous puissiez distinguer la « forme » et les similitudes qu’il peut y avoir entre les deux versions, l’objectif étant de comprendre comment passer de l’une à l’autre :

MEL Python
polySphere -r 1 -sx 20 -sy 20 -ax 0 1 0 -cuv 2 -ch 1 ; mc.polySphere(r=1, sx=20, sy=20, ax=[0, 1, 0], cuv=2, ch=1)
select -cl ; mc.select(cl=True)
select -r pSphere1 ; mc.select("pSphere1 ", r=True)
select -tgl pSphere2 ; mc.select("pSphere2", tgl=True)
move -r -0.52468 0.29218 0.242561 ; mc.move(-0.52468, 0.29218, 0.242561, r=True)
scale -r 0.848105 0.848105 0.848105 ; mc.scale(0.848105, 0.848105, 0.848105, r=True)
rotate -r -os -fo -14.111463 -12.135095 6.09803 ; mc.rotate(-14.111463, -12.135095, 6.09803, r=True, os=True, fo=True)
setAttr "pSphere2.visibility" 0; mc.setAttr("pSphere2.visibility", 0)

La première chose à noter est qu’il n’y a pas de point-virgule ; à la fin des commandes Python. C’est une des particularités du langage : ses règles de portées (scoping Rules en anglais) basées sur l’organisation du code (tabulations, :, espaces, retours à la ligne, etc.) font qu’il n’est pas nécessaire de faire une séparation entre les commandes. :soleil:

La seconde particularité, c’est que les -quelqueChose en MEL (-cl, -tgl, -r, etc.) deviennent des quelqueChose=True en Python (cl=True, tgl=True, r=True, etc.). C’est dû au fait que si vous passez un argument sans valeur, Python croit que c’est une variable. Ainsi, si vous essayez de convertir cette commande MEL :

uneCommande -quelqueChose

…en Python, sous la forme :

mc.uneCommande(quelqueChose)

Python s’attendra à ce que quelqueChose soit une variable, et non un argument. Il faut donc lui passer quelqueChose=True pour simuler la même chose que le -quelquesChose en MEL. :)

Ensuite viennent les chaînes de caractère. En MEL, pSphere1 ne peut pas être autre chose qu’une chaîne de caractère car une variable est toujours préfixée d’un $ ($maVariable est une variable, toto est une chaîne de caractère). Je ne rentre pas dans les détails, ce n’est pas le MEL qu’on cherche à apprendre ici ^^. En Python, une chaîne de caractère est forcément entre des guillemets (quote en anglais), à savoir : "pShere1" ou 'pShere1'.

Dernier point un peu plus subtil : la place de l’argument principal. Les commandes MEL tendent à mettre l’argument principal à la fin de la commande. On obtient donc le schéma suivant :

maCommand -argument1 -argument2 "node"

En Python, cela donnerait plutôt :

mc.maCommande("node", argument1=True, argument2=True)

Voyez-vous où "node" est placé ? ;)

C’est encore une fois dû à des différences entre MEL et Python ; le dernier prenant les arguments « non nommés » (ici, "node") en début de fonction et les arguments « nommés » (argument1 et argument2) ensuite.

La différence d’ordre est particulièrement visible sur la commande select (cf : tableau).

Idem pour les positions des commandes move, rotate et scale qui se retrouvent au début de la commande en Python.

Comment puis-je savoir où exactement placer l’argument ? :euh:

Encore et toujours, en comparant les exemples de la doc. :P

Alternez en cliquant sur le bouton MEL version et Python version (en haut à droite) et vous verrez immédiatement où se place l’argument.

D’une manière générale, c’est toujours l’opposé de la version MEL, mais il faut vous y faire, doc rule the world !

À votre tour !

Nettoyez l’historique de la sortie de votre Script Editor:

Nettoyez l’historique du Script Editor par le menu

Ou :

Nettoyez l’historique du Script Editor par l’icône

Faites File/Create Reference… et amenez un fichier Maya (n’importe lequel) en référence.

Suivant la scène en question, il est possible que la sortie du Script Editor vous affiche quelques erreurs.

Remontez l’historique pour chercher la commande qui a fait l’import en référence. Chez moi, j’obtiens ceci :

file -r -type "mayaAscii"  -ignoreVersion -gl -mergeNamespacesOnClash false -namespace "test" -options "v=0;p=17;f=0" "C:/Users/narann/Documents/maya/projects/default/scenes/test.ma";

Avec ce que je vous ai montré plus haut, vous devriez vous en sortir tout seul. Prenez votre temps, le but n’est pas que vous soyez rapide mais que vous assimiliez la méthode. :)

Rappelez-vous :

  • On trouve la commande (ici file) et on ajoute le module Maya devant (ce qui nous donne mc.file()).
  • On supprime le point virgule ; à la fin de la commande.
  • On passe l’argument principal en début de commande.
  • On remplace tous les arguments avec un tiret devant (comme -gl) par la forme en Python (gl=True).
  • Les arguments ayant des valeurs (-mergeNamespacesOnClash false par exemple) on les arrange en équivalent Python (mergeNamespacesOnClash=False).
  • On met des virgules entre chaque argument ,. ;)
  • Dans le doute, on va zieuter les exemples en fin de doc (et oui… toujours la doc. :P )
  • On convertit les arguments en version courte (-r par exemple) vers leur équivalent entier (-reference)

Bon, je vous ai quand même pas mal mâché le travail bande de feignasses. :-°

Quand vous avez fait ça, vous devriez pouvoir exécuter votre ligne de code dans un onglet Python et que ça fonctionne.

Avant de regarder la solution, si vous avez une erreur, lisez-la et modifiez votre commande en conséquence. Plus vous apprendrez à faire ce genre de conversion, plus vous serez à l’aise et aurez tendance à le faire instinctivement, à chaque fois que vous aurez des tâches répétitives à faire.

Et la solution, que vous ne devriez ouvrir qu’à titre de comparaison, une fois que votre commande fonctionne. ;)

mc.file("C:/Users/narann/Documents/maya/projects/default/scenes/test.ma", reference=True, type="mayaAscii", ignoreVersion=True, groupLocator=True, mergeNamespacesOnClash=False, namespace="test", options="v=0;p=17;f=0")

Vous savez maintenant comment importer un fichier en référence avec un namespace personnalisé. De quoi, je l’espère, automatiser pas mal de taches redondantes dans votre quotidien. :)


Voila, ce chapitre n’était peut-être pas le plus excitant, mais il a abordé les derniers concepts importants autour des commandes Maya. À partir de maintenant, nous allons aborder plus de commandes sous forme d’exemples pratiques.

L’aventure ne fait que commencer ! :pirate: