Mayaのマテリアルのこねこね

お誘いを受け、せっかくなので私もMaya-Pythonアドベントカレンダーに書こうかなと思います。

pythonメインのお話が多いようなのでmayaメインのお話でも。

中でも、意外とやることが多いマテリアルまわりの小ネタを書いていこうかなと思います。

念のため、pymelとmayaPythonの両方の記述で書いておきますね。
　

``` python
import pymel.core as pm
import maya.cmds as cmds
```
　

# マテリアルまたはシェーダーの取得

まずは、何事においても始めとなるマテリアルの取得について。


## 【全material取得】

``` python
pm.ls(mat=True)
```

``` python
cmds.ls(mat=True)
```

シーン内のすべてのマテリアルを取得できます。
　
　

特定のマテリアルをとりたいときはtypフラグを使います。

例：lambertのみを取得

``` python
pm.ls(typ='lambert')
```

``` python
cmds.ls(typ='lambert')
```
　
　


##【Maya上で使用できるシェーダーリストの取得】

前項で特定のマテリアルを取得というのをやりました。

しかし、環境によっては存在しないマテリアルのタイプを取得しようとすると

**「このマテリアルうちのMayaに入ってないからだめやんけ！」**

と、ワーニングを返されてしまいます。
　
　

そんなスクリプトでワーニングすら返されたくないという人に役立つのがこれです。

``` python
matTypList = pm.listNodeTypes('shader')
```

``` python
matTypList = cmds.listNodeTypes('shader')
```

これを実行すると、自分の環境で入っているすべてのマテリアルタイプを取得できます。

厳密にいうと、pluginで読まれて使用できる状態になっているものを取得できます。

例えば、vrayをインストールしていても、シーン的にpluginがオンになっていないと、

vrayのマテリアルタイプが存在しないことになります。

つまるところ、チェックしようとしているマテリアルタイプが

しっかりと読まれているかを確認するときに使えるんですよね。

単純に使用できるマテリアルリストアップとしても使えるので覚えておくとお得です。
　
　

## 【オブジェクトからマテリアルを取得】
あるオブジェクトからそれにアサインされているマテリアルやシェーディンググループを取得します。

<font color=orange>ls</font>よりもこっちの方が何気によく使います。

というのも、特定のオブジェクトのマテリアルに対しての処理というのをやりたい時が多いからなんですよね。
　
　

``` python
SG = pm.listConnections(objs, s=False, d=True, t='shadingEngine')
mat = pm.ls(pm.listConnections(SG, s=True, d=False), mat=True)
```

``` python
SG = cmds.listConnections(objs, s=False, d=True, t='shadingEngine')
mat = cmds.ls(cmds.listConnections(SG, s=True, d=False), mat=True)
```
　
ここで書かれている<font color=orange>objs</font>という変数は特定のオブジェクトが含まれているリスト変数になります。
　

選択しているオブジェクトに上記を使うなら例えば、

``` python
objs = pm.selected()
```

``` python
objs = cmds.ls(sl=True)
```

と事前に、<font color=orange>objs</font>変数を指定しておきます。
これのいいところは、複数指定した状態でも返ってくるところですね。

pymelでシェーディンググループを取得するなら

``` python
SG  = obj.shadingGroups()
```
とも書けるんですが、<font color=orange>obj</font>に入るものは単体でなければなりません。

これを複数のものに適用するなら、for文で回して…とかめんどいことになります。
　
　
　
　

ちょっと話は脱線しますが、ここで使用した<font color=orange>listConnections</font>はめちゃくちゃ便利です。

上記同様な感じで、マテリアルからオブジェクトにアサインされたものをとることもできますし、

jointからbind skinされたオブジェクトをとることもできます。
　
　
　

基本maya上での作業ってポリゴンやjoint選択(指定)から引数代わりとして指定することが多いので

何かのツールを作るときは大体の確率で使用します。

逆にいえばここら辺は関数にしとくと楽ですよね～。
　
　
　
　
　

# ファイルノードの取得

ファイルノード、いわゆるテクスチャパスが入っているノードを取得します。

シーン内の全部というのであれば最初に紹介した、<font color=orange>ls</font>コマンドを使用して、<font color=orange>typ</font>をfileと指定してあげればできます。

ただ、特定のマテリアルにつながっているファイルノードのみを取得したいというのが頻繁にあります。
　

### コマンドを使って簡単に取得

``` python
fileNodes = pm.ls(pm.listHistory(mat), type='file')
```

``` python
fileNodes = cmds.ls(cmds.listHistory(mat), type='file')
```

