Apprendre à coder par Maya

On pourrait citer une multitude de ressources sur l’apprentissage du code, mais chacun ne traite que du code et purement du code. Ici, c’est votre travail de graphiste qui vous montrera comment, de vos actions, vous pourrez les transformer en code.
Le but, ici, est de vous montrer :

  • comment débuter facilement
  • que coder ne veut pas dire « doctorat en mathématiques »
  • des concepts basiques de la programmation
  • que tout ça n’est pas sorcier
  • découvrir le MEL
  • le transformer en Python

Configurer maya

Peu de choses, le script editor se trouve ici:

Activez l’affichage du Stack trace

Vous n’avez rien besoin de plus.

Premiers pas – MEL

Dans cette partie, nous traiterons de comment commencer à scripter en MEL à partir de vos actions en tant que graphiste.

Actions

Créons une première scène basique:

  • un cube
  • on le groupe
  • on renomme le cube et le groupe

Regardez ce qui est écrit dans le script editor, ça ressemble très fortement aux actions que vous avez entreprises, mais aussi à la liste d’actions précédente!

polyCube -w 1 -h 1 -d 1 -sx 1 -sy 1 -sz 1 -ax 0 1 0 -cuv 4 -ch 1;
// Result: pCube1 polyCube1 // 
doGroup 0 1 1;
select -r group1 ;
rename "group1" "papa";
// Result: papa // 
select -r pCube1 ;
rename "pCube1" "jouet";
// Result: jouet // 

Ceci est un enchainement de fonctions MEL, vous pouvez commander Maya grâce à ça.
Les // définissent un commentaire, les lignes à droite ne seront pas interprétées par Maya lors de l’exécution.
On va s’amuser un peu:

  • faites une nouvelle scène (une nouvelle commande apparait: file -f -new;)
  • copiez/collez le script suivant dans un onglet MEL du script editor
  • exécutez (ctrl+a pour tout sélectionner, ctrl+entrée pour exécuter)
polyCube -w 1 -h 1 -d 1 -sx 1 -sy 1 -sz 1 -ax 0 1 0 -cuv 4 -ch 1;
doGroup 0 1 1;
select -r group1 ;
rename "group1" "papa";
select -r pCube1 ;
rename "pCube1" "jouet";

Vous devriez avoir exactement le même résulta qu’a la main.
Analysons maintenant la première ligne:
polyCube -w 1 -h 1 -d 1 -sx 1 -sy 1 -sz 1 -ax 0 1 0 -cuv 4 -ch 1;
La commande commence par le nom de la commande: polyCube, suivit de ce qu’on appelle les arguments (-w 1, …) et termine par un ;.
Les arguments servent à paramétrer la commande, si vous les changez, le résulta changera.
Essayez donc:
polyCube -w 2 -h .5 -d 1 -sx 10 -sy 1 -sz 1 -ax 1 1 1 -cuv 4 -ch 1;

Documentation

La partie la plus importante est la documentation, sans elle, vous êtes aveugles.
Heureusement, Maya à une documentation très simple et complète quant à ses commandes, elle se trouve ici.

Observez la documentation de notre polyCube.

Le premier détail à observer est le nom des paramètres. Le retour maya donne le nom court des paramètres. Aussi, le paramètre court -w est en réalité -width dont la description est Width of the cube. Default: 1.0.

Les paramètres ayant un Default sont optionnels.
Si vous avez cherchés la documentation des autres commandes, vous avez du vous casser les dents sur doGroup. En effet, ce n’est pas une commande maya à proprement parler mais une procédure. Pour le moment, ce que vous avez à retenir, c’est que les procédures sont un groupe de commandes encapsulées dans un fichier afin de pouvoir les réutiliser.

Précisions

doGroup

Quand un retour ne vous parait pas clair, vous pouvez demander à Maya de quoi il s’agit:

whatIs polyCube;
// Result: Command // 
whatIs doGroup;
// Result: Mel procedure found in: C:/Program Files/Autodesk/Maya2020/scripts/others/doGroup.mel // 

Il vous dit même où trouver la source de cette procédure!

Simplification

Je recommande, pour plus de lisibilité et de maintenabilité, d’utiliser le nom long des paramètres.
Nous pouvons réécrire notre liste d’action en fonction de ce qu’on connait déjà:

file -force -new;  // Comme ça on reset tout avant
polyCube -width 2 -height .5 -subdivisionsX 10 -axis 1 1 1;
group;  // La procedure execute la commande group
rename "group1" "papa";
rename "pCube1" "jouet";

Variables

Reprenons cette partie :

polyCube;
// Result: pCube1 polyCube1 //

Via les arguments, nous pouvons spécifier le nom du cube crée:

polyCube -n "uber_super_cube";
// Result: uber_super_cube1 polyCube3 //

De cette manière, la partie

rename "group1" "papa";
rename "pCube1" "jouet";

N’es plus valide, vu que pCube1 est maintenant uber_super_cube1.
De plus, vous avez remarqués que la ligne //Result: pCube1 polyCube1 est changée en // Result: uber_super_cube1 polyCube3.
Ca veut dire que la commande polyCube nous renvoie des valeurs, ici, deux mots correspondant à uber_super_cube1 et polyCube3. Nous pouvons stocker ce résulta en mémoire afin de le réutiliser.
C’est un des concepts fondamentaux de tout script/programme, la mise en mémoire d’une valeur pour la réutiliser, on les appelle variables.

