Licence CC BY-SA

Faire une simple interface

Publié :

Il est certes encore un peu tôt, mais nous allons voir comment faire de petites interfaces pour exécuter nos scripts. :D

Avant-propos

Avant de commencer ce court chapitre, il me semble important d’aborder certains points.

Nous n’avons vu que peu de commandes jusqu’ici, mais je sais que si vous êtes graphiste, un des premiers trucs que vous allez vouloir faire c’est une petite interface pour pouvoir utiliser vos scripts. ^^

Depuis ses origines, Maya permet d’utiliser les commandes pour faire des interfaces. Bien qu’il s’agisse d’une révolution à l’époque (cf. le premier chapitre : L’architecture) l’intégration de Python a pas mal changé la donne en permettant à n’importe quelle bibliothèque d’interface graphique compatible avec Python (et plus particulièrement Qt via ses binding Python respectifs PyQt et PySide) de s’exécuter depuis Maya. En pratique il était donc possible de se passer des commandes Maya pour faire des interfaces.

Mais pourquoi utiliser une bibliothèque externe si Maya dispose déjà de commandes pour ça ? :euh:

La réponse est simple : c’est plus facile de faire une interface simple avec les commandes Maya, mais c’est plus simple de faire une interface évoluée avec PyQt/PySide.

Euh… Tu peux développer ? o_O

PyQt et PySide nécessitent d’être familiarisé avec certains concepts un peu plus évolué de Python (programmation orientée objet). Il faut ajouter à ça que Qt n’est pas une petite bibliothèque et mériterait un tutoriel dédié. :-°

À l’inverse, les commandes Mayas d’interfaçage gardent la logique des autres commandes, que ce soit au niveau de la documentation ou de leur utilisation. L’idée est donc d’éviter de trop vous dépayser avec de nouveaux concepts, mais de faire de petites interfaces sans pousser trop loin, histoire d’avoir la classe à Dallas. :D

window() pour ouvrir des fenêtres

Avant de pouvoir mettre des boutons et des cases à cocher partout, il vous faut une fenêtre dans laquelle mettre tout ça. ^^

Exécutez ce code (extrait de… la doc bien entendu !):

window = mc.window("zds_window", title="Ma fenetre", widthHeight=(200, 55), backgroundColor=(0.925, 0.467, 0.047))
mc.columnLayout(adjustableColumn=True)
mc.button(label='Do Nothing')
mc.button(label='Close', command=('mc.deleteUI(\"'+window+'\", window=True)') )
mc.setParent('..')
mc.showWindow(window)
Fenêtre avec des boutons dans Maya.
Fenêtre avec des boutons dans Maya.

Oh ! Quand je clique sur Close la fenêtre se ferme, comment on peut faire ça ? o_O

Mais que vous allez vite ! :D

Les commandes qui vont nous intéresser sont window() et showWindow(). On va garder le reste pour plus tard sinon je vais vous perdre. ;)

La commande window()

Sur la première ligne, la commande window() crée une fenêtre et renvoie son nom interne (oui, parce que window ça veut dire fenêtre en français :p ).

Penchons-nous un peu sur les arguments :

  • Le premier, "zds_window", est le nom interne de la fenêtre. Notez que cet argument est optionnel, si vous ne le spécifiez pas, Maya va donner un nom interne automatiquement. Gardez également à l’esprit que si une fenêtre avec un nom similaire existe, le nom final de votre fenêtre sera suffixé d’un nombre, suivant les règles des noms Maya (et deviendra "zds_window1" par exemple).
  • L’argument title sert à donner un titre à votre fenêtre. :soleil:
  • L’argument widthHeight permet de donner la taille de la fenêtre sous la forme d’une list (ou un tuple) de deux valeurs. Notez que vous pouvez utiliser les arguments width et height séparément (width=200, height=55).
  • L’argument backgroundColor est mis à titre d’exemple, car on le retrouve dans énormément d’autres commandes d’interface. Il permet de définir une couleur en arrière-plan de ce que l’on crée.

Ajustement avec columnLayout()

Cette commande crée un agencement (un layout en anglais) qui agencera ses enfants (les boutons) sous la forme d’une seule colonne ajustable (l’argument adjustableColumn=True):

Avec `adjustableColumn=True`.
Avec `adjustableColumn=True`.

N’hésitez pas à modifier et expérimenter avec cette commande pour voir les différents effets possibles. Voire encore mieux, tester les exemples de la doc. ;)

Avec `adjustableColumn=False`
Avec `adjustableColumn=False`

