【MAYA】インスタンス全てのマテリアルを統一する

インスタンスごとにマテリアルが異なっている状態の場合に、マテリアルを一つに統一する処理コードを書いてみました。（ひょっとすると標準の機能であるのかもしれませんが、勉強にはなるので。） 
例によってスクリプトエディタにペーストする用です。シェルフに登録すると楽に使えると思います。MAYA2018 + MacOS Sierraで動作確認しています。

<img width="341" alt=" 2018-01-08 1.29.40.png" src="https://qiita-image-store.s3.amazonaws.com/0/8302/238d19db-314d-f24e-8523-24c9dc6b93d6.png">
この図では三つのキューブが存在し、一つだけ異なるマテリアルが設定されています。前回の調査でも触れたとおり、シェイプのインスタンスグループから各マテリアル（正確にはシェーディンググループ）のDAGセットメンバーへアトリビュートが伸びているので、ここをつなぎ変えてやれば良さそうです。具体的なコードは最後に。

#使い方

1.ノードを選択状態にします。対象としてはインスタンスメッシュそのもの、またはその親トランスフォームとなります。
2.Scriptを実行します。
3.親兄弟インスタンスノードがあれば全て同じマテリアル設定になります。選択すべきものがなければエラーメッセージが表示されます。

<img width="311" alt=" 2018-01-08 1.23.23.png" src="https://qiita-image-store.s3.amazonaws.com/0/8302/c402d7cb-6e5f-b231-5c1b-56783454a7ea.png">
インスタンスシェイプの親トランスフォームを１つ選択して実行。

<img width="303" alt=" 2018-01-08 1.23.40.png" src="https://qiita-image-store.s3.amazonaws.com/0/8302/8922b7cb-3135-6714-f658-73b60e4cb8fc.png">
マテリアルが揃う。

<img width="297" alt=" 2018-01-08 1.23.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/8302/e64b14cb-21ef-44e6-7c4a-c632a6da9ce1.png">
もしくは、インスタンスシェイプ自体を１つ選択して実行。

<img width="300" alt=" 2018-01-08 1.23.57.png" src="https://qiita-image-store.s3.amazonaws.com/0/8302/47454b81-5e64-cd0c-cd04-62953766884d.png">
マテリアルが揃う。

何らかのエラーでマテリアルが外れてしまっているパターンにも対応しています。

#対応していないパターン
* マテリアルのフェイスアサインには対応していません。（次課題）
* 一部にマテリアルを反映しないような操作はできません。（すぐ作れそうだが）

#クイック実行用コード

```python
import pymel.core as pm


def get_instance_siblings(node):
    """
    関連インスタンス全てを得る

    :param node: 探索元インスタンスノード参照
    :type node: pymel.nodetypes.DagNode
    :param exclude_first_instance: 親インスタンスを除くか
    :type exclude_first_instance: bool
    :return: 見つかったノードとその関連ノードとエラー情報ストリング
    :rtype: tuple of DagNode and (list of DagNode) and str
    """
    is_instanced = node.isInstanced()
    if not is_instanced:
        # 選択がインスタンスでない場合は子供を探す
        children = pm.listRelatives(node)
        node = None
        for child_node in children:
            if child_node.isInstanced():
                node = child_node
                break
        if node is None:
            return None, '対象インスタンスが見つかりません。'
    instances = node.getOtherInstances()
    if len(instances) == 0:
        return node, [], '兄弟インスタンスが存在しません。'
    return node, instances, None


def sync_instance_material():
    """
    選択したインスタンスのマテリアルに他のインスタンス全てを合わせる
    """
    selected = pm.selected()
    if len(selected) == 0:
        pm.confirmDialog(title='Info', message='ノードを選択して下さい。', button=['Ok'])
        return
    node = selected[0]
    base_node, sibling_nodes, error_info = get_instance_siblings(node)
    if error_info is not None:
        pm.confirmDialog(title='Info', message=error_info, button=['Ok'])
        return
    # 選択したノードのインスタンス番号を得る
    base_ins_index = base_node.instanceNumber()
    # そのノードに設定されているマテリアルを得る( 例)phong1SG
    cons = pm.listConnections(base_node.instObjGroups[base_ins_index], plugs=False)
    if len(cons) == 0:
        # マテリアルが外れてる場合を考慮
        base_ins_mat = None
        target_dist_attr = None
    else:
        base_ins_mat = cons[0]
        target_dist_attr = base_ins_mat.dagSetMembers
    for sibling_node in sibling_nodes:
        # 兄弟インスタンスのインスタンス番号を得る
        sibling_ins_index = sibling_node.instanceNumber()
        # マテリアルへの接続元アトリビュートを得る
        src_attr = sibling_node.instObjGroups[sibling_ins_index]
        # 兄弟インスタンスのマテリアルを得る( 例)phong2SG
        cons = pm.listConnections(src_attr, plugs=False)
        if len(cons) == 0:
            # マテリアルが外れてる場合を考慮
            sibling_ins_mat = None
        else:
            sibling_ins_mat = cons[0]
        # 現在のマテリアルが選択ノードのマテリアルと異なる場合に処理実行
        if base_ins_mat != sibling_ins_mat:
            # 現在の接続先アトリビュートを切断 ( 一度切断しないとエラーになった(connectAttrがforce=Trueであっても))
            cons = pm.listConnections(src_attr, plugs=True)
            if len(cons) > 0:
                # マテリアルが外れてる場合を考慮
                current_dist_attr = cons[0]
                pm.disconnectAttr(src_attr, current_dist_attr)
            # 新しい接続先に変更
            if target_dist_attr is not None:
                # pm.setsでは失敗したのでconnectAttrで実直につなぐ
                pm.connectAttr(src_attr, target_dist_attr, force=True, nextAvailable=True)


sync_instance_material()
del sync_instance_material
del get_instance_siblings
```

#雑メモ・ポイント
* 新しいマテリアルとのコネクション作成の際、`connectAttr`前に一度 `disconnectAttr` しないとエラーになった。
* `connectAttr` 時に `nextAvailable` フラグを使うと次のインデックス番号を考慮しなくていいので便利だ。
* `pm.sets` ではそのインデックス番号がらみのエラーになったので `connectAttr` でコネクションを作った。