On les déclare de cette manière:
$value = 10;
Le nom commence par un $, et on place un = entre le nom de la variable et sa valeur.
Pour afficher son contenu:
print($var);
Pour une commande, on doit rajouter ` au début et à la fin de la commande:

$var = `polyCube -w 2 -h .5 -d 1 -sx 10 -sy 1 -sz 1 -ax 1 1 1 -cuv 4 -ch 1`;
print($var);
pCube4
polyCube4

$var contient ce qu’on appelle une liste. Pour accéder à un de ces éléments, on spécifie son index entre [], l’index est la position de l’objet voulu et commence par 0:

print($var[0]);
pCube1

Nous pouvons simplifier/debugger notre script:

file -force -new;  // we clear the scene
$my_cube = `polyCube -width 2 -height .5 -subdivisionsX 10 -axis 1 1 1 -n "cube_msh"`;  // with a super cool name
$my_group = `group`;  // this returns the new group name
rename $my_group "papa";  // renames the group by papa
rename $my_cube[0] "jouet";  // renames the mesh by jouet

Type de données

Vous avez brièvement vu certains type de données: liste, mot, nombre, soyons maintenant un peu plus précis.

Il existe, en MEL plusieurs type de données dont:

  • les nombres: -10, 0, 5, …
  • les nombres décimaux: -10.102, 0.0, 5.6, …
  • les chaines de caractères: "cube", "jouet", …
  • les listes (ou array) qui peuvent contenir les types ci dessus: {“blahone”,“blahtwo”,“blahthree”}

Et quelques autres.

Conclusion

Vous pouvez donc très facilement, en MEL, le langage natif de script de maya, créer des scripts afin d’automatiser vos taches redondantes.

Voici quelques ressources utiles:
Documentation Commands Maya
# MEL How-To

Python

Vous pouvez très bien vous limiter au MEL pour scripter dans maya, je ne vais pas faire de débat dessus, et n’avancerais qu’une comparaison: coder en MEL c’est ne boire que de la 1664, s’ouvrir au Python, et potentiellement d’autres langages, c’est découvrir qu’il existe une multitude d’autres bières, du rhum, du vin, des cocktails…

MEL to Py

Vous avez vu qu’un commande MEL pouvait s’écrire de cette manière:
$var = `command -argument`;
L’équivalent python est:
var = command(argument)

En reprenant notre petit script MEL:

file -force -new;  // we clear the scene
$my_cube = `polyCube -width 2 -height .5 -subdivisionsX 10 -axis 1 1 1 -n "cube_msh"`;  // with a super cool name
$my_group = `group`;  // this returns the new group name
rename $my_group "papa";  // renames the group by papa
rename $my_cube[0] "jouet";  // renames the mesh by jouet

devient en python:

import maya.cmds as mc

mc.file(force=True, new=True)  # we clear the scene
my_cube = mc.polyCube(width=2, height=.5, subdivisionsX=10, axis=[1, 1, 1], name="cube_msh")  # with a super cool name
my_group = mc.group()  # this returns the new group name
mc.rename(my_group, "papa")  # renames the group by papa
mc.rename(my_cube[0], "jouet")  # renames the mesh by jouet

Voici les différences majeures constatées sur ce bout de code:

  • il n’y a pas de ;
  • import maya.cmds as mc
    python, n’étant pas un langage propre à maya mais un logiciel indépendant, celui ci à besoin de savoir où il doit piocher ses fonctions.
    Le concept de procédure n’existe plus.
    import maya.cmds permet d’exposer un groupe de fonctions utilisable par notre script,
    as mc permet de les utiliser en n’ayant besoin de préfixer les fonctions que par mc. au lieu de maya.cmds.
    En préfixant les fonctions (anciennement commandes en MEL), par mc., on permet à python d’utiliser les fonctions qu’on connait déjà.
    maya.cmds s’appelle un package.
  • mc.file(force=True, new=True)
    On spécifie à python: utilise cette fonction provenant de cette ressource (package): appelle la fonction file provenant du package mc.
    Les arguments sont mis entre parenthèse. Les arguments optionnels, dans notre cas, doivent spécifier une valeur: force=True.
  • my_group = mc.group()
    Dernière observation, en python, il n’y à pas de $ en début de variable ni de ` en début et fin de commande.

Type de données

En python, les types les plus utilisés sont:

  • int: -10, 0, 5, …
  • float
  • bool
  • string
  • les nombres (int): -10, 0, 5, …
  • les nombres décimaux (float): -10.102, 0.0, 5.6, …
  • les chaines de caractères (str): "cube", "jouet", …
  • les booleens (bool): True ou False

les conteneurs:

  • les listes (list): [-5, 6.3, "foo"]
    Contrairement au MEL, peuvent contenir de n’importe quel types
  • les relations clé/valeur (dict): {"keya": "valueA", "keyb": "valueb"}

Ressources

Quelques ressources pour aller plus loin.

openclassrooms
courspython

TP

Le plus dur est de trouver quoi faire, les exemples ne manquent pas, mais sans idées clair, on peut vite se perdre.
Pour aller au bout de ces exemples, une dernière notion très importante est la recherche.
Chaque script nous apprend quelque chose, il vous faudra chercher dans maya comment accomplir une tache, chercher sur le net si un concept de programmation n’a pas été abordé dans ce cour (tous en gros…), tel que les conditions, les boucles, … C’est comme ça que vous allez apprendre.
Dites vous que si vous ne trouvez pas un élément de réponse sur le net, c’est que vous avez mal formulé le problème.
Voici des exemples par département.

Modeling

Clean de scène avant d’envoyer au département suivant.

  • supprimer tous les objets préfixés par _trash_
  • supprimer toutes les références
  • vérifier que les nodes de type mesh sont suffixés par _msh
  • vérifier que les groupes sont suffixés par _grp
  • vérifier que les meshs sont conformes (pas de trous, pas de ngons, …)

Notions:
Listes, boucles, recherche dans les strings.

Rigging

Créer des manipulateurs le long d’une chaine de joint.
Pouvoir mettre à jour leur forme et couleur.

Notions:
Listes, boucles, attributs maya.

Shading

Créer un attribut qui défini un type de matériel par object/groupe.
Générer un shading network correspondant à ce matériel et l’assigner en fonction des attributs précédents.

Notions:
Listes, boucles, dictionnaires.

Layout

Générer une ville à base de cubes de démentions et couleurs aléatoires.

Notions:
Boucles, random.

Animation

Pour chaque object séléctionné, créer une animation en zig zag.

Notions:
Boucles, random

Lighting

Créer un dome light avec une HDR dans le dossier de projet maya.
Créer une animation pour une tournette.
La rendre.

Notions:
Gestion de chemin de fichier.

Conclusion