setParent('..') ?

Je ne vais pas rentrer dans les détails de cette commande pour l’instant, mais pour faire court :

  • Tout objet d’une interface nécessite un agencement parent.
  • Vous aurez remarqué que les deux commandes button() créent chacune un bouton dans l’agencement en colonne.
  • Ceci, car Maya définit un agencement implicite comme étant la dernière commande ayant généré un agencement. Ici, columnLayout().
  • Pour éviter que les prochaines commandes, que nous pourrions taper plus tard, aient notre fenêtre comme agencement implicite, setParent('..') « remonte » à la fenêtre principale de Maya.

La doc pour plus d’informations. ;)

showWindow() tout à la fin

Comme son nom l’indique, cette commande permet d’afficher la fenêtre qu’on lui donne en argument. En effet, jusqu’à présent, la fenêtre était créée mais pas affichée.

Mais… Mais… À quoi ça sert de faire une fenêtre si on ne l’affiche pas ? :euh:

Tout simplement à attendre qu’on y ait tout mis avant de l’afficher. ^^

Quand une fenêtre est affichée, à chaque fois qu’on y ajoute/modifie/supprime un élément de l’interface (bouton, case-à-cocher, agencement, etc.), les parties concernées par ces modifications doivent être rafraîchies.

Pour quatre boutons cela nous donnerait grosso-modo :

  • Création du bouton 1

    • Agencement du bouton 1
  • Création du bouton 2

    • Réagencement du bouton 1
    • Agencement du bouton 2
  • Création du bouton 3

    • Réagencement du bouton 1
    • Réagencement du bouton 2
    • Agencement du bouton 3
  • Création du bouton 4

    • Réagencement du bouton 1
    • Réagencement du bouton 2
    • Réagencement du bouton 3
    • Agencement du bouton 4

etc.

Le fait de n’afficher la fenêtre qu’une fois que tout est mis dedans permet d’ajuster le positionnement une seule fois, au dernier moment.

Prenez l’habitude de n’afficher vos fenêtres qu’une fois que tous les éléments y sont. ;)

Vérifier qu’une fenêtre existe

À l’heure actuelle, rien ne vous empêche de ré-exécuter le script et d’afficher une seconde fenêtre. Il vous faudra alors fermer la fenêtre précédemment ouverte pour éviter d’en avoir deux (trop dur la vie >_< ).

Une technique souvent utilisée pour s’assurer que la fenêtre principale de notre script n’est affichée qu’une seule fois consiste à vérifier qu’elle existe et à la supprimer avant de la recréer.

Pour cela, on utilise la commande window() en mode query avec l’argument exists=True pour savoir si elle existe :

mc.window("zds_window", query=True, exists=True)

Cet appel de commande pourrait se traduire par : « est-ce que la fenêtre "zds_window" existe ? ».

C’est quoi ce mode query ? :honte:

Nous y reviendrons. :) Le but ici c’est que questionner (query en anglais) l’existence de la fenêtre donnée, ici, "zds_window".

Pour supprimer un élément d’interface, il faut passer par la commande deleteUI() avec l’argument window=True pour bien spécifier que c’est une fenêtre qu’on cherche à supprimer :

mc.deleteUI("zds_window", window=True)

Cet appel de commande pourrait se traduire par « supprime la fenêtre nommée "zds_window" ».

Dans un code plus complet cela donnerait :

# supprime la fenetre si elle existe
if mc.window("zds_window", query=True, exists=True):
    mc.deleteUI("zds_window", window=True)

# cree ma nouvelle fenetre
window = mc.window("zds_window")
mc.showWindow(window)

Essayez d’exécuter ce code plusieurs fois, vous verrez qu’à chaque fois, la fenêtre se ferme avant d’être recréée. ;)

Notez que ceci implique que le nom que vous donnez à votre fenêtre (ici "zds_window") est unique pour ne pas entrer en collision avec d’autres noms déjà existants.

Mais comment je peux m’assurer d’avoir un nom unique ? :euh:

Il y a plusieurs méthodes, mais une approche couramment utilisée consiste à préfixer les noms de ses fenêtres (ici zds_ pour Zeste de Savoir).

Conclusion

On sait maintenant créer et manipuler les fenêtres, mais je vois bien que ce qui accapare votre attention depuis le début ce sont les boutons (allez, ne mentez pas ;) ).

Et bien c’est ce qu’on va attaquer. Préparez-vous ! :pirate:

La commande Benjamin… button()

Le jeu de mot qui se cache dans ce titre vous est offert par la guilde des codeurs fatigués. :soleil:

