13
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

MayaAdvent Calendar 2020

Day 12

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

Last updated at Posted at 2020-12-11

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さんの記事です!!

13
8
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?