Maya Python API 2.0 のプラグインをC++で書きかえる


Maya Advent Calender 2020も12日目です。

当記事では、Maya Python API 2.0とC++のコードを比較することで、「OpenMayaでプラグインを制作することができれば、なんとなくC++でも作れるよ」という事をお伝え出来ればと思います。

私自身、C++を使った業務経験はなく、そこまで理解度が進んでいる状態ではありません。ただ、動作するプラグインは制作することができました。C++化するにあたり、発生したエラーも記載しています。
C++でプラグインを書き始める切欠になれば幸いです。

はじめに

今回用意しましたのが、選択オブジェクトのTransformを一致させるコマンドです。
引数は、translation(t)、rotation(r)、scale(s)を指定することで、それぞれの要素ごとに一致させる事ができます。
お気づきの方もいると思いますが、既存の『matchTransform』コマンドとほぼ同じです。(知らずに作っていました。)
ちなみに、既存のコマンドとノードを選択順番が異なり、コンストレインコマンドと同じ感覚のノード選択で使用できます。

コード

matchTransform2.py
https://github.com/4jigenshiteiC/Maya_script/blob/master/matchTransform2.py

matchTransform2.cpp
https://github.com/4jigenshiteiC/Maya_script/blob/master/matchTransform2.cpp

モジュール読み込み

Pythonでは、maya.api.OpenMaya モジュールをimportします。

C++では、使用するクラスをincludeする必要があります。
コード内でクラスを使用する毎にincludeする必要がありますので、結構忘れがちです。
忘れると当然ながらエラーになります・・・

matchTransform2.py
import maya.api.OpenMaya as om
matchTransform2.cpp
#include <maya/MPxCommand.h> 
#include <maya/MFnPlugin.h> 
#include <maya/MSyntax.h> 
#include <maya/MArgParser.h>  
#include <maya/MSelectionList.h>  
#include <maya/MGlobal.h> 
#include <maya/MObject.h >
#include <maya/MItSelectionList.h >
#include <maya/MFnTransform.h >
#include <maya/MTransformationMatrix.h >
#include <maya/MMatrix.h >
#include <maya/MFnDependencyNode.h >
#include <maya/MObject.h >
#include <maya/MPlug.h >
#include <maya/MFnMatrixData.h >
#include <maya/MEulerRotation.h >
#include <maya/MVector.h >
#include <maya/MQuaternion.h >
#include <maya/MDagPath.h >

クラスと変数の指定と初期化

クラスを用意して、プラグイン名とコマンドのフラグ名を指定しています。
引数を判定する変数を用意しています。

pythonを触っているとあまり変数の型を意識することはないですが、C++ではしっかりと型指定する必要があります。
変数の宣言に static をつけることで、静的メモリを確保します。
変数の宣言に const をつけることで、その変数の値が書き換えられないようにできます。

matchTransform2::matchTransform2(){} は、Pyhtonでいう def init(self): に当たります。
matchTransform2::~matchTransform2(){} は、Pythonと異なり、破棄した時の動作を定義することができます。

変数の文字列の型char* で定義しています。
文字列の型として std::string という書き方もありますが、MArgParser::getFlagArgumentの引数の型はchara の為、エラーが発生します。

matchTransform2.py
class matchTransform2(om.MPxCommand):
    """PYTHON
    A command that prints a string to the console.
    """

    #   the name of the command
    kPluginCmdName = "matchTransform2"

    # requests translation information   
    kTranslationFlag = '-t'
    kTranslationLongFlag = '-translation'

    # requests rotation information
    krotationFlag = '-r'
    krotationLongFlag = '-rotation'

    # requests Scale information
    kScaleFlag = '-s'
    kScaleLongFlag = '-scale'


    def __init__(self):
        """
        Initialize the instance.
        """
        om.MPxCommand.__init__(self)

        self.__translationArg = True
        self.__rotationArg = True
        self.__scaleArg = True
matchTransform2.cpp
class matchTransform2 : public MPxCommand
{
public:
    matchTransform2();//コンストラクター
    virtual ~matchTransform2(); //仮想関数 デストラクター
    static void* cmdCreator();
    static  MSyntax syntaxCreator();
    MStatus doIt(const MArgList& arg);
    static  MStatus redoIt();
    static  MStatus undoIt();
    static  MStatus doItQuery();

    static const char*  kPluginCmdName;
    static const char*  kTranslationFlag;
    static const char*  kTranslationLongFlag;
    static const char*  krotationFlag;
    static const char*  krotationLongFlag;
    static const char*  kScaleFlag;
    static const char*  kScaleLongFlag;

    static bool __translationArg;
    static bool __rotationArg;
    static bool __scaleArg;
};

static MDagPath DagPath_Source;
static MObject mObjct_Target;
static MFnTransform transformFn_Source;
static MTransformationMatrix matrix_Source;
static MObject mObjct_Source;

//  静的な変数宣言
//the name of the command
const char*  matchTransform2::kPluginCmdName = "matchTransform2";

//requests translation information
const char*  matchTransform2::kTranslationFlag = "-t";
const char*  matchTransform2::kTranslationLongFlag = "-translation";

//requests rotation information
const char*  matchTransform2::krotationFlag = "-r";
const char*  matchTransform2::krotationLongFlag = "-rotation";

//requests rotation information
const char*  matchTransform2::kScaleFlag = "-s";
const char*  matchTransform2::kScaleLongFlag = "-scale";

bool  matchTransform2::__translationArg = true;
bool  matchTransform2::__rotationArg = true;
bool  matchTransform2::__scaleArg = true;

//クラス定義
matchTransform2::matchTransform2()
{
}

matchTransform2::~matchTransform2()
{
}

doIt 関数

doItは、コマンドを実行した際に、最初に処理される関数です。
選択ノードの取得や引数の解析を行っています。

Pythonでは、try/except で例外処理を行っています。
C++では、MStatus クラスで、メソッドが失敗したかどうかを判断します。
MGlobal::getActiveSelectionList()など、いろんなクラスの関数は Status を返してくれます。

MGlobal::displayError() を使用することで、エラーを発生させることができます。
MStatus::perror() を使用することでも、エラーを発生させることができます。

matchTransform2.py
    def doIt(self, args):
        """
        Parse arguments and then call redoIt()
        """
        # parse the arguments
        try: argData = om.MArgDatabase(self.syntax(), args)
        except: pass
        else:
            try:
                sList = argData.getObjectList()
                if sList.length() != 2:
                    raise Exception(
                        'This command requires exactly 2 argument to be specified or selected;')

                iter = om.MItSelectionList(sList, om.MFn.kTransform)

                count = 0
                while not iter.isDone():  
                    if count == 0:
                        self.mObjct_Target = iter.getDependNode()
                    else:
                        self.mObjct_Source = iter.getDependNode()
                        self.DagPath_Source = iter.getDagPath()

                        self.transformFn_Source = om.MFnTransform()
                        self.transformFn_Source.setObject(self.DagPath_Source)
                        self.matrix_Source = self.transformFn_Source.transformation()

                    count += 1
                    iter.next()

                if argData.isFlagSet(matchTransform2.kTranslationFlag):
                    self.__translationArg = argData.flagArgumentInt(matchTransform2.kTranslationFlag, 0)
                if argData.isFlagSet(matchTransform2.krotationFlag):
                    self.__rotationArg = argData.flagArgumentInt(matchTransform2.krotationFlag, 0)
                if argData.isFlagSet(matchTransform2.kScaleFlag):
                    self.__scaleArg = argData.flagArgumentInt(matchTransform2.kScaleFlag, 0)

                self.redoIt()
            except: pass
matchTransform2.cpp
MStatus matchTransform2::doIt(const MArgList& args)
{
    MStatus status;
    MArgParser    argData( syntaxCreator(), args, &status );


    MSelectionList slist;
    status = MGlobal::getActiveSelectionList(slist);

    if (slist.length() != 2)
    {
        MGlobal::displayError("This command requires exactly 2 argument to be specified or selected;");
        return status;
    }

    MItSelectionList iter(slist, MFn::kTransform);
    int count = 0;
    while (not iter.isDone())
    {
        if (count == 0)
        {

            iter.getDependNode(mObjct_Target);
        }
        else
        {
            iter.getDependNode(mObjct_Source);
            iter.getDagPath(DagPath_Source);
            transformFn_Source.setObject(DagPath_Source);
            matrix_Source = transformFn_Source.transformation();
        }

        count += 1;
        iter.next();
    }

    //  TranslationFlagを解析
    if (argData.isFlagSet(kTranslationFlag))
    {
        bool tmp;
        status = argData.getFlagArgument(kTranslationFlag, 0, tmp);
        if (!status) {
            status.perror("upside down flag parsing failed.");
            return status;
        }
        __translationArg = tmp;
    }

    //  krotationFlagを解析
    if (argData.isFlagSet(krotationFlag))
    {
        bool tmp;
        status = argData.getFlagArgument(krotationFlag, 0, tmp);
        if (!status) {
            status.perror("upside down flag parsing failed.");
            return status;
        }
        __rotationArg = tmp;
    }

    //  kScaleFlagを解析
    if (argData.isFlagSet(kScaleFlag))
    {
        bool tmp;
        status = argData.getFlagArgument(kScaleFlag, 0, tmp);
        if (!status) {
            status.perror("upside down flag parsing failed.");
            return status;
        }
        __scaleArg = tmp;
    }

    return redoIt();

redoIt 関数

doIt 関数で解析した値を利用して、メインの処理を行っています。
処理としては、マトリックスを用いて位置合わせをさせています。

Python API 2.0 では存在するが、C++には無い関数がありますので、置き換えが必要になります。例えば、mTransformationMatrix クラスの関数では、translation 関数やscale関数はありませんでした。その為、getTranslation 関数やgetScale 関数に変更しています。

C++では、関数の完了時に操作が問題なく完了したことをMayaに通知する為に、MS::kSuccess を返しています。

matchTransform2.py
    def redoIt(self):
        #   Get target node matrix.
        mFnD_Target = om.MFnDependencyNode(self.mObjct_Target)
        world_matrix_attr_Target = mFnD_Target.attribute("worldMatrix")
        matrix_plug_Target = om.MPlug(self.mObjct_Target, world_matrix_attr_Target).elementByLogicalIndex(0)
        world_matrix_data = om.MFnMatrixData( matrix_plug_Target.asMObject()).matrix()

        #   Get Source node parent matrix.
        mFnD_Source = om.MFnDependencyNode(self.mObjct_Source)
        parent_matrix_attr_Source = mFnD_Source.attribute("parentMatrix")
        parent_matrix_plug_Source = om.MPlug(self.mObjct_Source, parent_matrix_attr_Source).elementByLogicalIndex(0)
        parent_matrix_data = om.MFnMatrixData( parent_matrix_plug_Source.asMObject()).matrix()

        #   Get order of target node.
        rotateOrderPlag = mFnD_Source.findPlug("rotateOrder",False)
        rotateOrder = rotateOrderPlag.asInt()

        #   Get Transform value.
        Matrix = world_matrix_data * parent_matrix_data.inverse()

        mTransformationMatrix = om.MTransformationMatrix(Matrix)
        mTransformationMatrix.reorderRotation( rotateOrder + 1 )
        pos = mTransformationMatrix.translation(om.MSpace.kWorld)
        scl = mTransformationMatrix.scale(om.MSpace.kWorld)
        rot = mTransformationMatrix.rotation(False)

        #   Set Transform value.
        if self.__translationArg:
            translatePlag = mFnD_Source.findPlug("translate",False)
            translatePlag.child(0).setDouble(pos.x)
            translatePlag.child(1).setDouble(pos.y)
            translatePlag.child(2).setDouble(pos.z)

        if self.__rotationArg:
            rotatePlag = mFnD_Source.findPlug("rotate",False)
            rotatePlag.child(0).setDouble(rot.x)
            rotatePlag.child(1).setDouble(rot.y)
            rotatePlag.child(2).setDouble(rot.z)

        if self.__scaleArg:  
            scalePlag = mFnD_Source.findPlug("scale",False)
            scalePlag.child(0).setDouble(scl[0])
            scalePlag.child(1).setDouble(scl[1])
            scalePlag.child(2).setDouble(scl[2])
matchTransform2.cpp
MStatus matchTransform2::redoIt()
{
    //  Get target node matrix.
    MFnDependencyNode mFnD_Target(mObjct_Target);

    MObject world_matrix_attr_Target = mFnD_Target.attribute("worldMatrix");

    MPlug matrix_plug(mObjct_Target, world_matrix_attr_Target);
    MPlug matrix_plug_Target = matrix_plug.elementByLogicalIndex(0);

    MFnMatrixData world_FnMatrixData(matrix_plug_Target.asMObject());
    MMatrix world_matrix_data = world_FnMatrixData.matrix();


    //   Get Source node parent matrix.
    MFnDependencyNode mFnD_Source(mObjct_Source);

    MObject parent_matrix_attr_Source = mFnD_Source.attribute("parentMatrix");


    MPlug parent_matrix_plug(mObjct_Source, parent_matrix_attr_Source);
    MPlug parent_matrix_plug_Source = parent_matrix_plug.elementByLogicalIndex(0);

    MFnMatrixData   parent_FnMatrixData(parent_matrix_plug_Source.asMObject());
    MMatrix parent_matrix_data = parent_FnMatrixData.matrix();


    //   Get order of target node.
    MPlug rotateOrderPlag = mFnD_Source.findPlug("rotateOrder", false);
    int rotateOrder_index = rotateOrderPlag.asInt();

    //   Get Transform value.
    MMatrix Matrix = world_matrix_data * parent_matrix_data.inverse();

    MTransformationMatrix mTransformationMatrix(Matrix);
    mTransformationMatrix.reorderRotation( MTransformationMatrix::RotationOrder(rotateOrder_index + 1));


    MVector pos = mTransformationMatrix.getTranslation(MSpace::kWorld);

    MQuaternion q = mTransformationMatrix.rotation();
    MEulerRotation rot = q.asEulerRotation();

    double scl[3];
    mTransformationMatrix.getScale(scl, MSpace::kWorld);

    //   Set Transform value.
    if (__translationArg) {

        MPlug translatePlag = mFnD_Source.findPlug("translate", false);
        translatePlag.child(0).setDouble(pos.x);
        translatePlag.child(1).setDouble(pos.y);
        translatePlag.child(2).setDouble(pos.z);
    }

    if (__rotationArg) {

        MPlug rotatePlag = mFnD_Source.findPlug("rotate", false);
        rotatePlag.child(0).setDouble(rot.x);
        rotatePlag.child(1).setDouble(rot.y);
        rotatePlag.child(2).setDouble(rot.z);
    }

    if (__scaleArg) {
        MPlug scalePlag = mFnD_Source.findPlug("scale", false);
        scalePlag.child(0).setDouble(scl[0]);
        scalePlag.child(1).setDouble(scl[1]);
        scalePlag.child(2).setDouble(scl[2]);
    }

    return MS::kSuccess;
}

isUndoable 関数と undoIt関数

undoする為の処理です。

Pythonでは、setTransformation 関数でマトリックスの値をセットしています。
C++には、 setTransformation 関数が無いので、set 関数を使用しています。

isUndoable には const をつける必要があります。
ついていないと、undoさせることができませんでした。

matchTransform2.py
    def undoIt(self):
        self.transformFn_Source.setTransformation(self.matrix_Source)

    def isUndoable(self):
        return True
matchTransform2.cpp
MStatus matchTransform2::undoIt()
{
    transformFn_Source.set(matrix_Source);
    return MS::kSuccess;
}


bool matchTransform2::isUndoable() const
{
    return true;
}

コマンドクリエイト

creator は単にオブジェクトのインスタンスを返しています。

matchTransform2.py
    @classmethod
    def cmdCreator(cls):
        """
        Return pointer to proxy object.
        """
        return cls()

    @classmethod
    def syntaxCreator(cls):
        """
        Specify custom syntax
        """
        syntax = om.MSyntax()
        syntax.addFlag(cls.kTranslationFlag, cls.kTranslationLongFlag, om.MSyntax.kBoolean)
        syntax.addFlag(cls.krotationFlag, cls.krotationLongFlag, om.MSyntax.kBoolean)
        syntax.addFlag(cls.kScaleFlag, cls.kScaleLongFlag, om.MSyntax.kBoolean)
        syntax.useSelectionAsDefault(True) 
        syntax.setObjectType(om.MSyntax.kSelectionList, 2, 2)

        return syntax

matchTransform2.cpp
void* matchTransform2::cmdCreator()
{
    return (void *)(new matchTransform2);
}

MSyntax matchTransform2::syntaxCreator()
{

    //Specify custom syntax
    MSyntax syntax;
    syntax.addFlag(matchTransform2::kTranslationFlag, matchTransform2::kTranslationLongFlag, MSyntax::kBoolean);
    syntax.addFlag(matchTransform2::krotationFlag, matchTransform2::krotationLongFlag, MSyntax::kBoolean);
    syntax.addFlag(matchTransform2::kScaleFlag, matchTransform2::kScaleLongFlag, MSyntax::kBoolean);
    syntax.useSelectionAsDefault(true); 
    syntax.setObjectType(MSyntax::kSelectionList, 2, 2);
    return syntax;
}

initializePlugin 関数と uninitializePlugin 関数

MayaにPython API 2.0を使うことを明示する為にmaya_useNewAPI 関数を定義しています。C++には不必要な関数です。

matchTransform2.py
def maya_useNewAPI():
    """
    The presence of this function tells Maya that the plugin produces, and
    expects to be passed, objects created using the Maya Python API 2.0.
    """
    pass

def initializePlugin(obj):
    """
    Initialize the plug-in.
    """
    plugin = om.MFnPlugin(
        obj,
        'Shigehiro',
        '1.0',
        'Any'
    )
    try:
        plugin.registerCommand(
            matchTransform2.kPluginCmdName,
            matchTransform2.cmdCreator,
            matchTransform2.syntaxCreator
        )
    except:
        raise Exception(
            'Failed to register command: %s'%
            matchTransform2.kPluginCmdName
        )

def uninitializePlugin(obj):
    """
    Uninitialize the plug-in
    """
    plugin = om.MFnPlugin(obj)
    try:
        plugin.deregisterCommand(matchTransform2.kPluginCmdName)        
    except:
        raise Exception(
            'Failed to unregister command: %s'%
            matchTransform2.kPluginCmdName
        )
matchTransform2.cpp
MStatus initializePlugin(MObject obj)
{
    MStatus   status;//メソッドが失敗したかどうかを判断できる
    MFnPlugin plugin(obj, "Shigehiro", "1.0", "Any");
    status = plugin.registerCommand(
        matchTransform2::kPluginCmdName,
        matchTransform2::cmdCreator,
        matchTransform2::syntaxCreator
    );
    return status;
}


// プラグインの解除部分
MStatus uninitializePlugin(MObject obj)
{
    MStatus   status;
    MFnPlugin plugin(obj);
    status = plugin.deregisterCommand(matchTransform2::kPluginCmdName);
    return status;
}

まとめ

自分自身がそうなのですが、アート出身のTAにとって、C++は敷居が高い印象を持っていると思います。でも、OpenMayaと並べてみると、ほとんど同じことが分かったと思います。
リファレンスを見ながら、ググって進めて行けば、意外と書けると思います。
是非、皆様もC++に挑戦してみてください。


Maya Python Advent Calender 2020の13日目は、UnPySideさんの記事です!!