Reprenons le code précédent :

window = mc.window("zds_window")
mc.columnLayout(adjustableColumn=True)
mc.button(label='Do Nothing')
mc.button(label='Close', command=('mc.deleteUI(\"'+window+'\", window=True)') )
mc.setParent('..')
mc.showWindow(window)
Fenêtre avec des boutons.
Fenêtre avec des boutons.

On sait maintenant à quoi servent toutes les commandes sauf… button() !

Cette commande permet de faire des boutons qui exécutent des scripts quand on clique dessus (vous l’avez deviné tout seul, vous êtes décidément très intelligent :p ).

Voici une liste non exhaustive des arguments qu’on utilise le plus souvent (pour la liste exhaustive, c’est la même, chan-son !).

Notez que les commandes d’interface sont très souvent utilisées en mode edit pour modifier leurs états (visibilité, grisé/non grisé, etc.) et ainsi donner un peu de dynamique aux comportements de l’interface.

  • label permet de définir le texte qui s’affichera sur le bouton (exemple : label="Mon Bouton").
  • enable active ou désactive le bouton pour déterminer si on peut cliquer dessus (exemple : enable=False).
  • height et width permettent de définir la taille du bouton (exemple : height=600). Par défaut, le bouton s’ajustera au layout qui le contient (ici columnLayout).
  • visible permet d’afficher ou de masquer le bouton (exemple : visible=False rend le bouton invisible).
  • backgroundColor comme vu pour la commande window(), cet argument permet de faire souffrir les yeux de vos collègues (essayez backgroundColor=(1.0, 0.0, 1.0) pour vous donner une idée >_< ).

L’expérimentation vous permettra de découvrir deux trois choses intéressantes, alors prenez quelques minutes et essayez-les tous !

Il en reste un dernier, le plus important. :diable:

L’argument command

Cet argument est particulier, car il y a plusieurs façons de l’utiliser, le but étant de définir ce qui va se passer quand on clique sur le bouton en question.

C’est l’argument qui est utilisé par le bouton Close et qui ferme la fenêtre ? :euh:

Exactement ! Reprenons la ligne en question :

mc.button(label='Close', command='mc.deleteUI(\"'+window+'\", window=True)')

C’est la première façon d’utiliser l’argument command : en lui passant une chaîne de caractère qu’il va évaluer comme un script python.

Ici, on utilise la variable window, contenant le nom interne de la fenêtre, pour générer une chaîne de caractère qui ne sera qu’un appel à une commande avec son argument :

print 'mc.deleteUI(\"'+window+'\", window=True)'
# mc.deleteUI("zds_window", window=True)

Du coup, cliquer sur le bouton revient à exécuter :

mc.deleteUI("zds_window", window=True)

Bien que facile à comprendre, je ne trouve pas cette approche très élégante. :-°

Les fonctions de rappel (callbacks en anglais)

En voilà un bien vilain mot :lol: . Ce mécanisme est très connu en programmation et permet de faire mille choses. Dans notre cas, il permet au bouton d’exécuter lui-même une fonction qu’on lui passe (en argument de command).

Utiliser une fonction de rappel, c’est passer une fonction qui sera appelée au moment où on clique sur le bouton.

Regardez le code suivant :

def close_callback(*args):
  print "Close!", args

window = mc.window(width=150)
mc.columnLayout(adjustableColumn=True)
mc.button(label="Close", command=close_callback)
mc.showWindow()

On définit une fonction, close_callback() qu’on passe comme une simple variable (sans l’appeler) en argument de command.

Quand on l’appelle, cela écrit Close ! :

Appel du callback
Appel du callback

Comme vous pouvez le voir, nous passons la fonction close_callback() en argument de command mais sans les (), de sorte de ne pas l’appeler. C’est bien la fonction que nous passons en argument, pas son résultat. :)

Le bouton appellera la fonction close_callback() autant de fois qu’on cliquera dessus.

Mais si je souhaite utiliser mc.deleteUI() pour faire un bouton Close comme précédemment, il faut que je puisse passer le nom de la fenêtre en argument non ? :euh:

Bien vu, on ne peut pas le faire avec une simple fonction de rappel, il faut aller plus loin, et c’est ce qu’on va faire ! ;)

functools.partial() à la rescousse !

Pour ceci, nous allons utiliser une fonction très intéressant de Python : functools.partial(). Cette fonction prend deux arguments :

  • La fonction à appeler.
  • Les arguments à passer à cette fonction.

