maya pythonの4つの書き方（pymel, maya.cmds, API 1.0, API 2.0）による頂点処理の比較

# はじめに

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面ピラミッドの頂点の位置を取得したい場合です。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/a2dd2ee4-c3f0-4eeb-caa3-c4745817afdd.jpeg)

書き方を比べてみます。

## pymel

まずは一番書きやすいpymelです。全部の頂点の位置を取得したい時に.getPoints()というメソッドがあるので書き方は簡単です。

```python
import pymel.core as pm

mesh = pm.ls(sl=1)[0]
x = mesh.getPoints()
print(x)
```
結果はdt.Pointというxyzの位置を収めるクラスのオブジェクトのリストです。頂点が6なので、dt.Pointが6つ。

```python
[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

```python
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`。

```python
[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です。

```python
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オブジェクト。

```python
<maya.OpenMaya.MPointArray; proxy of <Swig Object of type 'MPointArray *' at 0x000002A1B1CD5960> >
```

ただしこのMPointArrayの中には頂点の位置を収めていますが、全体printしてみても中の値が表示されません。一つずつprintしないと見られません。

```python
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から改善されたもので、書き方は似ていますが、大分楽になります。

```python
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するとすぐ中の値が見えて便利です。

```python
[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らしい書き方で扱いやすくなるだけでなく、速度も随分速くなります。



# 頂点の位置を取得する時の速度の比較

次は実際に各方法で実行して時間を計ってみます。

速度を比較するために、今回はこのような無駄にハイポリな螺旋で試します。

```python
mc.polyHelix(c=6,h=4,w=4,r=0.1,sa=12,sco=120)
```

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/ae3c45ca-a082-9c18-76a2-d5b083d97800.jpeg)

頂点の数は8652です。どう見てもやりすぎですね。

ここで全部の頂点の位置を100回くらい繰り返して取得するという無駄な行動を実行してかかる時間を計ります。

```python
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))
```

結果

```python
pymel: 6.56777秒
maya.cmds: 1.60982秒
API 1.0: 0.01965秒
API 2.0: 0.00201秒
```

こうやって見ると、時間の違いは圧倒的です。

何回やっても速度の差は同じくらいで明白です。


# ポリゴンの頂点をそれぞれ移動する処理の速度の比較

次はもっと実用的な例を挙げます。

例えばポリゴン球を弄ってこんなものを作ってみたいと思います。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/67823e22-2ac2-16c5-9054-6a517e23d56b.jpeg)

方法は一つずつの頂点をその元の位置によって移動させて形を作るのです。

この4の方法で作成して時間を比較します。

```python
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))
```
結果

```python
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と一緒に使ってみます。

```python
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