En espérant que ça vous permette de vous initier au script et surtout vous permette de ne plus perdre de temps sur vos taches répétitives.

Crossover python interpreter binaries

Note: ce billet concernent ceux qui utilisent plusieurs interpréteurs python différents et s’amusent à appeler les uns via les autres, voir un futur billet sur les services, webservices ou multi tasking.

En utilisant REZ et deadline, je suis tombé sur un problème épineux, tous mes jobs lançant une ligne de commande de type `rez env monPackages — python -m monScript` échouaient avec des erreurs imbitables.

Traceback (most recent call last):
   File "dblocal wrapper.py", line 4, in 
     import sqlite3
   File "c:\packages\sofwares\python\3.7\platform-windows\arch-AMD64\os-windows-10\Lib\sqlite3__init__.py", line 23, in 
     from sqlite3.dbapi2 import *
   File "c:\packages\sofwares\python\3.7\platform-windows\arch-AMD64\os-windows-10\Lib\sqlite3\dbapi2.py", line 27, in 
     from _sqlite3 import *
 ModuleNotFoundError: No module named '_sqlite3'

Bien entendu, cette erreur ne veut rien dire pour plusieurs raisons:

  • tout marche bien en temps normal
  • sqlite3 est un module livré avec python (2 et 3)
  • si je passe l’import de sqlite3, j’ai la même erreur sur d’autres modules compilés
  • j’ai des erreurs de syntaxe python 3

Les deux derniers points mettent donc la puce à l’oreille sur un problème cross interpréteur (ici, environnement python3 lancé via un python2).

Méthode:

  • print(sys.interpreter) pour vérifier que l’interpréteur est le bon, si c’est le cas, votre erreur n’est pas décrite dans ce billet. Et effectivement il va pointer vers `d:/mon_ptn_workspace/python.exe` au lieu de `d:/je_te_veux_ici/python.exe`
  • `print(os.getcwd()) et vérifier qu’il n’y a pas d’interpréteur dans ce dossier, et effectivement, dans mon cas, il pointait sur `d:/mon_ptn_workspace/` et donc, REZ allait chercher d’abord dans le workspace.

Le problème à donc été résolu en modifiant, au lancement du job (dans le plugin deadline), le directoire de travail courant.

[MAYA PYTHON API] Optimisation de l’utilisation du wrapper python

Dorian Fevrier a déjà écrit un article similaire sur l’utilisation des commandes maya versus l’API dans ce billet: Récupérer rapidement la position des vertices d’un mesh Maya
Pour résumer:
pSphere 50×50 smoothée 4 (soit 633602 vertices):
iteration xform: 39 sec (xform(« mesh.pt[n] »))
iteration API: 4 sec (MFnMesh.getPoints())
xform sans itération: 2 sec (xform(« mesh.pt[*] »)

Ici je vais parler d’une optimisation liée à l’utilisation d’un wrapper Python -> C++ (l’API python de maya quoi).

On est tombés, avec mon compère, vendredi dernier, en codant un algo de copie de poids de deformer indépendant d’une topologie, sur un petit souci de temps: récupération pour chaque vertex d’un mesh des vertex connectés: 45 secondes de process pour 1 000 000 de vertices… Après divers optimisations (suppression des boucles pour passer en récupération d’infos par pack puis traitement en pur python) nous sommes passés ) à une vingtaine de secondes… Mieux mais toujours énorme! Le process qui apparaissait comme long était le suivant:

it = MItMeshVertex(node.asMObject())
while not it.isDone():
    vtxList = MIntArray()
    it.getConnectedVertices(vtxList)
    related.append([vtxList[i] for i in xrange(vtxList.length())])
    it.next()

17.04 secondes pour 1002001 points

J’avais déjà été confronté à un problème de la sorte pour un wrapper purement Python, pour énormément d’objets, l’accès aux attributs pouvait être horriblement long… Idem pour la création de leurs instances! Donc que pouvait-on optimiser ici?
Sachant que la partie C++ remplit le tableau de points lui-même, il gère directement son traitement dans la sous couche C++, donc laissons le faire!

vtxList = MIntArray()
while not it.isDone():
    it.getConnectedVertices(vtxList)
    related.append([vtxList[i] for i in xrange(vtxList.length())])
    it.next()

Et bim! On passe à 8.15 secondes pour 1002001 points

Si on veut pousser mémé dans les orties et descendre à 6.60 secondes:

vtxList = MIntArray()
appd = related.append
size = vtxList.length
acces = vtxList.__getitem__
t = time.time()
while not it.isDone():
    it.getConnectedVertices(vtxList)
    appd([acces(i) for i in xrange(size())])
    it.next()

On pourrait utiliser map(acces, range(size())) qui ferait gagner un chouia, mais range supporte très mal les grands tableaux, donc on restera sur une liste compréhension et un xrange.

Edit:

Depuis peu, j’essaie d’utiliser au maximum l’API 2 qui s’avère bien plus rapide lors des post traitement des objets python (ex: conversion MIntArray -> list):

On descends à 3.20 secondes pour les mêmes nodes.

it = MItMeshVertex(ob)
while not it.isDone():
    vtxList = it.getConnectedVertices()
    related.append(list(vtxList))
    # related.append(vtxList)  # On descend à 1.09 sec
    it.next()

[Qt/Maya] Switch focus sur les widgets Qt

Qt dans maya, c’est merveilleux, niveau interface graphique, c’est le top, ici, je parle bien sur des bindings PySide/PyQt.

On regrettera juste le passage un peu tardif à Qt5 (et la suppression définitive de PySide, qui devrais arriver avec la 2016, si je me suis bien renseigné), si tant est que ça arrive un jour (je suis impatient d’utiliser le QML dans maya…).

Mais bref, ici, je vous expose un ***** de problème…

C’est bien, on a fait toutes nos toolboxes et outils de prods en Qt (normal), sauf que pour certains clampins, je ne sais pas pourquoi, mais quand ils font un ctrl + v ou une majuscule, le focus de la fenêtre Qt passe a un focus vers maya (et donc, avec certains raccourcis, tels que le ctrl + v, on a des catastrophes…)

Je ne sais toujours pas à quoi c’est du, et franchement, pas le temps de m’attarder là dessus… Mais le problème arrive quand on initialise la fenêtre devant le viewport 3D, avec un objet sélectionné via l’outliner (autant dire, souvent, l’air de rien).

Maintenant, la solution: Overridez la méthode keyPressEvent de votre fenêtre (du moins, c’est la seule solution que j’ai trouvé, j’éditerais ce post si je trouve plus efficace ou si c’est foireux à la longue).

Pour ceux qui se poserais la question, vous garderez vos raccourcis de tous les widgets enfants (et donc des champs de saisie des enfants de la fenêtre), par contre, si vous l’implémentez sur un champ de texte directement, forcément, plus rien de marchera.

Ici, un exemple d’implémentation:

 

class MDockWidget(QDockWidget):
    def __init__(self, parent=None, **params):
        parent = parent or shiboken.wrapInstance(long(MQtUtil.mainWindow()), QMainWindow)
        QDockWidget.__init__(self, parent)

    def keyPressEvent(self, *args, **kwargs):
        """Because of fucking maya switch focus...
        """
        pass

Ou:

from PySide.QtGui import QWidget
w = QWidget()
w.keyPressEvent = lambda *args: ""
w.show()

En espérant que ça vous aide et que vous ne vous fassiez pas autant arracher la tête que moi par les graphistes en colère…

[Python/Maya] Reload modules

Dans Maya (ou tout autre soft disposant d’une console) quand on utilise les packages, on est amené à les recharger pour prendre en compte les modifications, et c’est chiant!
Pour un package du style:

Root
`- __init__.py
`- main.py
`- ui.py