Ça va être compliqué quelques minutes (pas plus, promis !), accrochez-vous. ;)

Prenons un exemple simple :

import functools

def print_something(something):
  print something

my_function = functools.partial(print_something, "toto")
my_function()
# toto

Ligne a ligne :

import functools

On importe le module functools :

def print_something(something):
  print something

On définit une fonction print_something qui ne fait qu’afficher l’argument something qu’on lui donne.

La partie intéressante arrive ici :

my_function = functools.partial(print_something, "toto")

On fabrique une fonction my_function. Cette fonction appellera la fonction print_something avec l’argument "toto".

La suite vous la connaissez :

my_function()
# toto

On appelle la fonction qui fait exactement ce qu’on lui a dit : appeler print_something avec l’argument "toto".

D’une certaine manière :

my_function()

revient à appeler :

print_something("toto")

Si vous souhaitez avoir plus d’informations sur functools.partial(), je vous invite à jeter un œil à la documentation officiel.

Vous vous en doutez, on va donc passer ça à notre argument command :

import functools

def close_callback(window, arg):
  print "Close!", window
  mc.deleteUI(window, window=True)

window = mc.window(width=150)
mc.columnLayout(adjustableColumn=True)
mc.button(label="Close", command=functools.partial(close_callback, window))
mc.showWindow()

Et ça marche ! :soleil:

Notre belle fenêtre.
Notre belle fenêtre.

Et quand on clique:

Tadaaa !
Tadaaa !

Quel est l’argument arg que nous passons après window ? o_O

Excellente question et je vais malheureusement devoir botter en touche. :-°

Maya envoie toujours cet argument et vous devez le mettre dans votre fonction ou vous aurez une erreur de ce genre :

# TypeError: close_callback() takes exactly 1 argument (2 given) # 

Maya vous explique qu’il a mis deux arguments dans la fonction mais que celle-ci ne s’attendait qu’à un seul. :(

Une convention, en Python, quand un argument ou une variable doit être présent mais que vous ne considérez pas l’utiliser est de lui donner la valeur _, comme ceci :

def close_callback(window, _):

Ainsi, vous n’aurez pas de message d’erreur et vous informerez le lecteur de votre code que cette variable ne vous intéresse pas. :D

Prenez quelques minutes pour bien comprendre ce mécanisme, le modifier et l’expérimenter. Il n’est pas propre à Python et vous servira pour d’autres choses. ;)

loadUI() pour les flemmards

Maya est fourni avec Qt Designer, un outil de construction d’interface graphique. Allez dans le dossier d’installation de Maya, dans le dossier bin et cherchez designer.exe.

Sous Windows il est placé ici :

C:\Program Files\Autodesk\Maya2016.5\bin\designer.exe
Chemin vers Qt Designer dans Maya.
Chemin vers Qt Designer dans Maya.
Qt Designer livré avec Maya.
Qt Designer livré avec Maya.

Ne me dis pas que… :euh:

Si, si, :P vous pouvez construire vos interfaces avec cet outil, les sauvegarder dans un fichier .ui et les charger dans Maya en pointant dessus via la commande loadUI :

import maya.cmds as mc

dialog = mc.loadUI(uiFile='C:/users/username/mydialog.ui')
mc.showWindow(dialog)

Mais, pourquoi ne pas l’avoir dit plus tôt ? :'(

Sinon vous n’auriez jamais continué plus loin. :P

Allez, ne soyez pas déçu, je suis sûr que vous avez appris des choses.

De plus, vous allez voir que nombre des choses vus dans les précédentes sections de ce chapitre sont nécessaires (agencement, fonctions de rappel, etc.). En effet, avec un tel outil, vous êtes dans du Qt pur ce qui n’est pas non plus quelque chose qu’on maîtrise en un rien de temps. :-°

Mais essayez, et vous devriez vite revenir avec pas mal d’interrogations.

J’ai créé une superbe interface que j’arrive à afficher dans Maya, mais je n’arrive pas à exécuter mes fonctions quand je clique sur les boutons. :'(

Nous y voilà ! :D

Connecter ses boutons a Maya en passant par ce mécanisme passe par un moyen détourné. Il existe plusieurs mécanismes, mais je vais vous proposer celui que je trouve le plus simple.

Connecter les interfaces de Qt Designer avec Maya

Je ne vais pas rentrer dans l’utilisation à proprement parler de Qt Designer (création des widgets, agencement, etc.), internet regorge d’information dédiée à ce sujet et je ne ferais que répéter. >_<

