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する必要がありますので、結構忘れがちです。
忘れると当然ながらエラーになります・・・
import maya.api.OpenMaya as om
#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 の為、エラーが発生します。
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
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() を使用することでも、エラーを発生させることができます。
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
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 を返しています。
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])
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させることができませんでした。
def undoIt(self):
self.transformFn_Source.setTransformation(self.matrix_Source)
def isUndoable(self):
return True
MStatus matchTransform2::undoIt()
{
transformFn_Source.set(matrix_Source);
return MS::kSuccess;
}
bool matchTransform2::isUndoable() const
{
return true;
}
#コマンドクリエイト
creator は単にオブジェクトのインスタンスを返しています。
@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
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++には不必要な関数です。
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
)
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さんの記事です!!