<font color=orange>mat</font>変数には特定のマテリアルが入ります。

この書き方をするとマテリアルにマテリアルがつながっていても、

その先につながっているファイルノードを取得することができます。

結果的にそのマテリアルに関連するファイルノードをすべて取得できるんですね。
　

ところがどっこい。この<font color=orange>listHistory</font>注意して欲しいんですが、

特定のアトリビュートにコネクトされているものは引っ張ってこれないという弱点があります。

![advcale_mayapy_01.png](https://qiita-image-store.s3.amazonaws.com/0/153835/fc994865-1d7b-0029-e0f5-c1173396e737.png)

こういったノードがあったとき、

``` python
pm.select(pm.listHistory('lambert2'))
```
これを実行してみます。

するとこんな感じで

![advcale_mayapy_02.png](https://qiita-image-store.s3.amazonaws.com/0/153835/7dd48b54-2b59-36e1-36eb-f2c499f56907.png)


file15, 18, 24, 27 が取得できていないということがわかります。

とはいえ、この取得できない特定のアトリビュートというのはそうそう繋ぐこともないので

問題ないといえば問題ないんですよね。
　
　
　
　

### 完璧に取得

**「もしかして、必要ノードを取り逃がすかもしれない…」**

というような完璧主義者さんには、<font color=orange>while</font>で回すという方法もあります。

ここはpymelだけ載せてますが、基本<font color=orange>pm.</font>を<font color=orange>cmds.</font>にすればmayaPythonでも動くはずです。

``` python
limitNum = 20
##---------------------------------------------------------
## get connected file node
##---------------------------------------------------------
## @param <obj/List>nodes : nodes
## @return <obj/List>fileNode : file node
## @return <obj/List>othNode  : other node
def getConnectedFileNode(nodes):

    connNode = pm.listConnections(nodes, s=True, d=False)
    fileNode = pm.ls(connNode, typ='file')
    othNode  = list(set(connNode) - set(fileNode))

    return fileNode, othNode
##---------------------------------------------------------
## get file node from history
##---------------------------------------------------------
## @param <obj/List>materials : target material
## @return <obj/List>fileNode : file node
def getFileNodeFromHistory(materials):

    global limitnum

    count   = 0
    fileNode, othNode = getConnectedFileNode(materials)
    while othNode:
        tmpFNode, othNode = getConnectedFileNode(othNode)
        if tmpFNode:
            fileNode += tmpFNode
        count += 1
        if count >  limitNum:
            break

    return fileNode
```
``` python
getFileNodeFromHistory(mat)
```

で実行できます。matにはリストで渡しても、単体で渡しても問題ないです。

さっき例に挙げたマテリアルネットワークで試してみます。

![advcale_mayapy_01.png](https://qiita-image-store.s3.amazonaws.com/0/153835/fc994865-1d7b-0029-e0f5-c1173396e737.png)

``` python
pm.select(getFileNodeFromHistory('lambert2'))
```

![advcale_mayapy_03.png](https://qiita-image-store.s3.amazonaws.com/0/153835/38a14a6f-840e-dd2e-ed4f-e0871bebbd3d.png)


このように漏れなく取得できるようになります。
　

<font color=orange>limitNum</font>というglobalの変数は階層の深さを指定しているものです。

指定しなければどのような階層の数でもいけるのですが、

<font color=orange>while</font>文は無限ループが引き起こる要因になりますので、

念のための保険として入れてます。

流石に20階層以上のシェーディングネットワークは無いと思いますので…

万が一あったとしても<font color=orange>limitNum</font>の数値を上げておけば済みます。
　
　

ちなみに、言わずもがなかもしれませんが、処理速度は<font color=orange>getFileNodeFromHistory</font>の関数を使うよりも圧倒的に<font color=orange>listHistory</font>コマンド使った方が速いです。

**「一度このwhile文を使ってみたかったんだ！助かる！」**

というスクリプト初心者にはテストケースとして勉強になるかもしれません。
　

はい。私のことですね。

べ、べつに<font color=orange>listHistory</font>知らなくて、作ったわけじゃないんだからね！
　
　
　
　
　
　

# おわり
というわけで、マテリアル周りのお話でした～。

誰かの役に立てばいいですね～。

これで、コネクションまわりは<font color=orange>listConnections</font>使っておけばなんとでもなるというのが勉強できると思います。
　
　
　

お仕事のコネも<font color=orange>listConnections</font>できればいいんですけどね…


以上、「マテリアルのコネクション小ネタ」

略して「マテリアルのこねこね」でした。

ではでは(:3ﾉｼ　)ﾍ