En revanche, nous allons voir comment connecter nos widgets avec Maya, ici, les boutons.

Commencez par créer un widget vide :

Cliquez-glissez-y un bouton :

Push Button.
Push Button.
Trop ultime ! :-°
Trop ultime ! :-°

Vous pouvez faire Ctrl+r pendant que vous travailler pour visualiser votre interface. ;)

Sauvez votre jolie interface puis ouvrez Maya et chargez-la :

Apple n’a qu’à bien se tenir ! :diable:
Apple n’a qu’à bien se tenir ! :diable:

Vous remarquerez que rien ne se passe quand vous cliquez sur le bouton.

En même temps, je n’ai rien fait pour qu’il se passe quelque chose donc bon. :-°

On fait le malin. :p

Bref, vous avez compris, on va essayer de connecter une fonction à notre bouton.

Le nom des widgets

Retournez dans Maya puis regardez bien l’Inspecteur d’objets :

Mais là qui voilà, l’*Inspecteur d’objet*
Mais là qui voilà, l’*Inspecteur d’objet*

Il vous affiche comment les choses sont organisées dans votre interface.

La colonne Classes affiche la classe Qt utilisée par le widget. C’est très pratique pour trouver de la documentation sur un objet qu’on souhaite modifier dans l’Éditeur de propriétés. Il suffit de taper le nom de la classe dans votre moteur de recherche favori. Je ne vais pas en parler plus que ça, juste pour vous dire que ces classes sont les noms des widgets que Qt utilise.

L’information qui nous intéresse, c’est le nom de chaque widget. Ici, nous avons Form et pushButton.

Exécutez ceci :

dialog = mc.loadUI(uiFile=r'C:\Users\vous\zds_qt_designer_001.ui')
print dialog

Comme vous pouvez le voir, on va afficher ce que renvoie la commande loadUI() :

Oh ! Il renvoie Form, comme le nom dans l’Inspecteur d’objet ! o_O

Exactement ! :D

Et si vous modifiez son nom dans Qt Designer, sauvez puis rechargez, vous verrez que ce nom sera utilisé.

Au même titre que pour la fenêtre, on peut vérifier l’existence d’un bouton.

Vérifions l’existence de… pushButton pour voir. ;)

mc.button('pushButton', query=True, exists=True)

La combinaison de l’argument query et exists vous rappellera sûrement ce qu’on a utilisé vérifier l’existence d’une fenêtre.

Exécutez cette commande avant de lancer votre fenêtre, elle devrait renvoyer False, puis chargez votre interface et ré-exécutez la. Cette fois ci, elle devrait renvoyer True.

Chez moi elle renvoie toujours True. :'(

Il est possible qu’une fenêtre soit toujours ouverte. Au pire, exécutez mc.deleteUI('Form', window=True) pour forcer Maya à supprimer la fenêtre. Si ça ne fonctionne toujours pas, redémarrez Maya.

Modifier notre bouton

Bon, il semblerait que nous ayons notre bouton d’accessible. Tiens, si nous lui demandions ce qu’il exécute quand on lui clique dessus. Pour cela nous allons lui demander quel est le contenu de son argument command :

print mc.button('pushButton', query=True, command=True)
# None

Sans surprise, il n’y a rien dans l’argument command. Comment puis-je le modifier pour qu’il exécute mon code ?

Avec l’argument edit bien sur ! :D

C’est quoi edit ? :honte:

Là où l’argument query permet de questionner un argument, l’argument edit permet de modifier (edit en anglais) des choses.

Je sais, je sais, je vous l’expliquerai en détails plus tard, promis. En attendant on va l’utiliser pour modifier l’argument command qui, à l’heure actuelle, est vide.

Chargez votre fenêtre si ce n’est déjà fait (sinon le bouton pushButton n’existera pas et la commande plantera) puis exécutez :

def my_function(*args):
    print "Hello!"

mc.button('pushButton', edit=True, command=my_function)

Ici, on définit une fonction my_function() qu’on passe via l’argument command de mc.button() en mode edit. On vient donc modifier le bouton pushButton existant.

À partir de maintenant, vous devriez commencer à comprendre un certain nombre de choses :

  • Le nom des objets est important. Je vous invite donc à préfixer tout vos objets avec zds_. ;)
  • Le chargement de l’interface, via la command loadUI() doit se faire avant la modification, via edit, de son contenu.

Exemple final

Je vous mets un exemple complet utilisant functools.partial() vu précédemment.

J’ai fait quelques modifications de nom dans ma superbe interface :