Il faut executer:

import Root
import Root.main
import Root.ui
reload(Root)
reload(Root.main)
reload(Root.ui)

Vous trouverez multiple sites vous expliquant comment python gère ses modules, je n’aborderais donc pas ça ici.
Excellent article sur ce site:

Avec le bout de code suivant, vous supprimez les modules

def rld(*args):
    """Delete modules with given name inside their sid (key in
     sys.modules)
    Ex:
    rld("Ti", "Foo")
    Will clear entry from all modules containing Ti and Foo in thair name.
    """
    for x in sorted(sys.modules):
        if any([True for t in args if t in x]):
            del sys.modules[x]

Vous n’aurez qu’a les re-importer pour les utiliser, modifications comprises.
Il existe sans doute autant d’autres méthodes que de devs, mais en 4 ans d’utilisation intensive, il ne m’a jamais fait défaut.

Maya: executer un fichier python en le glissant dans le viewport

Si vous êtes un du genre à vouloir utiliser des modules python comme des fichiers MEL, à vouloir juste drag and drop un .py dans le viewport maya pour le charger (oui, j’ai souvent eu droit à des « Eh mais python c’est de la *****, c’est pas reconnu par maya sauf si c’est dans le script editor, c’est ******! »), utiliser des petits scripts sans dépendances, tous regroupés dans un fichier, si vous avez peur de vouloir gérer des modules/packages et que vous ne voulez pas vous prendre la tête avec les « import », cet article est fait pour vous!

Enregistrez le code ci-dessous dans un fichier « .py », dans le plugin manager de maya (Window->Settings/Preferences->Plug-in manager), allez chercher le plugin précédemment créé (où placez le dans un des répertoire de plugins que maya va lancer au démarrage) et le tour est joué!
Vous pouvez maintenant drag&drop des fichiers .py (attention, pas .pyc) dans maya!

# -*- coding: utf-8 -*-

import sys
import maya.OpenMayaMPx as mpx


class PyDrop(mpx.MPxFileTranslator):
    registeredName = "pyDrop"

    def __init__(self):
        mpx.MPxFileTranslator.__init__(self)

    @classmethod
    def canBeOpened(cls):
        return False

    @classmethod
    def haveReadMethod(cls):
        return True

    @classmethod
    def filter(cls):
        return "*.py"

    @classmethod
    def defaultExtension(cls):
        return "py"

    def reader(self, file_, optionsString, mode):
        execfile(file_.rawFullName())


def translatorCreator():
    return mpx.asMPxPtr(PyDrop())


def initializePlugin(mobject):
    mplugin = mpx.MFnPlugin(mobject)
    try:
        mplugin.registerFileTranslator(PyDrop.registeredName, None, translatorCreator)
    except:
        sys.stderr.write("Failed to register translator: %s" % PyDrop.registeredName)
        raise


def uninitializePlugin(mobject):
    mplugin = mpx.MFnPlugin(mobject)
    try:
        mplugin.deregisterFileTranslator(PyDrop.registeredName)
    except:
        sys.stderr.write("Failed to deregister translator: %s" % PyDrop.registeredName)
        raise

Maya convertir des images en normal map

from maya.OpenMaya import MImage
import os.path as osp


def convertBumpToNormal(src, dst, scale=1, outputFormat=None, force=False):
    """Creates a new file (dst) that can be used as normal map file from given (src)
     file.

    :param src: Source file used to create normal map.
    :type src: str
    :param dst: Destination file, where to save converted map.
    :type dst: str
    :param scale: Depth normal scale, can vary from -256.0 to 256.0, although typical values range from 1.0 to 10.0.
    :type scale: float
    :param outputFormat: Destination format, if not set, if taken from given dst or src.
    :type outputFormat: str
    :param force: If dst file exists, overwrites it.
    :type force: bool
    """
    image = MImage()
    if not osp.isfile(src):
        raise ValueError("%s if not a file!" % src)
    if osp.isfile(dst) and not force:
        raise Exception("%s already exists, use -force flag to overwrite." % dst)

    image.readFromFile(src)
    image.filter(MImage.kHeightFieldBumpFormat, MImage.kNormalMapBumpFormat, scale)

    outputFormat = (outputFormat or osp.splitext(dst)[1].replace(".", "") or
                    osp.splitext(src)[1].replace(".", ""))
    if not outputFormat:
        image.writeToFile(dst)
    else:
        image.writeToFile(dst, outputFormat)

Maya, Visual Express et plugins x64

