village_people_API.png

Traduction:

Lisez ce billet en entier avant de commencer à bidouiller dans l'API Maya - Il vous permettra d'économiser beaucoup temps et d'efforts à long terme, je peux vous l'assurer.

Conseils
  • Vérifiez TOUT les MStatus renvoyé par l'API Maya. Je sais que vous trouvez ça lourd, mais Maya a l'habitude d'échouer silencieusement. Si vous ne prenez pas l'habitude de vérifier les retours d'erreurs, vous pouvez ne pas en voir une qui s'est produite! Cela accélèrera grandement le développement si vous prenez l'habitude de tout vérifier.
  • Vous devez penser à écrire votre code (dans l'API) de façon à ce qu'il fonctionne de la manière dont Maya fonctionne. Essayer de faire fonctionner l'API à votre sauce ne vous garantie aucun résultat.
  • Utilisez Visual Studio 2005 ou plus récent. Vous ne pouvez pas utiliser d'autre compilateur. Utilisez GCC sous Linux/Mac.
  • Le MEL EST plus puissant que l'API C++. Difficile à croire et pourtant vrai.
  • L'API n'est pas une version C++ du MEL. Les deux ont des usages différents.
  • N'importe quel plugin est un mixte de MEL et d'API. Vous ne pouvez pas vous passer du MEL.
  • Si vous ne savez pas utiliser Maya, apprenez! C'est le seul moyen d'être sur que vos outils fonctionneront de manière cohérente avec lui.
  • Si vous avez du code en MEL que vous voulez porter en C++ dans un soucis de vitesse, avant cela, désactiver l'undo, soyez sur que vous n'utilisez pas la commande "print()", et voyez à quelle vitesse il va. Vous devez trouver un moyen d'accélérer le script suffisamment afin d'éviter d'avoir à utiliser l'API.
  • Gardez vos nodes le plus petit possible (ndt: "small and atomic" dans le texte). Un node "couteau suisse" ne sera pas efficacement à traiter par Maya, donc découpez les opérations complexes en plusieurs nodes.
  • Essayez autant que possible d'utilisez les nodes pré-existant, et n'ajoutez de nouveaux nodes uniquement pour des choses que vous ne pouvez pas faire avec l'API.
  • Comprendre les différentes entre les nodes de type DAG et les nodes de type DG est vital si vous travaillez avec l'API. Lisez ce post pour meilleur description: http://www.highend3d.com/boards/index.php?showtopic=242681 (ndt: le liens est mort mais il semble que ce soit ce billet: http://www.creativecrash.com/forums/api/topics/dag-confusion).
  • N'utilisez que des fichiers Maya au format ASCII (.ma). D'une part ils fonctionnent avec un logiciel de contrôle de version, mais plus important, vous gardez une chance de pouvoir charger le fichier si vous changez les attributs du plug-in. Dans le cas des fichier binaires, Maya charge les informations comme un bloque de donné "brut". Si vous changez les attributs, ou l'ordre dans lequel les attributs sont définis, vous chargerez des données corrompues. Dans la mesure ou le format Maya ASCII charge les attributs "par nom", vous pouvez normalement faire quelques modifications minimes tout gardant la possibilité de charger vos scènes.
Sur le forum (ndt: http://www.creativecrash.com/forums/api/topics)
  • Vous remarquerez que des questions reçoivent la réponse "utilise du MEL". Nous n'essayons pas d'être énervant quand nous disons cela, nous somme juste réaliste. La question que vous avez posé est soit quelque chose d'impossible à faire dans l'API, ou soit quelque chose pour laquelle l'API est totalement inadaptés (voir l'exemple du code plus bas). Si vous avez la réponse "utilise le MEL", alors s'il vous plait, utilisez le MEL! Bon nombre de gens s'énerve quand nous leur répondons ça, mais honnêtement, l'API est loin d'être aussi puissante que MEL, et 99% du temps, les choses devraient être fait en MEL.
  • Vous pouvez penser que les gens n'ont pas la réponse à votre question. C'est tout à fait normal dans la mesure ou l'API est assez conséquente et vous ne pouvez pas attendre des gens qu'ils connaissent tout cela. Dans ce cas, il est bon de garder son thread d'ouvert et de mettre à jour votre progression dans la mesure ou cela forme un enregistrement dans google qui peut aider les autres plus tard. En fin de compte, la connaissance que nous avons acquise au fil des ans est dû aux gens qui font ça.
  • Ne m'envoyez pas de questions sur l'API en PM, ni aux modérateurs ni aux autres membres du forum. Si vous faites ça, votre question n'est pas public. Si vous avez un problème, l'endroit ou demander est le forum. Comme c'est écrit dans les règles du forum, nous ne répondons pas aux question posé par PM car nous voulons garder un tableau "googleable" des question posées, et de leur éventuel réponse dans le forum.
  • Ne m'envoyez pasde lien vers un post que vous avez crée sur le forum. Ni à moi, ni aux autres membres. Les modérateur d'ici (ndt: les forums de creativecrash.com) reçoivent un e-mail à chaque topic créé, et nous regardons chaque question qui est posé. Si nous pouvons donner une réponse nous le ferons. Envoyer un PM avec un lien vers le billet ne fait que nous énerver.
Les exporters

Si vous voulez simplement écrire un exporter pour Maya, vous avez de la chance - c'est l'une des choses les plus facile à faire avec l'API, et cela vous donnera un joli aperçu de ce qu'il faut savoir sur Maya. J'ai écrit une documentation il y a longtemps, qui peut être trouvé ici. Elle fut écrit pour Maya 6.5, mais devrait vous indiquer la marche à suivre. (Allez y pour avoir des exemples à compiler sur Maya 8.0 ou plus récent, soyez certain d'avoir ajouté le define "REQUIRE_IOSTREAM" dans vos options de pre-processeur).