Préfixer ses objets, c’est important. :soleil:
Préfixer ses objets, c’est important. :soleil:

Notez que le bouton s’appelle maintenant zds_close_button.

import functools

def close_callback(window, *args):
  print "Close!", window
  mc.deleteUI(window, window=True)

# load ui
window = mc.loadUI(uiFile=r'C:\Users\vous\zds_qt_designer_001.ui')

# set callbacks
mc.button('zds_close_button', edit=True, command=functools.partial(close_callback, window))

mc.showWindow(window)

Et quand vous cliquez sur le bouton il affiche Close! zds_window, puis ferme la fenêtre.

Sous le capot

Nous sommes revenus à notre point de départ mais en utilisant Qt Designer. Mais je ne pouvais clore ce chapitre en expliquant un peu ce qui se passe quand on exécute la commande loadUI(). ^^

En fait, loadUI() lit le contenu de votre fichier .ui puis exécute les commandes d’interface Maya correspondantes (mc.window(), mc.button(), etc.). Il fait donc le travail pour vous ! :D

C’est également pour cette raison que vos éléments d’interface étant de vrais objets Maya, vous pouvez les interroger (via query) et les modifier (via edit), les supprimer (via mc.deleteUI()), etc., comme si vous les aviez créés vous-même.

Durant cette interprétation, Maya peut faire d’autres choses, je vous donne la page de la documentation officielle que je vous invite à lire si vous souhaitez allez plus loin. ;)

Conversion par loadUI()

Comme je sais que vous êtes très curieux, je me doute que vous avez déjà potassé la page de la documentation de la commande loadUI() pas vrai ? ;)

En fait… Euh…

Je m’en doutais. C’est par là ! :P

Si vous exécutez la commande loadUI() avec l’argument verbose, Maya affichera listera les conversions qu’il exécute. Dans le cas de notre simple bouton :

mc.loadUI(uiFile=r'C:\Users\vous\zds_qt_designer_001.ui', verbose=True)
# Creating a QPushButton named "zds_close_button". # 

Maya ne crée qu’un bouton via le widget Qt QPushButton.

Liste des widgets supportés

Si vous exécutez la commande loadUI() avec l’argument listTypes. Maya vous renverra la relation entre la classe Qt qu’il utilise et la commande Maya correspondante.

mc.loadUI(listTypes=True)
# Result: [u'CharacterizationTool:characterizationToolUICmd',
 u'QCheckBox:checkBox',
 u'QComboBox:optionMenu',
 u'QDialog:window',
 u'QLabel:text',
 u'QLineEdit:textField',
 u'QListWidget:textScrollList',
 u'QMainWindow:window',
 u'QMenu:menu',
 u'QProgressBar:progressBar',
 u'QPushButton:button',
 u'QRadioButton:radioButton',
 u'QSlider:intSlider',
 u'QTextEdit:scrollField',
 u'QWidget:control',
 u'TopLevelQWidget:window'] # 

À gauche, le nom de la classe Qt, à droite, le nom de la commande Maya. :)

Vous remarquerez rapidement que tous les widgets présents dans Qt Designer n’ont pas d’équivalent dans Maya. C’est le cas notamment de la très utile Spin Box. Nous verrons plus tard comment bricoler (on est plus sur de la spéléologie à ce stade) pour la faire fonctionner.

Voici la liste non exhaustive des équivalents Qt en commande Maya (La Classe Qt est le nom qui apparaît dans l’Inspecteur d’objet) :

Nom dans Qt Designer Nom de la classe Qt Nom de la commande Maya
Push Button (Section Buttons) QPushButton button
Radio Button (Section Buttons) QRadioButton radioButton
Check Box (Section Buttons) QCheckBox checkBox
Combo Box (Section Containers) QComboBox optionMenu
Line Edit (Section Input Widgets)QLineEdit textField
List View (Section Item Views) QListView textScrollList
Horizontal Slider (Section Input Widgets) QSlider intSlider
Vertical Slider (Section Input Widgets) QSlider intSlider
Progress Bar(Section Display Widgets) QProgressBar progressBar
Tab Widget (Section Containers) QTabWidget tabLayout

Sans oublier la fenêtre principale de classe QWindow et généré par la commande Maya window.

Les widgets qui n’ont pas d’équivalent en commande Maya sont toutefois modifiables via la commande control (documentation ici).

Récupérer la valeur d’un Spin Box dans Maya

Comme je vous disais, il n’existe pas d’équivalent à la classe QSpinBox sous forme de commande Maya.

