チュートリアル / 読んで触ってよくわかる!Mayaを使いこなす為のAtoZ
第68回:AttributeとPlugについて語る

2017.01.16

  • Maya
  • ゲーム
  • チュートリアル
  • 中級者
  • 学生・初心者
  • 教育
  • 映画・TV

Mayaを使っていると「アトリビュート」というものが出てきます。他のソフトで「プロパティ」と呼ばれるものです。

アトリビュートをいじるコマンドにはAttrやAttributeという名前がついています。値の設定はsetAttr、接続はconnectAttr、アトリビュートの作成はaddAttrという風に。アトリビュートの情報を調べるのはattributeQueryコマンドです。

ところがMayaのAPIを触ると、Attributeという名前のついたクラスでは値を取ったり設定したりすることが出来ないことに気づきます。アトリビュートの値を操作するには「プラグ」という物を使います。このプラグ、スクリプトには出てこない代物なので、始めてMayaのAPIを使う人にとってつまずきやすいポイントです。

今回はMayaのAPI側から見た「アトリビュート」と「プラグ」の関わりについて見ていきたいと思います。

そもそも「アトリビュート」って何?

ではたとえ話を一つ…。

アパートやマンションには集合の郵便受けがあります。
101, 210, 256, 303, 402, 512
といった数字になっていて、どのマンションでも百の桁は階数で…という共通のルールがあります。

隣のマンションにも101という郵便受けがあり、向かいのマンションにも101という郵便受けがあります。
この「101」という名前は、どのマンションにも共通した郵便受け番号となっています。

「ハガキを101に届けてください」と送っても、どのマンションかわからないので届けようがありません。
必ず「オートデスクマンション.101」に届けてくださいというように、マンション名と郵便受け番号が揃っていないと届きません。

さてこれをMayaの仕組みに当てはめてみましょう:P
マンションがノードで、郵便受けがアトリビュートの役割を果たしています。

マンションがノードで、郵便受けがアトリビュートの役割を果たす

たとえばTransformというノードタイプではrotateというアトリビュートがあります。
でも「rotateをx=0, y=90, z=45にして」と命令をMayaに出しても、具体的にどのノードのrotateかわかりません。必ず「polyCube1.rotate」というようにノード名とアトリビュート名を指定します。

「ノード+アトリビュート」を組み合わせた情報がないと値を操作できません。そこでプラグというものが登場します。

プラグって何?

また先ほどの流れで、郵便受けを使った例え話を。

住所を指定してなにか届けるのに、郵便や宅配を使いますよね。
送るものには、ハガキもあれば封筒、小包など様々な物があります。

プラグは郵便や宅配業者のようなものです。
指定した住所の郵便受け(ノードのアトリビュート)に荷物(データ)を運んでくれます。
ユーザーが自分で直接郵便受けにハガキを持っていくと若干怪しまれるので、プラグという専門業者に頼みます。

プラグ

プラグと呼ばれるものは、どのノードのどのアトリビュートを指し示しているかという情報を知っています。API内ではMPlugというクラスがプラグにあたります。

APIでノードの値を取る例

実際にMPlugを使ってノードの値を取って見ましょう。
今回はPython API 2.0を使用します。と言っても、単に読み込むPythonのモジュールがいつもと異なるだけです。Python API 2.0では、APIの機能がPython流に使えるので、コードが短くなってわかりやすいです。

シーンに円柱を作って、選択します。
次のコードを実行すると、選択しているオブジェクトのTranslateのXYZ値が出力されます。

			
import maya.api.OpenMaya as om

# 選択をリストとして取得。APIではMSelectionListというタイプのリストになります。
selList = om.MGlobal.getActiveSelectionList()

for i in range(selList.length()):
	# 指定した番号のMDagPathをリストから取り出し、そのfullPathName関数でノード名を得ます。
	print selList.getDagPath(i).fullPathName()

	# getDependNode(i)で指定した番号のMObjectを取り出す。MFnDependencyNodeにそのまま設定。
	dnFn = om.MFnDependencyNode( selList.getDependNode(i) )

	# findPlug関数にアトリビュート名を指定して、プラグを取得。
	# (二つ目の引数はとりあえずFalseに。)
	translatePlug = dnFn.findPlug('translate', False)
	
	# translatePlug(MPlug)には、translateという
	# tx,ty,tzの親となるアトリビュートが記録されています。
	# tx, ty, tzの値を取り出すには、plugの子のプラグを得て、その値を出力します。
	txPlug = translatePlug.child(0)
	tyPlug = translatePlug.child(1)
	tzPlug = translatePlug.child(2)

	# APIでは取得する値の型を正確に指定する必要があります。
	print 'translateX:', txPlug.asDouble()
	print 'translateY:', tyPlug.asDouble()
	print 'translateZ:', tzPlug.asDouble()

		

いろいろな処理が行なわれていますが、注目すべきは真ん中あたりでfindPlugが使われている所からです。

MELコマンドなら、getAttr pCylinder1.translate; で値を取れます。
APIでは細かい処理を全て実装する必要があります。

ノードのアトリビュートに対するMPlugを得る方法はいくつかありますが、ここではMFnDependencyNodeのfindPlug関数を使っています。
まず対象のノードをMFnDependencyNodeクラスに設定してからfindPlug関数を実行します。すると、そのノードのアトリビュートへのプラグが返されます。プラグにはノードとアトリビュートが記録されています。