Installer Microsoft Visual Express (VCE) pour compiler un plugin sur maya sur Maya en 64 bits est une vraie tannée quand on ne connais pas la méthode.
VCE n’inclue pas de compiler x64, pour cela, il faut l’installer via des services pack, ce qui est bien et tout à fait normal, c’est que si ceux ci ne sont pas installés dans un ordre précis, ça plante!

Voici la procédure pour VCE 2010 (J’y compile pour Maya 2014):

1. Désinstaller Visual Studio (Visual studio, sdk, redistributables, distributions, que ce soit x84 ou x64)

2. Installer le Windows SDK 7.1

3. Installer le patch du Windows SDK 7.1 qui installe le compiler x64

4. Installer Visual Studio C++ Express 2010

5. Installer Visual Studio C++ Express 2010 SP1

Une fois fait, vous avez un MVC prêt à l’emploi. Si vous utiliser des plugins open source, avec un peu de chance, ils serons fournis avec un CMakeFile qui vous permettra d’utiliser CMake (qui sert juste à créer des projets, donc vous pouvez l’installer en x86, ça changera rien) pour créer et configurer les projets Visual sans vous prendre la tête.

Comment signaler efficacement un bug

Voici un document extrêmement instructif que toute personne touchant à l’informatique devrais lire.

Version originale écrite par Simon Tatham, programmeur professionnel et de logiciels libres;
traduit par Julien Kirch, développeur.

Introduction

Quiconque a déjà développé un logiciel destiné à un usage public a probablement déjà reçu au moins un mauvais rapport de bug. Il y a les rapports qui ne disent rien (« Ça ne marche pas ! »), qui n’ont aucun sens, qui ne donnent pas assez d’informations, ou même qui donnent de fausses informations. Il y a des comptes-rendus de problèmes qui sont dus à des erreurs de l’utilisateur, dus au programme de quelqu’un d’autre, ou dus à des problèmes de réseau.

Une chose explique qu’on considère le support technique comme une tâche horrible : les mauvais rapports de bug. Toutefois, tous les rapports de bug ne sont pas désagréables : quand je ne gagne pas ma vie, je développe des logiciels libres, et quelquefois je reçois des rapports de bug merveilleux, clairs, utiles et informatifs.

Dans ce texte, je vais essayer d’exposer clairement ce qui fait un bon rapport de bug. Idéalement, j’aimerais que tout le monde sur terre lise ce texte avant de signaler un bug à qui que ce soit. En tous cas j’aimerais au moins que tous ceux qui me signalent des bugs l’aient lu.

En gros, le but d’un rapport de bug est de permettre au programmeur de voir le programme planter devant lui. Vous pouvez soit lui montrer de visu, soit lui fournir des instructions soignées et détaillées sur la façon de le faire planter. S’il parvient à le faire planter, il essaiera de collecter des informations jusqu’à découvrir la cause du bug. S’il n’y parvient pas, il devra vous demander d’obtenir ces informations pour lui.

Dans un rapport de bug, essayez de séparer nettement les faits (« J’étais devant l’ordinateur et ceci s’est produit« ) des spéculations (« Je pense que c’est ça le problème »). Si vous voulez, vous pouvez laisser tomber les spéculations, mais en aucun cas les faits.

Quand vous signalez un bug, vous le faites car vous voulez que ce bug soit corrigé. Ce n’est pas la peine de houspiller les programmeurs, ou de leur faire délibérément perdre leur temps : c’est peut-être leur faute et votre problème, et vous avez le droit d’être en colère contre eux, mais le bug sera corrigé plus rapidement si vous les aidez en leur fournissant toutes les informations dont ils ont besoin. D’autre part, n’oubliez pas que si ce programme est gratuit, c’est que l’auteur s’en occupe par gentillesse, alors si trop de gens se montrent désobligeants avec lui, il arrêtera peut-être d’être gentil.

« Ça ne fonctionne pas »

Les programmeurs ne sont pas des idiots : si le programme ne fonctionnait pas du tout, ils s’en seraient probablement déjà rendu compte. Comme ils n’ont rien remarqué, pour eux, il doit fonctionner. Donc, soit vous faites quelque chose d’une autre manière qu’eux, ou votre environnement est différent du leur. Ils ont besoin d’informations ; leur fournir ces informations et l’objectif d’un rapport de bug. Plus d’informations est toujours mieux que moins.

Beaucoup de programmes, particulièrement les programmes libres, sont accompagnés de la liste de leurs bugs connus. Si vous pouvez mettre la main dessus, mieux vaudrait la lire pour vérifier si le bug que vous venez de découvrir n’est pas déjà référencé. S’il l’est déjà, il est probablement inutile de faire un nouveau rapport, sauf si vous pensez disposer de plus d’informations que n’en contient le rapport dans la liste de bugs. Les programmeurs pourront peut-être corriger plus facilement le bug si vous leur fournissez des informations qu’ils ne possèdent pas encore.

Ce texte est rempli de consignes, et aucune d’entre elles n’est une règle absolue. Certains programmeurs veulent que les bugs leur soient signalés sous une forme particulière. Si le programme est fourni avec son propre assortiment de règles de rapport de bug, lisez le. S’il contredit celles de ce texte, suivez le.

Si vous ne signalez pas un bug mais demandez seulement de l’aide pour utiliser le programme, vous devriez indiquer où vous avez déjà cherché la réponse à votre question (« J’ai regardé dans le chapitre 4 et dans la section 5.2 mais je n’ai rien trouvé m’indiquant si c’était possible »). Cela permettra aux programmeurs de savoir où les gens pensent trouver la réponse, et ainsi d’améliorer la documentation.

« Montre moi. »

L’une des meilleures manières dont vous pouvez signaler un bug est de le montrer à un programmeur. Amenez-le devant votre ordinateur, lancez son programme, et montrez lui ce qui se passe mal. Laissez le vous voir démarrer la machine, lancer le programme, l’utiliser, et voir comment il répond à vos actions.

