はじめに
mayaでpythonを使う時に、色んなモジュールの選択肢がありますね。
主によく使うのがこの4つの方法
- pymel.core (pymel)
- maya.cmds (mc)
- maya.OpenMaya (maya python API 1.0)
- maya.api.OpenMaya (maya python API 2.0)
それぞれの方法についての説明は割愛しますが、mayaの公式サイトなどですでに詳しいドキュメントが書かれています。
maya python APIについては公式サイトに書いてあります
https://help.autodesk.com/view/MAYAUL/2022/JPN/?guid=Maya_SDK_Maya_Python_API_html
mayaでのpythonのAPIは1.0と2.0がありますが、その違いについてはこちら https://help.autodesk.com/view/MAYAUL/2022/JPN/?guid=Maya_SDK_Maya_Python_API_PythonAPI1VsPythonAPI2_html
maya.cmdsの公式ドキュメントは
https://help.autodesk.com/cloudhelp/2022/JPN/Maya-Tech-Docs/CommandsPython/index.html
pymelは
https://help.autodesk.com/cloudhelp/2022/JPN/Maya-Tech-Docs/PyMel/index.html
pymelについてこの本もとても参考になります https://www.amazon.co.jp/gp/product/B079Z121TB/
書き方の比較
まずは書き方の違いを見せるために例を挙げたいと思います。
ここで使うのはmaya2022でpython3ですが、maya2021以前でのpython2とはあまり変わらないはずです。
例えばこんな5面ピラミッドの頂点の位置を取得したい場合です。
書き方を比べてみます。
pymel
まずは一番書きやすいpymelです。全部の頂点の位置を取得したい時に.getPoints()というメソッドがあるので書き方は簡単です。
import pymel.core as pm
mesh = pm.ls(sl=1)[0]
x = mesh.getPoints()
print(x)
結果はdt.Pointというxyzの位置を収めるクラスのオブジェクトのリストです。頂点が6なので、dt.Pointが6つ。
[dt.Point([1.0514626502990723, -1.051462173461914, -3.2360682487487793]), dt.Point([-2.7527637481689453, -1.051462173461914, -2.000000238418579]), dt.Point([-2.7527639865875244, -1.051462173461914, 1.9999998807907104]), dt.Point([1.051462173461914, -1.051462173461914, 3.2360680103302]), dt.Point([3.4026031494140625, -1.051462173461914, 0.0]), dt.Point([0.0, 1.051462173461914, 0.0])]
maya.cmds
次にmaya.cmdsはxformという関数があります。照会モードで使うと頂点や面やエッジの位置は簡単に取得できます。
詳しくは https://help.autodesk.com/cloudhelp/2022/JPN/Maya-Tech-Docs/CommandsPython/xform.html
import maya.cmds as mc
mesh = mc.ls(sl=1)[0]
x = mc.xform(mesh+'.vtx[*]',q=1,t=1)
print(x)
結果は6の頂点のxyzの位置を収めるリスト。長さは6×3=18
。
[1.0514626502990723, -1.051462173461914, -3.2360682487487793, -2.7527637481689453, -1.051462173461914, -2.000000238418579, -2.7527639865875244, -1.051462173461914, 1.9999998807907104, 1.051462173461914, -1.051462173461914, 3.2360680103302, 3.4026031494140625, -1.051462173461914, 0.0, 0.0, 1.051462173461914, 0.0]
API 1.0
そして一番古くて面倒くさい書き方であるAPI 1.0です。
import maya.OpenMaya as om1
sll = om1.MSelectionList()
om1.MGlobal.getActiveSelectionList(sll)
dagpath = om1.MDagPath()
sll.getDagPath(0,dagpath)
mesh = om1.MFnMesh(dagpath)
x = om1.MPointArray()
mesh.getPoints(x)
print(x)
結果はMPointArrayオブジェクト。
<maya.OpenMaya.MPointArray; proxy of <Swig Object of type 'MPointArray *' at 0x000002A1B1CD5960> >
ただしこのMPointArrayの中には頂点の位置を収めていますが、全体printしてみても中の値が表示されません。一つずつprintしないと見られません。
print(x[0][0]) # 1.0514626502990723
print(x[0][1]) # -1.051462173461914
print(x[0][2]) # -3.2360682487487793
API 2.0
API 2.0はAPI 1.0から改善されたもので、書き方は似ていますが、大分楽になります。
import maya.api.OpenMaya as om2
sll = om2.MGlobal.getActiveSelectionList()
mesh = om2.MFnMesh(sll.getDagPath(0))
x = mesh.getPoints()
print(x)
結果はAPI 1.0と同じくMPointArrayオブジェクトですが、printするとすぐ中の値が見えて便利です。
[maya.api.OpenMaya.MPoint(1.0514626502990722656, -1.0514621734619140625, -3.2360682487487792969, 1), maya.api.OpenMaya.MPoint(-2.7527637481689453125, -1.0514621734619140625, -2.0000002384185791016, 1), maya.api.OpenMaya.MPoint(-2.7527639865875244141, -1.0514621734619140625, 1.9999998807907104492, 1), maya.api.OpenMaya.MPoint(1.0514621734619140625, -1.0514621734619140625, 3.2360680103302001953, 1), maya.api.OpenMaya.MPoint(3.4026031494140625, -1.0514621734619140625, 0, 1), maya.api.OpenMaya.MPoint(0, 1.0514621734619140625, 0, 1)]
できた結果の形式はそれぞれ違うのですが、結果として頂点の位置を示す6×3=18
の数字が得られます。
概観
書きやすさから見るとこうなるでしょう。
pymel > maya.cmds > API 2.0 >> API 1.0
ただし、速度の方が一般的に言うとこうなります。
API 2.0 > API 1.0 >> maya.cmds > pymel
同じことをする時に、pymelとmaya.cmdsの方は書きやすいのですが、速度はAPI 2.0とAPI 1.0の方が数十倍や百倍速いです。
API 1.0は古い方法で書き方が冗長で面倒くさくてあまりpythonらしくないです。
それに比べてAPI 2.0は一番新しいもので、API 1.0よりpythonらしい書き方で扱いやすくなるだけでなく、速度も随分速くなります。
頂点の位置を取得する時の速度の比較
次は実際に各方法で実行して時間を計ってみます。
速度を比較するために、今回はこのような無駄にハイポリな螺旋で試します。
mc.polyHelix(c=6,h=4,w=4,r=0.1,sa=12,sco=120)
頂点の数は8652です。どう見てもやりすぎですね。
ここで全部の頂点の位置を100回くらい繰り返して取得するという無駄な行動を実行してかかる時間を計ります。
import time
import pymel.core as pm
import maya.cmds as mc
import maya.OpenMaya as om1
import maya.api.OpenMaya as om2
n = 100 # 百回繰り返す
t0 = time.time()
for i in range(n):
poly = pm.ls(sl=1)[0]
x = poly.getPoints()
print('pymel: %.5f秒'%(time.time()-t0))
t0 = time.time()
for i in range(n):
poly = mc.ls(sl=1)[0]
x = mc.xform(poly+'.vtx[*]',q=1,t=1)
print('maya.cmds: %.5f秒'%(time.time()-t0))
t0 = time.time()
for i in range(n):
sll = om1.MSelectionList()
om1.MGlobal.getActiveSelectionList(sll)
dagpath = om1.MDagPath()
sll.getDagPath(0,dagpath)
mesh = om1.MFnMesh(dagpath)
x = om1.MPointArray()
mesh.getPoints(x)
print('API 1.0: %.5f秒'%(time.time()-t0))
t0 = time.time()
for i in range(n):
sll = om2.MGlobal.getActiveSelectionList()
mesh = om2.MFnMesh(sll.getDagPath(0))
x = mesh.getPoints()
print('API 2.0: %.5f秒'%(time.time()-t0))
結果
pymel: 6.56777秒
maya.cmds: 1.60982秒
API 1.0: 0.01965秒
API 2.0: 0.00201秒
こうやって見ると、時間の違いは圧倒的です。
何回やっても速度の差は同じくらいで明白です。
ポリゴンの頂点をそれぞれ移動する処理の速度の比較
次はもっと実用的な例を挙げます。
例えばポリゴン球を弄ってこんなものを作ってみたいと思います。
方法は一つずつの頂点をその元の位置によって移動させて形を作るのです。
この4の方法で作成して時間を比較します。
import math,time
import pymel.core as pm
import maya.cmds as mc
import maya.api.OpenMaya as om2
import maya.OpenMaya as om1
pm.polySphere(r=10,sx=180,sy=30)
t0 = time.time()
mesh = pm.ls(sl=1)[0]
xyz = mesh.getPoints()
for i,t in enumerate(xyz):
w = 1+0.2*math.cos(math.atan2(t[0],t[2])*8)
xyz[i] = t[0]*w,t[1]*(100-t[1]**2)/100,t[2]*w
mesh.setPoints(xyz)
print('pymel: %.5f秒'%(time.time()-t0))
mc.polySphere(r=10,sx=180,sy=30)
t0 = time.time()
mesh = mc.ls(sl=1)[0]
xyz = mc.xform(mesh+'.vtx[*]',q=1,t=1)
for i in range(int(len(xyz)/3)):
t = xyz[i*3:(i+1)*3]
w = 1+0.2*math.cos(math.atan2(t[0],t[2])*8)
mc.scale(w,(100-t[1]**2)/100,w,'.vtx[%d]'%i)
print('maya.cmds: %.5f秒'%(time.time()-t0))
mc.polySphere(r=10,sx=180,sy=30)
t0 = time.time()
sll = om1.MSelectionList()
om1.MGlobal.getActiveSelectionList(sll)
dagpath = om1.MDagPath()
sll.getDagPath(0,dagpath)
mesh = om1.MFnMesh(dagpath)
xyz = om1.MPointArray()
mesh.getPoints(xyz)
for i in range(xyz.length()):
t = xyz[i]
w = 1+0.2*math.cos(math.atan2(t[0],t[2])*8)
xyz.set(om1.MPoint(t[0]*w,t[1]*(100-t[1]**2)/100,t[2]*w),i)
mesh.setPoints(xyz)
print('API 1.0: %.5f秒'%(time.time()-t0))
mc.polySphere(r=10,sx=180,sy=30)
t0 = time.time()
sll = om2.MGlobal.getActiveSelectionList()
mesh = om2.MFnMesh(sll.getDagPath(0))
xyz = mesh.getPoints()
for i,t in enumerate(xyz):
w = 1+0.2*math.cos(math.atan2(t[0],t[2])*8)
xyz[i] = om2.MPoint([t[0]*w,t[1]*(100-t[1]**2)/100,t[2]*w])
mesh.setPoints(xyz)
print('API 2.0: %.5f秒'%(time.time()-t0))
結果
pymel: 0.09283秒
maya.cmds: 0.49966秒
API 1.0: 0.01959秒
API 2.0: 0.01200秒
この場合でも相変わらずAPI 2.0が一番速いです。
API 2.0の書き方はpymelとはほとんど変わらないのに、速度は8倍くらいの差です。
今回maya.cmdsが一番遅いのは、.setPoints()みたいに全ての頂点を一括として位置を決める関数がないからです。
もしmaya.cmdsと同じような書き方をpymelで書いたら、pymelの方が遅いです。
つまりpymelとmaya.cmdsはどちらも書きやすいが、高速とは言えません。一般に言うとpymelの方がmaya.cmdsより遅いようですが、やりたいことによってmaya.cmdsより速いという場合もあります。
##numpyと一緒に使ってみたら
おまけに、maya2022ではpipでnumpy, pandas, sklearn, cv2など便利なモジュールを簡単にインストールできるので、mayaに使うのも便利です
例えば上述の例はAPI 2.0をnumpyと一緒に使ってみます。
import time
import maya.cmds as mc
import maya.api.OpenMaya as om2
import numpy as np
mc.polySphere(r=10,sx=180,sy=30)
t0 = time.time()
sll = om2.MGlobal.getActiveSelectionList()
mesh = om2.MFnMesh(sll.getDagPath(0))
xyz = mesh.getPoints()
xyz = np.array(xyz).T
w = 1+0.2*np.cos(np.arctan2(xyz[0],xyz[2])*8)
xyz = np.stack([xyz[0]*w,xyz[1]*(100-xyz[1]**2)/100,xyz[2]*w]).T
xyz = om2.MPointArray(xyz)
mesh.setPoints(xyz)
print('API 2.0 + numpy: %.5f秒'%(time.time()-t0))
API 2.0のMPointArrayは色々numpyのndarrayと似ていますが、numpyのndarrayみたいに直接計算に使ったり転置したりすることはできないのでまだちょっと不便です。
numpyの配列に変換するとforを使わずに計算することができて便利です。
ただし変換に時間もかかるので、全体見ると速度はやっぱりAPI 2.0だけ使う時と比べて少し遅くなります。
まとめ
結果から見れば、同じことをする時に、何をしようとしてもAPI 2.0が一番速いです。なのでできるだけAPI 2.0を使った方がいいようです。
pymelとmaya.cmdsは便利な関数が整って書きやすいので、時間に気にする必要がない場合pymelとmaya.cmdsを使った方いいですが、速度はAPI 1.0とAPI 2.0の方が圧倒的に速いです。
API 1.0は古いもので書きにくい上に、速度もAPI 2.0より遅いので、今のところあまり使う理由がないかもしれません。ただしAPI 1.0のできることはAPI 2.0より多いようです。
なのでAPI 2.0のできないことはまだAPI 1.0を使う必要がありますが、API 2.0のできることなら今更API 1.0を使ってもあまりメリットがないでしょう。
以上説明した4の方法の他にも、まだ色々できる手段があるようです。例えばcymel, cmdx, metan 詳しくはこの記事 https://qiita.com/ryusas/items/eba6ab1b5af0700799e6
ただしどれもまだあまり普及というほどではないようです。速度もやはりpymelより速くても、API 2.0には敵いません。
違う書き方の比較は、他にもこの記事など https://qiita.com/ktmmilk/items/c88041b654f84b8498ca