Blenderとか3DCGとか

スクリプト寄りです。

カレントでない時間でのアトリビュート値を取得する (Maya)

本日受講したセミナー

Maya のしくみ ~ 基礎から学ぶ DG とパラレル評価、そしてキャッシュプレイバック ~ | ボーンデジタル

にて佐々木さんが最後に「公式の標準機能が、MDGContextを用いて違う時間帯の評価を行うようになったということは・・・」とおっしゃっていました。(ニュアンスは多少違うかもしれませんが) 

機能としての提供はあったが、基本機能への投入実績ができた以上、Mayaにとっての標準が新たに定義されたということです。
 大変になるなぁと思うと同時に、今後はそのあたりの内部機能が安定していくだろうと思う次第でした。

ともあれ、カレントフレーム以外でのアトリビュート値を取得する方法を紹介いたします。
 次のような条件では特に役立ちます。

  • タイムスライダを動かせば値は判る
  • でもシーンの更新に時間がかかる
  • 知りたいのはチャンネルボックスの値だけ

想定

pCube1のtranslateXについて、frame11での値を知りたい

準備

キューブを作成してtranslateXのアニメーションをつけます。 f:id:masayuki-osaka-blend:20190711214929p:plain

指定フレームでの値の取得

以下のスクリプトを実行することで、11frameでの値を取得できます。

import maya.api.OpenMaya as om2
# pCube1という名前から、apiで扱うためのリストを生成
sel = om2.MGlobal.getSelectionListByName('pCube1')
# リストから0番目(pCube1)の物体を、Dagとして取り扱える状態で取得
dag = sel.getDagPath(0)
# Dagをtransformノードとして扱えるようにする
trn = om2.MFnTransform(dag)
# transformノードのtranslateXを管理する物(プラグ)を取得
plug = trn.findPlug('tx', 0)

# 現在のfps設定を、時間を管理するクラス(MTime)から取得
fps = om2.MTime().unit
# "現在のfpsでの、11frame" に該当するタイムオブジェクトを生成
frame_11 = om2.MTime(11.0, fps)
# "現在のfpsでの、11frame" を表す、時間に関する文脈を生成
context = om2.MDGContext(frame_11)
# translateXを管理するプラグへ "現在のfpsでの、11frame" という文脈を伝え、それに応じた値をfloatで受け取る
value = plug.asFloat(context)

print(value)

この図では、コマンド実行後にタイムスライダで11frame目を表示させていますが、カレントフレームがどこであっても同じ結果が得られます。
f:id:masayuki-osaka-blend:20190711221110p:plain

カレントフレームでの値を取得

カレントフレームでの値を取得したい場合には、以下のように後半を差し替えます

import maya.api.OpenMaya as om2
# pCube1という名前から、apiで扱うためのリストを生成
sel = om2.MGlobal.getSelectionListByName('pCube1')
# リストから0番目(pCube1)の物体を、Dagとして取り扱える状態で取得
dag = sel.getDagPath(0)
# Dagをtransformノードとして扱えるようにする
trn = om2.MFnTransform(dag)
# transformノードのtranslateXを管理する物(プラグ)を取得
plug = trn.findPlug('tx', 0)
# ここまでは同じ

# "現在のfps、現在のフレーム" を表す、時間に関する文脈を生成
context = om2.MDGContext(om2.MDGContext.kNormal)
# translateXを管理するプラグへ "現在のfpsでの、11frame" という文脈を伝え、それに応じた値をfloatで受け取る
value = plug.asFloat(context)
print(value)

MDGContext.kNormal が「Normal=通常=今」を示すので、現在のfps, 現在のフレームを扱えます。
f:id:masayuki-osaka-blend:20190711222422p:plain

頂点のワールド座標を取得

メッシュシェイプに関しては、outMeshアトリビュートへのアクセスによってカレントフレーム以外の状態を取得できます。
 ただし、outMeshアトリビュートを通して取得した形状はローカル座標しか扱えません。そのため、メッシュの親ノードのワールドマトリクスを取得し、それを使用して頂点座標値の変換を行う必要があります。

import maya.api.OpenMaya as om2
# pCubeShape1という名前から、apiで扱うためのリストを生成
sel = om2.MGlobal.getSelectionListByName('pCubeShape1')
# リストから0番目(pCubeShape1)の物体を、Dagとして取り扱える状態で取得
odag = sel.getDagPath(0)
# DagをMeshとして扱えるようにする
mmesh = om2.MFnMesh(odag)
# メッシュの最終的な形(outMeshアトリビュート)を管理するプラグを取得
plug_outmesh = mmesh.findPlug('outMesh', 0)

# "現在のfpsでの、11frame" を表す、時間に関する文脈を生成
context = om2.MDGContext(om2.MTime(11, om2.MTime().unit))
# Meshの "現在のfpsでの11frame" にあたる形を取得
omeshdata = plug_outmesh.asMObject(context)
# "現在のfpsでの11frame" にあたる形をMeshとして扱えるようにする
# (カレントタイムのMeshとは別物)
time_shifted_mmesh = om2.MFnMesh(omeshdata)
# 各頂点のローカル座標を取得(outMesh経由で取得したものはワールド座標がない)
# 最後をgetPoints(om2.MSpace.kWorld)にしてもローカル座標になる
points = time_shifted_mmesh.getPoints()

# 親階層(pCube1)のworldMatrixを管理するプラグを取得
plugs_mtrx = mmesh.findPlug('parentMatrix', 0)
# parentMatrixはマルチアトリビュートなので、parentMatrix[0]のみを考慮
plug_mtrx = plugs_mtrx.elementByLogicalIndex(0)
# parentMatrix[0]のプラグから "現在のfpsでの11frame" のworldMatrixを取得
omtrxdata = plug_mtrx.asMObject(context)
# matrixとして扱えるようにする
mmtrxdata = om2.MFnMatrixData(omtrxdata)
# matrixを取得(親階層pCube1のworldMatrix)
mmtrx = mmtrxdata.matrix()

# 各頂点のローカル座標へ親階層のworldMatrixを適用
for pt in points:
    pt *= mmtrx

# 各頂点位置がワールド座標になっていることを確認
import maya.cmds as cmds
group = cmds.group(em=1, w=1)
for pt in points:
    locator = cmds.spaceLocator(p=(pt.x, pt.y, pt.z))
    cmds.parent(locator, group)

あとがき

多少無理してでも参加した甲斐のある、とても濃密なセミナーでした。
 そして、Mayaは「とことん突き詰める人」向きなのだと改めて思ったり。
そうでなければ?Evaluation ToolkitではなくFix Toolkitが無難なラインで提供されていたと思います。

久しぶりにプロ向けのセミナーに参加した事もあり、失礼もあったかと思いますが、引き続きセミナーにアンテナを張っていきたい、と思った次第です。