Mais comme on est des graphistes, c’est pas ça qui va nous arrêter ! :pirate:

Exactement ! Comme tout graphiste qui se respecte, on va faire un truc sale sous le capot pour faire un truc cool en surface. :D

Faites une Spin Box et un Line Edit :

A quoi sert le Line Edit ? o_O

Comme on ne peut pas récupérer la valeur de la Spin Box directement, que diriez-vous de l’écrire dans un autre widget quand elle change puis de récupérer la valeur de cet autre widget ?

Mais comment écrire la valeur de la Spin Box dans le Line Edit ? :honte:

C’est ce que nous allons voir. ;)

Utiliser les signaux de Qt Designer

Qt dispose d’un mécanisme de signaux qu’il expose dans Qt Designer. Ce mécanisme permet, entre autre, d’exécuter des fonctions prédéfinies quand un widget fait que chose. En l’occurrence, nous allons faire en sorte que le Spin Box écrive sa valeur dans le Line Edit au moment où on la modifie.

Et tout ça, sans (trop) de code ! :D

Cliquez sur la seconde icône pour passer en mode Éditeur de signaux/slots :

Cliquez-glissez la Spin Box sur le Line Edit, ceci indique dans quel sens le signal que nous allons créer va s’exécuter :

Une fenêtre de configuration de connexion va s’ouvrir :

Connectez le signal valueChanged(QString) de la Spin Box au setText(QString) de la Line Edit puis validez.

Une magnifique représentation des signaux s’affichera dans votre interface :

Faites Ctrl+r pour tester votre interface :

Remarquez comment modifier la *Spin Box* modifie également votre *Line Edit*.
Remarquez comment modifier la *Spin Box* modifie également votre *Line Edit*.

Au démarrage, la Line Edit ne contient rien. :(

Bien vu, il vaut mieux lui mettre une valeur par défaut. Repassez en mode Éditeur de widget (première icône), double-cliquez sur le Line Edit, entrez 0 puis validez.

Retour dans Maya

Une fois cela fait, sauvez puis chargez votre interface dans Maya, jouez avec la Spin Box puis exécutez :

window = mc.loadUI(uiFile=r'C:\Users\vous\zds_qt_designer_002.ui')
mc.showWindow(window)

print mc.textField('lineEdit', query=True, text=True)

La valeur de votre Line Edit devrait s’afficher.

Cette commande questionne (via l’argument query) la valeur du texte (via l’argument text). La page de la documentation est ici.

Au passage, le type de la valeur renvoyée par la commande est, vous vous en doutez, str, ce qui veut dire que la valeur n’est pas un nombre (int ou float) mais un texte. Pour convertir, rien de plus simple :

result = int(mc.textField('lineEdit', query=True, text=True))

Ceci revient à faire int('3') ce que Python est parfaitement capable d’exécuter.

Suite et fin

Il nous suffit maintenant de cacher notre widget :

mc.textField('lineEdit', edit=True, visible=False)
spinbox maya
Jouer à cache-cache avec ses widgets, un vrai plaisir…

Vous pouvez modifier la Spin Box puis réessayer de récupérer sa valeur, vous verrez que vous aurez toujours la bonne. La Spin Box n’est pas supprimée, elle est simplement cachée. Mais elle est toujours là ! :D

Notez que du coup, on ne peut faire l’inverse. Ainsi, il est impossible de setter une valeur à la Spin Box dynamiquement. C’est dommage, je vous l’accorde, mais on a déjà pas mal retourné Maya pour pouvoir faire ça, il ne faut pas trop lui en demander. :(

Ajouter un menu à Maya

Sans forcément vouloir faire une fenêtre, il peut être intéressant d’ajouter ses propres menus à l’interface Maya.

Pour cela, nous allons devoir récupérer la fenêtre principale, puis lui ajouter des menus. :D

Tu veux dire qu’on va manipuler la fenêtre Maya comme si c’était la nôtre ? o_O

Exactement ! :soleil:

Récupérer la fenêtre principale de Maya

Comme je vous l’ai expliqué précédemment, l’interface de Maya est codée intégralement via ces commandes, du coup rien ne vous empêche de les manipuler comme vous le feriez avec vos propres fenêtres ! :pirate:

En fait, Maya stocke le nom de la fenêtre principale dans une variable MEL : $gMainWindow

Mais… Mais… C’est une variable MEL ça, comment je fais pour y accéder en Python ? :'(

Pas de panique ! :D

Il existe une solution :

import maya.mel

main_window = maya.mel.eval('$tmpVar=$gMainWindow')

La première ligne importe le module maya.mel qui permet d’exécuter du MEL en Python. L’usage est assez restreint mais dans notre cas, la fenêtre est stockée dans une variable en MEL, donc nous n’avons pas le choix.

La seconde ligne évalue (c’est-à-dire exécute) l’expression MEL $tmpVar=$gMainWindow et renvoie le résultat dans la variable main_window. Dans notre cas, cela nous donne le nom interne de la fenêtre globale de Maya (souvent, sa valeur est MayaWindow).

Faites-vous une petite fonction et stockez ça dans un coin, ça peut toujours servir. :p

import maya.mel

def get_maya_main_window():
    """Renvoit la fenetre principale de Maya.
    """
    return maya.mel.eval('$tmpVar=$gMainWindow')

Vous avez maintenant la fenêtre Maya à disposition et vous pouvez en faire ce que vous voulez (ça sent la bavure ! :pirate: ) :

maya_window = get_maya_main_window()
mc.window(maya_window, edit=True, widthHeight=(900, 777))

Comme vous l’aurez deviné, la dernière commande change la taille de votre fenêtre Maya. :magicien:

Super… Mais moi je veux ajouter un menu… :-°

C’est qu’on est pressé… :-°

Le code, brut de pomme

Tous les prétextes sont bons pour faire du cidre. Voici le code :

import maya.cmds as mc

# get main window menu
maya_window = get_maya_main_window()

# top menu
menu = mc.menu('Coucou!', parent=maya_window)

mc.menuItem(label="Another Manager", command="print 'another manager'", parent=menu)

# optionnal: add another entry in the menu with a function instead of a string
def do_something(arg):
    print "Do something"

mc.menuItem(label="Another Menu", command=do_something, parent=menu)

Alors ? On fait moins le malin pas vrai ? :p

OK, tu peux expliquer ? :'(

Avec joie ! C’est parti ! :zorro:

Notez que vous pouvez exécuter ce code ligne à ligne pour voir son résultat. Et je vous encourage à le faire. ;)

Et les explications

Les premières commandes ont déjà été expliquées plus haut, je ne reviens pas dessus.

# top menu
menu = mc.menu('Coucou!', parent=maya_window)

Cette commande créée un menu nommé Coucou! dans votre interface Maya.

mc.menuItem(label="Another Manager", command="print 'another manager'", parent=menu)

Celle-ci créée, comme son nom l’indique, un élément (item en anglais) du menu. Cet élément s’appellera Another Manager dans l’interface, aura pour parent le menu qu’on aura créé précédemment et exécutera le code print 'another manager' quand on cliquera dessus.

Notez la similitude avec la commande button() vu précédemment, notamment concernant l’argument command. ^^

def do_something(arg):
    print "Do something"

Ici, on définit une fonction qui va simplement afficher Do something dans votre script editor, mais vous pourriez mettre votre code qui ouvre une fenêtre avec vos outils dedans. ;)

mc.menuItem(label="Another Menu", command=do_something, parent=menu)

Et la dernière commande crée, encore menuItem, mais cette fois-ci, elle appellera la fonction do_something() quand on cliquera dessus.

Encore une fois, rappelez-vous les fonctions de rappel de l’argument command de la commande button(). :-°

custom menu maya
"Coucou !" Notre menu personnalisé.
custom menu maya
Et son résultat dans le Script Editor.

Et voilà ! :D

Encore une fois, je vous invite à tester les exemples en fin de documentation de menu et menuItem.


Nous savons maintenant comment créer de petites interfaces. Nous n’avons qu’effleuré ce qu’il était possible de faire, mais je vous invite à aller plus loin par vous-même et à exécuter les codes d’exemples en fin de chacune des pages de la documentation :

  • text, qui permet d’afficher de simples textes et de les aligner.
  • checkBox pour permettre à l’utilisateur de cocher/décocher des cases, et ainsi prendre en compte ses choix (via l’argument query dans l’exécution des scripts).
  • floatSlider pour afficher et récupérer des valeurs de l’utilisateur. Tapez « slider » dans la page de recherche pour trouver ses frères.
  • frameLayout pour organiser de grosses interfaces.
  • treeLister si on aime les nuits blanches aspirines.
  • progressWindow pour faire attendre les gens comme un pro. :soleil:
  • etc.

Gardez cependant à l’esprit l’existence de PySide intégré à Maya. Si vous commencez à passer plusieurs heures sur votre interface, alors peut-être est-il temps de franchir le pas et d’entamer un tutoriel dédié à PySide. :-°