Il connaît ce programme comme sa poche. Il sait en quelles parties il peut avoir confiance, et quelles parties peuvent avoir des défauts. Il sait intuitivement ce qu’il cherche. Quand arrive le moment où le programme se met à faire clairement n’importe quoi, il a peut-être déjà remarqué une légère erreur survenue plus tôt et qui pourrait lui donner une indication. Il peut observer tout ce que fait l’ordinateur pendant le test et noter les choses importantes.

Cela ne sera peut-être pas suffisant. Il pourrait décider qu’il a besoin de plus d’informations, et vous demander de refaire la même chose. Il pourrait vous demander de lui décrire toute l’opération, pour qu’il puisse reproduire le bug autant de fois qu’il le désire. Il pourrait faire varier la procédure plusieurs fois, pour vérifier si le problème apparaît dans un seul cas ou dans un ensemble lié de cas. Si vous êtes malchanceux, il aura peut-être besoin de prendre votre place pendant quelques heures, avec des outils de développement, et vraiment commencer à creuser. Mais la chose la plus importante est que le programmeur observe l’ordinateur quand ça se passe mal. Quand il a vu le problème se produire, il peut généralement commencer à le résoudre.

« Montre moi comment y arriver »

Nous sommes à l’ère d’Internet, de la communication globale, où en appuyant simplement sur un bouton, je peux envoyer mon programme à quelqu’un en Russie, et où tout aussi simplement il peut m’envoyer ses commentaires sur celui-ci. Mais s’il a un problème avec mon programme, je ne peux pas aller chez lui voir le programme planter. « Montre moi » c’est bien quand on peut, mais souvent, on ne peut pas.

Si vous devez signaler un bug à un programmeur qui ne peut être physiquement présent, le but de l’exercice est de lui permettre de reproduire ce bug. Vous voulez que le programmeur exécute sa propre copie du programme, effectue les mêmes manipulations, et le fasse planter de la même manière. Quand il réussit à faire planter le programme devant lui, il peut s’en occuper.

Alors dites-lui exactement ce que vous avez fait. S’il s’agit d’un programme graphique, dites lui sur quels boutons vous avez cliqué, et dans quel ordre. Si c’est un programme que vous avez lancé par une commande, donnez lui précisément la commande que vous avez utilisée. Si possible, vous devriez lui fournir une transcription complète de la session, montrant quelles commandes vous avez tapées, et quelles réponses l’ordinateur a retournées.

Donnez au programmeur toutes les entrées auxquelles vous pouvez penser. Si le programme lit un fichier, vous devriez probablement en envoyer une copie. Si l’ordinateur communique avec un autre ordinateur via un réseau, vous ne pourrez probablement pas envoyer une copie de cet ordinateur, mais vous pouvez au moins indiquer de quel type d’ordinateur il s’agit, et (si vous le pouvez) quels logiciels s’y exécutent.

« Chez moi ça fonctionne. Alors d’où vient le problème ? »

Si vous donnez au programmeur une longue liste d’entrées et d’actions, qu’il lance sa propre copie du programme, et qu’aucun problème n’apparaît, c’est que vous ne lui avez pas donné suffisamment d’informations. Peut-être que l’erreur n’apparaît pas sur tous les ordinateurs ; vos deux systèmes diffèrent peut-être d’une manière ou d’une autre. Vous avez peut-être mal compris ce que le programme est censé faire, et vous êtes tous deux exactement devant le même écran, mais vous pensez qu’il s’agit d’une erreur, et il sait qu’il n’y a aucun problème.

Donc décrivez ce qui s’est passé. Dites exactement ce que vous avez vu. Dites pourquoi vous pensez que ce que vous avez vu était une erreur ; au mieux dites lui exactement ce que vous pensiez voir. Si vous dites « et là, ça a planté », vous oubliez des informations très importantes.

Si des messages d’erreur apparaissent, rapportez les soigneusement et précisément au programmeur. Ils sont importants. A cet instant, le programmeur n’essaie pas de résoudre le problème : il essaie seulement de le localiser. Il a besoin de savoir ce qu’il s’est mal passé, et ces messages d’erreurs représentent le résultat des efforts de l’ordinateur pour vous l’indiquer. Recopiez ces erreurs si vous n’avez pas d’autre moyen de les conserver, mais il n’est pas très utile d’indiquer que le programme a généré une erreur, à moins que vous ne puissiez rapporter également quel était ce message d’erreur.

En particulier, si le message d’erreur comprend des nombres, rapportez les au programmeur. Ce n’est pas parce que vous n’y voyez aucune signification qu’ils n’en ont pas. Ces nombres contiennent toutes sortes d’informations qui peuvent être exploitées par les programmeurs, et qui sont susceptibles de contenir des indications vitales. Les nombres dans les messages d’erreurs apparaissent dans les cas où l’ordinateur est trop embrouillé pour rendre compte de l’erreur sous forme de mots, mais il fait son possible pour vous fournir les informations importantes d’une manière ou d’une autre.

À ce moment, le programmeur effectue en fait un travail de détective. Il ne sait pas ce qui est arrivé, et ne peut pas s’approcher suffisamment pour le voir par lui-même, il cherche donc des éléments lui permettant de découvrir ce qu’il cherche. Les messages d’erreurs, les chaînes de nombres incompréhensibles, et mêmes des retards inexpliqués sont pour lui comme des empreintes digitales sur la scène d’un crime. Conservez les !

Si vous utilisez Unix, le programme a peut-être généré un core dump. Les core dumps sont une source d’informations particulièrement bonne, alors ne les effacez pas. D’un autre côté, la plupart des programmeurs n’apprécient guère de recevoir d’énormes core dumps par mail sans avoir été prévenu, alors demandez leur avant d’en envoyer un. Sachez également que les core dump contiennent un enregistrement complet de l’état du programme, toute donnée « secrète » (si par exemple le programme contenait un message personnel, ou traitait des données confidentielles) peut être contenue dans ce fichier.

« Alors j’ai essayé … »

Quand un bug arrive, vous pouvez faire beaucoup de choses. Beaucoup d’entre elles ne font qu’aggraver le problème. Une de mes amies à l’école a effacé tous ses fichiers Word par erreur, et avant de demander de l’aide, elle a essayé de réinstaller Word, puis de lancer defrag. Aucun des deux n’a contribué à récupérer les fichiers, et ils ont altéré le disque dur au point qu’aucun programme de récupération ne serait capable de sauver quoi que ce soit. Si elle n’avait rien fait, elle aurait pu avoir une chance.