最後にプラグのas〇〇という関数を使って、アトリビュートの型に合わせて値を取り出します。

随分と手順が多いように思えますが、Mayaのコマンドではこういった細かい処理がひとまとめで行なわれるようにしてあります。
どのソフトでも本格的なAPIではこのぐらいの手順を踏むのが普通ですので、慣れると気にならなくなりますし、MELコマンドより細かい扱いが出来る場合があります。

値によってかわるデータの取り扱い方

ハガキ一枚なら、見れば何が書いてあるかすぐわかります。
でも沢山の情報が入っている封書だと、ペーパーナイフか何かで封を開けなければ中身を見られません。

沢山の情報が入っている封書だと、ペーパーナイフか何かで封を開けなければ中身を見られません。

Mayaも同じで、単純な数値や文字列なら上のサンプルのようにプラグから簡単に取り出せますが、メッシュや配列など複雑なデータは専用のツールを使わなければ中身が取り出せません。

データを取り出す一般的な手順としては、
1. プラグのasMObject関数を実行して、データのMObjectを取得。
2. MObjectを「MFnデータ型名」のクラスに設定。例えばmeshデータならMFnMesh。
3. MFn〇〇クラスの関数を使用してデータを取得、加工。

プラグにはasDoubleやasIntといったas〇〇の関数が用意されていますので、asMeshなど用意されていそうに思いますが、データ型ごとにas〇〇を用意していると大量のas〇〇が用意されることになり、スマートではありません。
代わりにMObjectを取得し、そのMObjectをデータに適したMFn〇〇に設定するようにします。この方法ならユーザーが新しいデータを追加しても、MPlugはMObjectを介するだけなので問題なく動作します。データを実際に取り出すのはMFn〇〇が担当しますので、ユーザーが自分のMFn〇〇を拡張すれば万事OKなのです。

概念図

MObjectでデータを渡さないといけないかどうかは、各MFn〇〇のコンストラクタを見るとわかります。大抵コンストラクタの引数にMObjectが指定できるようになっています。

メッシュを選択して次のコードを実行すると、ポリゴン数が出力されます。

import maya.api.OpenMaya as om

# 選択をリストとして取得。APIではMSelectionListというタイプのリストになる。
selList = om.MGlobal.getActiveSelectionList()

# 選択のリストにはTransformノードが入っています。
# ここではメッシュのデータが欲しいので、
# 選択しているTransformの下にあるmeshシェープのパスを取得します。
# extendToShape()で、dagPathの中身がメッシュへのパスへ変更されます。
dagPath = selList.getDagPath(0)
dagPath.extendToShape()

# プラグを取り出すためにMFnDependencyNodeを作成。
dnFn = om.MFnDependencyNode( dagPath.node() )

# findPlug関数に、メッシュデータのアトリビュート名を指定。
plug = dnFn.findPlug('outMesh', False)

# プラグの値をMObjectとして取得。
# MFnMeshに渡して、MFnMesh経由でメッシュデータを取り出します。
obj = plug.asMObject()
meshFn = om.MFnMesh(obj)

print 'num of polygons:', meshFn.numPolygons
		

ちなみにMFnから始まるクラス名のクラスはファンクションセットと呼ばれます。
MFnの後に続くクラス名が扱えるデータを示しています。対象のデータのツールとして機能しますので、データを編集、削除、追加等出来ます。

MPlugという運び屋を介して値を郵便受け(Attribute)に投函したり、持ち出してきたり(…)する。
ハガキ(数値や文字列)はそのまま読めばいいけど、小包(複雑なデータ)はハサミなど専用のツール(MFn〇〇)を使って開ける。

という仕組みになっています。
分かるようなわからないような感じかもしれませんが、一応処理の流れは掴んでいただけると思います。

まとめ

APIでのアトリビュートの扱いがなんとなくわかってもらえたでしょうか?
つまるところ、APIでsetAttr, getAttrをするときはMPlugというプラグを使うということです。

C++での場合はAPIでMPlugを使うことが基本ですが、PythonでAPIを使う場合は、まあ、実のところPythonはコマンドを呼び出すことも出来るわけですから、単に値を取得したり設定したりするだけなら、あえてMPlugを使う必要もないです。setAttr/getAttrを使ったほうが簡単です。処理速度もそこまで変わらないです。(内部的には、自前でMPlugを使って値を取るのと同じようなことをしていますし。)

APIを触ると3DCGの仕組みの部分がとても良く見えてきます。3DCGはコンピューテーションの中でも複雑な分野ですから、基礎的な部分を垣間見ることでより深くCGを理解できるようになります。
こういった基礎知識は3DCGのどのソフトやゲームエンジンでも当てはまることですので、ひいてはTAとしてのキャリアを大きく切り開くことになります。

何よりも、技術的なことが好きな人にとっては、わかってくるととてもおもしろい分野です。 以前はプラグインをビルドする環境がないと手が出せなかったOpen Mayaですが、今はPythonで簡単に使用できるようになっていますので、興味がある方は是非APIの世界へ飛び込んでみてください。

著者

築島 智之

築島 智之

オートデスク株式会社
オートデスクコンサルティング
ソフトウェアエンジニア