Les importeurs

Écrire un importer pour Maya est un peu plus compliqué, mais ce n'est pas la mer à boire. Typiquement, vous devez chercher une class sous la forme MFn####, ou #### est la chose importante (exemple: Transform, Mesh, etc...). Chaque classe MFn a une méthode "create", l'idée étant justement de faire l'inverse de ce qui est présenté ici. Notez que vous ne pouvez pas faire tout ce que vous souhaitez aussi facilement avec l'API, donc attendez vous à faire du MEL occasionnellement.

Les nodes

Il est intéressant de commencer à apprendre l'API en créant vos propres nodes. Les nodes sont essentiels pour comprendre comment Maya fonctionne, et en réalité, quelque soit la chose que vous souhaitiez faire, elle sera probablement mieux faite avec un node (et non une commande, comme vous pouvez le penser). Encore une fois, j'ai quelques exemples pour débuter sur mon site web (http://robthebloke.org) - Les livres de David Gould donnent également des explications sur comment débuter. Les choses dont vous avez besoin pour un nouveau node sont:

  • Le code C++, MPxNode, MPxEmitter, etc...
  • L'AETemplate pour "customiser" l'Attribut Editor de votre node (en MEL)
  • Des commandes MEL pour créer le node et le connecter dans le DG
  • Un "custom menu item with options box" (Sauvegardez les options en utilisant optionVar)
Les nodes de locator

Ils sont assez trivial à faire, ils peuvent être utilisé comme "marqueurs personnalisés" dans la scène. Il y a des exemples sur mon site, ainsi que dans les livres de David Gould.

Des primitives poly/nurbs personnalisées

Vous souhaitez créer une shape personnalisée, comme les primitives polyCube/polySPhere? Utilisez un node! Vous pouvez créer un attribut de sortie de type MPxData::kMesh, ou kNurbsSurface, etc... Qui sera modifié grace à la méthode "compute".

Modifier la géométry ou d'autres informations

N'allez pas essayer de supprimer une face d'un mesh, ou ajouter des vertex à un mesh, ou, en fait, n'importe quoi qui change les données dans Maya. Il y a 99% de chance que cela ne marche pas. Toute les modifications de données devrait être fait en utilisant des nodes. Considérez cet exemple: Si vous créez un poly cube à partir d'une MPxCommand, que vous supprimez une de ses faces en utilisant MFnMesh. Tout ce qui se passera c'est que la prochaine evalutation du DG (ndt: le prochain refresh Maya) va simplement réécrire votre mesh modifié par dessus l'ancien. Vous avez en fait besoin de créer un node qui modifie les données comme le ferait le DG! Comme point de départ, vous pouvez utilisez les exemples de polyModifier qui sont dans le SDK de Maya.

Les commandes

Alors, vous voulez convertir une commande MEL toute lente pour qu'elle s'executer plus vite dans l'API? Dans un premier temps, éssayez de désactiver "l'undo queue", soyez sur que vous ne "printez" rien dans le script éditor, et voyez à quelle vitesse elle va. Cela peut résoudre votre problème. La seul vrai raison pour écrire une class dérivé de la MPxCommand c'est si vous souhaitez interfacer du code C++ ou les librairies. Toute autre utilisation est une perte de temps, et vous devriez utiliser le MEL.

GUI

Oubliez ça. L'API Maya n'a pas de GUI, vous devez utiliser le MEL. La seule exception est un "context" personnalisé que vous pouvez écrire en C++ pour gérer les entrées de la souris pour vous. Vous pouvez cepandant utiliser une commande MEL comme scriptCtx pour faire ça (Bien que ce ne soit pas aussi flexible que la version C++)

Applications Stand alone

Vous pouvez vouloir utiliser la MLibrary pour avoir Maya qui fonctionne sans la GUI, ce qui est entierement possible sans trop d'effort. Mais avant de faire ça, est ce que mayabatch avec un MEL script est capable de faire le boulot plus rapidement? Aussi longtemps que vous éviterez d'utiliser les commandes MEL de GUI, cela devrait vous satisfaire.

La classe MPxCommand

Elle peut être pratique à utiliser comme un bac à sable pour apprendre l'API, mais 99 fois sur 100, vous n'irez pas loin. Je recommande fortement d'utiliser le MEL pour toute les commandes, dans la mesure ou vous n'avez pas à vous soucier de l'implémentation du undo!

Le MEL est plus puissant que l'API Maya

Bien que l'API Maya soit très puissante, il est également très facile de mal la comprendre lorsque vous débutez. L'idée de chaque plugin devrait être d'améliorer le MEL, et non de le remplacer par du C++. L'exemple suivant devrait, esperons le, démontrer que le MEL est simple mais également très flexible:

$ret = `polyCube -n "hello"`;
expression -o $ret[0] -s "tx = time" -ae 1 -uc all;

Tout ce que ce code fait, c'est connecter un couple de node ensemble. La commande "polyCube" et "expression" sont déja implémenté en MEL, et elles sont toute les deux très puissante, et extrêmement flexibles. Pour refaire ce code à l'identique avec l'API, vous devrez vous préoccuper des "set options", vous devrez être sur que le undo/redo fonctionne, vous devrez vous préoccuper des assignations de matériaux, etc... Pour faire court, la quantité de travail n'est pas négligeable. La version C++ de ses deux lignes de MEL est en fait équivalent à 350 lignes de code, simplement pour manipuler des nodes tout simples! Imaginez avec des centaines de node, vos 200 lignes de MEL pourrait ressembler à 35000 lignes de C++!!!

Donc, éssayez d'utiliser le MEL sinon vous finirez par perdre beaucoup de temps, un peu comme ça:

// to execute once compiled, call this in mel:
//
// wasteOfTime "hello";
//
// wasteOfTime.h
#ifndef __WHY_MPX_COMMAND_IS_A_WASTE_OF_TIME__H__
#define __WHY_MPX_COMMAND_IS_A_WASTE_OF_TIME__H__
 
#ifdef WIN32
#define NT_PLUGIN
#define REQUIRE_IOSTREAM
#pragma once
#endif
 
#include <maya/MPxCommand.h>
#include <maya/MGlobal.h>
#include <maya/MFnSet.h>
#include <maya/MFnDependencyNode.h>
#include <maya/MDagModifier.h>
#include <maya/MSyntax.h>
#include <maya/MArgDatabase.h>
#include <maya/MSelectionList.h>
#include <maya/MPlug.h>
#include <maya/MFnDagNode.h>
#include <maya/MDagPath.h>
#include <maya/MFnExpression.h>
#include <iostream>
 
#define CHECK_STAT(X) \
  if( (X) != MS::kSuccess) { \
  std::cout << __FILE__ << ":" << __LINE__ << std::endl; \
  MGlobal::displayError(status.errorString()); \
  return (X); \
  }
 
//---------------------------------------------------------------------------------
class WasteOfTime
  : public MPxCommand
{
public:
  bool isUndoable() const;
  MStatus doIt(const MArgList& args);
  MStatus redoIt();
  MStatus undoIt();
  static MSyntax newSyntax();
  static void* creator();
private:
  MDagModifier mDagMod;
  MDagPath mPath;
  MObject mExpression;
};
 
#endif
 
 
// wasteOfTime.cpp
CODE
#include "WasteOfTime.h"
 
//---------------------------------------------------------------------------------
bool WasteOfTime::isUndoable() const
{
  return true;
}
 
//---------------------------------------------------------------------------------
void* WasteOfTime::creator()
{
  return new WasteOfTime;
}
 
//---------------------------------------------------------------------------------
MSyntax WasteOfTime::newSyntax()
{
  MSyntax syn;
  syn.addArg(MSyntax::kString);
  return syn;
}
 
//---------------------------------------------------------------------------------
MStatus WasteOfTime::doIt(const MArgList& args)
{
  MStatus status = MS::kFailure;
 
  // verify args
  MArgDatabase db(syntax(),args,&status);
  CHECK_STAT(status);
 
  // grab name arg
  MString name;
  status = db.getCommandArgument(0,name);
  CHECK_STAT(status);
 
  // avoid annoying creatNode class with base
  MDGModifier& mod = mDagMod;
 
  // create nodes
  MObject oT = mDagMod.createNode("transform",MObject::kNullObj,&status);
  CHECK_STAT(status);
  MObject oM = mDagMod.createNode("mesh",oT,&status);
  CHECK_STAT(status);
  MObject oP = mod.createNode("polyCube",&status);
  CHECK_STAT(status);
 
  // rename based on args
  status = mDagMod.renameNode(oM,name + "Shape");
  CHECK_STAT(status);
  status = mDagMod.renameNode(oT,name);
  CHECK_STAT(status);
  status = mDagMod.renameNode(oP,name + "Creator");
  CHECK_STAT(status);
 
  // connect polycube to mesh
  {
    MFnDependencyNode fnPC(oP,&status);
    CHECK_STAT(status);
    MFnDependencyNode fnPM(oM,&status);
    CHECK_STAT(status);
 
    MPlug plgI = fnPM.findPlug("i",&status);
    CHECK_STAT(status);
    MPlug plgO = fnPC.findPlug("out",&status);
    CHECK_STAT(status);
 
    status = mDagMod.connect(plgO,plgI);
    CHECK_STAT(status);
  }
 
  // grab current options for polyCube, and set on node
  {
    double w=0;
    double h=0;
    double d=0;
    int sdw=0;
    int sdh=0;
    int sdd=0;
    int tex=0;
    int axis=0;
 
    status = MGlobal::executeCommand(MString("optionVar -q \"polyPrimitiveCubeWidth\""),w,false,false);
    CHECK_STAT(status);
    status = MGlobal::executeCommand(MString("optionVar -q \"polyPrimitiveCubeHeight\""),h,false,false);
    CHECK_STAT(status);
    status = MGlobal::executeCommand(MString("optionVar -q \"polyPrimitiveCubeDepth\""),d,false,false);
    CHECK_STAT(status);
    status = MGlobal::executeCommand(MString("optionVar -q \"polyPrimitiveCubeSX\""),sdw,false,false);
    CHECK_STAT(status);
    status = MGlobal::executeCommand(MString("optionVar -q \"polyPrimitiveCubeSY\""),sdh,false,false);
    CHECK_STAT(status);
    status = MGlobal::executeCommand(MString("optionVar -q \"polyPrimitiveCubeSZ\""),sdd,false,false);
    CHECK_STAT(status);
    status = MGlobal::executeCommand(MString("optionVar -q \"polyPrimitiveCubeTexture\""),tex,false,false);
    CHECK_STAT(status);
    status = MGlobal::executeCommand(MString("optionVar -q \"polyPrimitiveCubeAxis\""),axis,false,false);
    CHECK_STAT(status);
 
    MPlug plg;
    MFnDependencyNode fnP(oP,&status);
    plg = fnP.findPlug("w",&status);
    CHECK_STAT(status);
    status = plg.setValue(w);
    CHECK_STAT(status);
    plg = fnP.findPlug("h",&status);
    CHECK_STAT(status);
    status = plg.setValue(h);
    CHECK_STAT(status);
    plg = fnP.findPlug("d",&status);
    CHECK_STAT(status);
    status = plg.setValue(d);
    CHECK_STAT(status);
    plg = fnP.findPlug("sw",&status);
    CHECK_STAT(status);
    status = plg.setValue(sdw);
    CHECK_STAT(status);
    plg = fnP.findPlug("sh",&status);
    CHECK_STAT(status);
    status = plg.setValue(sdh);
    CHECK_STAT(status);
    plg = fnP.findPlug("sd",&status);
    CHECK_STAT(status);
    status = plg.setValue(sdd);
    CHECK_STAT(status);
    plg = fnP.findPlug("tx",&status);
    CHECK_STAT(status);
    status = plg.setValue(tex);
    CHECK_STAT(status);
 
    MPlug axx = fnP.findPlug("axx",&status);
    CHECK_STAT(status);
    MPlug axy = fnP.findPlug("axy",&status);
    CHECK_STAT(status);
    MPlug axz = fnP.findPlug("axz",&status);
    CHECK_STAT(status);
 
    switch (axis)
    {
    case 0:
      status = axx.setValue(1.0);
      CHECK_STAT(status);
      status = axy.setValue(0.0);
      CHECK_STAT(status);
      status = axz.setValue(0.0);
      CHECK_STAT(status);
      break;
    case 1:
      status = axx.setValue(0.0);
      CHECK_STAT(status);
      status = axy.setValue(1.0);
      CHECK_STAT(status);
      status = axz.setValue(0.0);
      CHECK_STAT(status);
      break;
    default:
    case 2:
      status = axx.setValue(0.0);
      CHECK_STAT(status);
      status = axy.setValue(0.0);
      CHECK_STAT(status);
      status = axz.setValue(1.0);
      CHECK_STAT(status);
      break;
    }    
  }
 
  // now assign the mesh to the default shading group.
  {
    MFnDagNode fnM(oM,&status);
    CHECK_STAT(status);
    status = fnM.getPath(mPath);
    CHECK_STAT(status);
  }
  return redoIt();
}
 
//---------------------------------------------------------------------------------
MStatus WasteOfTime::redoIt()
{
  MStatus status = mDagMod.doIt();
  CHECK_STAT(status);
 
  status = MGlobal::selectByName("initialShadingGroup",MGlobal::kReplaceList);
  CHECK_STAT(status);
 
  MSelectionList sl;
  status = MGlobal::getActiveSelectionList(sl);
  CHECK_STAT(status);
 
  MObject o;
  status = sl.getDependNode(0,o);
  CHECK_STAT(status);
  MFnSet fnS(o,&status);
  CHECK_STAT(status);
 
  status = fnS.addMember(mPath,MObject::kNullObj);
  CHECK_STAT(status);
 
  {
    MDagPath p = mPath;
    status = p.pop();
    CHECK_STAT(status);
 
    MFnExpression fnE;
    MObject oT = p.node();
    CHECK_STAT(status);
    mExpression = fnE.create(MString("tx = time"),oT,&status);
    CHECK_STAT(status);
  }
 
  return status;
}
 
//---------------------------------------------------------------------------------
MStatus WasteOfTime::undoIt()
{
  MStatus status;
 
  status = MGlobal::selectByName("initialShadingGroup",MGlobal::kReplaceList);
  CHECK_STAT(status);
 
  MSelectionList sl;
  status = MGlobal::getActiveSelectionList(sl);
  CHECK_STAT(status);
 
  MObject o;
  status = sl.getDependNode(0,o);
  CHECK_STAT(status);
  MFnSet fnS(o,&status);
  CHECK_STAT(status);
 
  status = fnS.removeMember(mPath,MObject::kNullObj);
  CHECK_STAT(status);
 
  status = MGlobal::deleteNode(mExpression);
  CHECK_STAT(status);
 
  return mDagMod.undoIt();
}
 
#include <maya/MFnPlugin.h>
 
//---------------------------------------------------------------------------------
#ifdef WIN32
#pragma comment(lib,"Foundation.lib")
#pragma comment(lib,"OpenMaya.lib")
#endif
 
//---------------------------------------------------------------------------------
#ifdef WIN32
#define EXPORT __declspec(dllexport)
#else
#define EXPORT
#endif
 
//---------------------------------------------------------------------------------
EXPORT MStatus initializePlugin(MObject obj)
{
  MStatus status;
  MFnPlugin fnPlugin( obj, "Rob Bateman", "1.0", "Any");
  status = fnPlugin.registerCommand( "wasteOfTime",WasteOfTime::creator,WasteOfTime::newSyntax);
  CHECK_STAT(status);
  return status;
}
 
//---------------------------------------------------------------------------------
EXPORT MStatus uninitializePlugin(MObject obj)
{
  MStatus status;
  MFnPlugin fnPlugin(obj);
  status = fnPlugin.deregisterCommand( "wasteOfTime" );
  CHECK_STAT(status);
  return status;
}

Conclusion

J'espere que cette traduction vous servira. Je ne crois pas qu'il existe ce genre d'information en français (cela dis, il ne doit pas y avoir beaucoups de dev qui utilise l'API Maya...) :franceHappy:

Cela dis, depuis qu'il est possible de faire des plugins et des nodes Maya directement en Python je m'interesse un peu à comment l'API fonctionne. Cité plusieurs fois, le site de Rob est très interessant car il fourmille de codes, exemples, explications sur l'API Maya. Malgrès le fait qu'il soit en C++, si vous commencez à avoir l'habitude de l'API Maya python et que vous connaissez un peu le C++, vous n'aurez (presque) aucun soucis à faire la transition. :sourit:

Voila, je n'ai pas grand chose à dire si ce n'est: "+1" car je trouve que les choses sont bien résumés.

A bientôt!

:marioCours: