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する必要がありますので、結構忘れがちです。
忘れると当然ながらエラーになります・・・

```Python:matchTransform2.py
import maya.api.OpenMaya as om
```

```cpp: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 の為、エラーが発生します。

```Python: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
```

```cpp: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() を使用することでも、エラーを発生させることができます。 


```Python: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
```

```cpp: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 を返しています。

```Python: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])
```

```cpp: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させることができませんでした。

```Python:matchTransform2.py
    def undoIt(self):
        self.transformFn_Source.setTransformation(self.matrix_Source)
 
    def isUndoable(self):
        return True
```

```cpp:matchTransform2.cpp
MStatus matchTransform2::undoIt()
{
	transformFn_Source.set(matrix_Source);
	return MS::kSuccess;
}


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


#コマンドクリエイト
creator は単にオブジェクトのインスタンスを返しています。


```Python: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
 
```

```cpp: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++には不必要な関数です。


```Python: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
        )
```

```cpp: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さんの記事です！！