Ce genre d’utilisateurs est comme une mangouste acculée dans un coin : le dos au mur et voyant une mort certaine arriver, elle attaque frénétiquement parce qu’il vaut mieux faire quelque chose plutôt que rien. Mais cette attitude n’est pas très bien adaptée au type de problèmes que les ordinateurs produisent.

Au lieu d’être une mangouste, soyez une antilope. Quand une antilope est confrontée avec quelque chose d’inattendu ou d’effrayant, elle s’immobilise. Elle reste totalement immobile et essaie de ne pas attirer l’attention; et, pendant qu’elle est stoppée, elle réfléchit à la meilleure chose à faire. (Si les antilopes disposaient d’un support technique, c’est à ce moment là qu’elles téléphoneraient.) Puis, une fois qu’elle a décidé quelle était la chose la plus sûre à faire, elle la fait.

Quand quelque chose se passe mal, cessez immédiatement de faire quoi que ce soit. N’appuyez sur aucun bouton. Regardez l’écran et observez tout ce qui sort de l’ordinaire, et souvenez vous-en ou notez le. Ensuite seulement, vous pourrez cliquer prudemment sur « OK » ou « Annuler », suivant ce qui vous semble le plus sûr. Essayez de développer une réaction réflexe : si un ordinateur a une réaction inattendue, arrêtez vous.

Si vous réussissez à vous tirer du problème, que ce soit en fermant l’application concernée ou en rebootant l’ordinateur, il serait bon d’essayer de le reproduire. Les programmeurs aiment les problèmes qu’ils peuvent reproduire. Et les programmeurs heureux corrigent les bugs plus rapidement, et plus efficacement.

« Je pense que la modulation tachyonique doit être mal polarisée »

Les non programmeurs n’ont pas le monopole des mauvais rapports d’erreur. Certains des plus mauvais rapports d’erreur que j’ai jamais vu proviennent de programmeurs, et même de bons programmeurs.

Il m’est arrivé une fois, de travailler avec un autre programmeur, qui ne cessait de trouver de erreurs dans son propre code et de les corriger. De temps en temps il tombait sur un bug qu’il n’arrivait pas à corriger et il me demandait de l’aide. « Quel est le problème ? », lui demandais-je. Il me répondait en me donnant sa propre opinion sur ce qu’il fallait corriger.

Quand son opinion était juste, ça se passait bien. Dans ce cas, il avait déjà fait la moitié du travail, et nous étions capable de terminer ensemble. C’était efficace et utile.

Mais parfois, il se trompait. Nous pouvions alors passer un certain temps à essayer de comprendre pourquoi une certaine partie du programme produisait des données incorrectes, pour parfois nous rendre compte qu’au contraire il fonctionnait parfaitement, et que nous venions de passer une demi-heure à examiner une portion de code parfaitement fonctionnelle, et qu’en fait le problème se situait ailleurs.

Je suis sûr qu’il ne se conduirait pas ainsi avec un médecin: « Docteur, je voudrais une prescription pour de l’Hydroyoyodyne. » Les gens savent qu’il ne faut pas se conduire ainsi avec un médecin : vous lui décrivez les symptômes, gènes, souffrances, douleurs, éruptions et fièvres, et vous le laissez diagnostiquer le problème et ce qu’il faut faire. Dans le cas contraire, le médecin vous prend pour un hypocondriaque ou un cinglé, et il a plutôt raison.

Il en va de même avec les programmeurs. Fournir votre propre diagnostic est parfois utile, mais donnez toujours les symptômes. Le diagnostic est un plus, et pas une alternative aux symptômes. De même, envoyer une modification à effectuer sur le code pour résoudre est un bon ajout à un rapport de bug, mais pas un substitut adéquat.

Si un programmeur vous demande des informations supplémentaires, ne l’ignorez pas. Une fois, quelqu’un m’a signalé un bug, et je lui ai demandé d’essayer une commande en sachant qu’elle ne fonctionnerait pas. Je voulais qu’il l’essaie parce que je voulais savoir lequel de deux messages d’erreur allait en résulter. Avoir cette réponse m’aurait donné un indice vital. Mais il n’a pas essayé, il s’est contenté de me répondre « Non, ça ne fonctionnera pas ». Il m’a fallu du temps pour le persuader d’essayer réellement.

C’est très bien d’utiliser son intelligence pour aider le programmeur. Même si vos déductions sont fausses, le programmeur devrait vous être reconnaissant d’avoir, au moins, essayé de lui simplifier la vie. Mais n’oubliez pas de lui fournir aussi les symptômes, sans cela vous pourriez bien lui rendre la vie bien plus difficile.

« C’est marrant, j’ai fait ça il y a juste une seconde. »

Dites « bug intermittent » à n’importe quel programmeur et regardez son visage se décomposer. Les problèmes simples sont ceux qui sont déclenchés par une séquence d’actions simple. Dans ce cas le programmeur peut répéter ces actions, en observant attentivement les conditions de tests et surveiller en détail ce qui arrive. Trop de problèmes ne suivent pas cette règle : il y a des programmes qui se plantent une fois par semaine, de temps en temps, ou jamais quand vous essayez devant le programmeur, mais systématiquement quand une deadline se rapproche.

La plupart des bugs intermittents ne sont pas réellement intermittents. Ils obéissent généralement à une certaine logique. Certains se manifestent quand la machine arrive à court de mémoire, certains quand un autre programme essaie de modifier un fichier critique au mauvais moment, et certains uniquement pendant la première demie de chaque heure. (J’en ai déjà vu un de cette dernière sorte).

Par ailleurs, si vous pouvez reproduire le bug, mais que le programmeur n’y parvient pas, il se peut très bien que vos ordinateurs diffèrent, et que ce soit cette différence, qui cause le problème. Un jour j’ai eu un programme dont la fenêtre s’enroulait en une petite balle dans le coin supérieur gauche de l’écran, et restait là à bouder. Mais il ne se comportait ainsi que sur des écrans 800×600, sur mon moniteur 1024×768, tout se passait bien.

Le programmeur voudra connaître tout ce que vous pouvez découvrir sur le problème. Essayez sur une autre machine. Essayez deux ou trois fois pour connaître la fréquence du plantage. Si ça se passe mal quand vous faites un travail lourd mais pas quand vous voulez le montrer, c’est que c’est peut-être l’utilisation pendant une longue durée ou avec de gros fichiers qui cause le problème. Essayez de vous rappeler autant de détails que vous le pouvez sur ce que vous faisiez quand l’erreur est apparue, et si vous y voyez un modèle, mentionnez le. Toute information peut être utile, même s’il ne s’agit que d’une probabilité (comme « ça a tendance à planter plus souvent quand emacs est lancé »), peut-être que ça ne donne pas d’indication directe sur la cause du problème, mais il est possible que ça puisse aider le programmeur à le reproduire.

Et le plus important : le programmeur voudra être sûr de savoir s’il s’agit d’une vraie erreur intermittente ou d’un bug spécifique à l’ordinateur. Il voudra connaître le maximum de détails sur votre ordinateur, pour pouvoir travailler sur ce qui le différencie du sien. Beaucoup d’entre eux sont spécifiques au programme, mais les numéros de versions seront très certainement une chose à préciser : le numéro de version du programme, celui du système d’exploitation, et probablement ceux de tout programmes impliqués dans le problème.

« Alors j’ai chargé ma disquette dans Windows . . . »

Il est essentiel qu’un rapport de bug soit rédigé clairement. Si le programmeur ne parvient pas à comprendre ce que vous voulez dire, vous auriez tout aussi bien fait de n’avoir rien écrit.

Je reçois des rapports de bugs qui viennent de partout. Beaucoup d’entre eux proviennent de personnes dont l’anglais n’est pas la langue maternelle, et un grand nombre d’entre eux s’excusent de leur mauvais anglais. En général, ces rapports de bugs sont clairs et utiles ; les rapports les plus obscurs étant envoyés par des personnes dont l’anglais est la langue maternelle, et qui pensent que je parviendrai à les comprendre même s’ils ne font aucun effort de clarté ou de précision.

  • Soyez spécifique. Si vous pouvez faire une chose de deux manières différentes, indiquez celle que vous avez utilisée. « J’ai sélectionné Charger » peut signifier « J’ai cliqué sur charger » ou « J’ai appuyé sur ALT-L ». Ça peut sembler inutile, mais parfois, ça compte.
  • Soyez prolixe. Donnez plutôt plus d’informations que moins. Si vous en dites trop, le programmeur pourra en ignorer une partie, mais si vous n’en dites pas suffisamment, il devra vous répondre en vous posant d’autres questions. Il m’est arrivé de recevoir un rapport d’erreur constitué d’une seule phrase ; et à chacune de mes demandes d’informations, la réponse du rapporteur tenait en une phrase. Il m’a fallu plusieurs semaines pour obtenir une quantité suffisante d’information, tellement les réponses étaient brèves.
  • Faites attention aux pronoms. N’utilisez pas des mots comme « il », ou des références comme « la fenêtre » quand leur signification est imprécise. Considérez l’exemple suivant : « J’ai lancé l’application FooApp. Elle a affiché une fenêtre d’erreur. J’ai essayé de la fermer et elle a planté ». Quel élément l’utilisateur a-t-il bien pu essayé de fermer ? S’agit-il de la fenêtre d’erreur ou de toute l’application ? Ça fait une différence ! Au lieu de cela, dites plutôt : « J’ai lance l’application FooApp. Elle a affiché une fenêtre d’erreur. J’ai essayé de fermer la fenêtre d’erreur et FooApp a planté ». C’est plus long et répétitif, mais également plus clair et moins susceptible d’être compris de travers.
  • Relisez ce que vous avez écrit. Relisez le rapport et voyez si vous pensez qu’il est clair. Si vous avez fait une liste des actions qui devraient produire le bug, essayez de la suivre, et vérifier ainsi qu’aucune étape ne manque.

Résumé

  • Le premier objectif d’un rapport de bug est de permettre au programmeur de voir le bug de ses propres yeux. Si vous ne pouvez le faire planter devant lui, donnez lui des instructions détaillées afin qu’il puisse le faire planter par lui-même.
  • Si le premier objectif ne peut être atteint, et donc si le programmeur ne peut voir l’erreur se produire, le second objectif d’un rapport de bug est de décrire ce qui s’est mal passé. Décrivez tout, en détails. Dites ce que vous avez vu, mais dites aussi ce que vous vous attendiez à voir. Recopiez les messages d’erreur, surtout s’ils contiennent des nombres.
  • Quand votre ordinateur fait quelque chose d’inattendu, arrêtez-vous. Ne faites rien tant que vous n’êtes pas calme, et ne faites rien que vous pensez pouvoir être dangereux.
  • Bien entendu, si vous pensez en être capable, diagnostiquez l’erreur par vous-même, mais si vous le faites, vous devriez tout de même lui communiquer les symptômes.
  • Soyez prêt à fournir des informations supplémentaires si le programmeur en a besoin. S’il pouvait s’en passer, il ne vous les demanderait pas. Il ne vous dérange pas pour le plaisir.
  • Écrivez clairement. Dites ce que vous voulez dire, et soyez sûr que ça ne peut pas être mal compris.
  • Et surtout, soyez précis : les programmeurs aiment la précision.

Précision : je n’ai en fait jamais vu ni mangouste ni antilope, ma zoologie est donc peut-être incorrecte.

Fiche #10: Reflecto

Synopsis:

L’histoire d’un homme qui se fait poursuivre par sa conscience, après a voir rompu avec son passé…

Crédits :

  • Equipe principale:

Xavier Collos
Jingze Sun
Julien Favini

Film live action avec incrustation de FX.

Pous pouvez visionner le film ci dessous, suivit du making of (ou vimeo).
http://reflecto-themovie.com/

Haut de Page