Help us understand the problem. What is going on with this article?

Maya Dependency GraphをPython APIで走査する

More than 1 year has passed since last update.

ディペンデンシーグラフをAPIでたどる

Mayaの仕事をしていると、「Shading Networkを調べるため全部スキャンする」なんてことが仕事でよくあるので、Python APIでできないか調べてみました。
Dagノードの階層を走査するサンプルはよくあるのですが、ディペンデンシーノードグラフ(DG)のスキャンはあまりないのでメモしておきます。(ちなみにDagはDGの一部。)

また、コマンドで同じ処理を書いた場合とのスピードを比較してみました。

DG用イテレータ

DGにも走査するためのイテレータが用意されています。
OpenMayaモジュールのMItDependencyGraphを使います。→ MItDependencyGraph

dependIter = om.MItDependencyGraph(sg, 
                                   om.MFn.kInvalid, 
                                   om.MItDependencyGraph.kUpstream)

API1.0と2.0で渡すオブジェクトが異なるので注意が必要です。
1.0だとMFnDependencyNode、2.0だとMObjectです。

サンプルコード

指定されたオブジェクトからそれに使用されているテクスチャをリストするスクリプトを作成してみました。

処理の大まかな流れは
1. 指定されたオブジェクトから、その下位階層のメッシュリストを取得
2. 各オブジェクトにアサインされているShadingGroupを取得
3. ShadingGroupをルートノードとして、上流のDGノードを辿っていく
4. テクスチャノードが見つかったらストアする

ここではAPI1.0を使用して、スキャンスクリプトを書いてみます。

dg_traverse_api1.py
# -*- coding: utf-8 -*-
from maya import OpenMaya as om
import maya.cmds as cmds
def get_texture_from_nodes(nodes):
    ## オブジェクト名のリストから、実際のメッシュリストを取得
    cmds.select(cmds.ls(nodes, dag=1, s=1))
    mesh_tex_dict = {}
    selection_list = om.MSelectionList()
    om.MGlobal.getActiveSelectionList(selection_list)
    ## 選択オブジェクトを走査するイテレータを作成
    shape_iter = om.MItSelectionList(selection_list)

    ## すべてのメッシュを検査するまでループ
    while not shape_iter.isDone():
        item_type = shape_iter.itemType()
        if item_type == om.MItSelectionList.kDagSelectionItem:
            dag_path = om.MDagPath()
            shape_iter.getDagPath(dag_path)
            if dag_path.hasFn(om.MFn.kMesh):
                mesh_fn = om.MFnMesh(dag_path.node())
                sgs = om.MObjectArray()
                mesh_compos = om.MObjectArray()
                ## メッシュにアサインされているShadingGroupを取得
                mesh_fn.getConnectedSetsAndMembers(dag_path.instanceNumber(),
                                                sgs,
                                                mesh_compos,
                                                True)
                textures = []
                for i in range(sgs.length()):
                    sg_fn = om.MFnDependencyNode(sgs[i])
                    ## ShadingGroupから上流のDGノードを検査するためのイテレータを作成
                    dependIter = om.MItDependencyGraph(sgs[i],
                                                    om.MFn.kInvalid,
                                                    om.MItDependencyGraph.kUpstream)
                    while not dependIter.isDone():
                        node = dependIter.currentItem()
                        depNode = om.MFnDependencyNode(node)
                        ## ノードタイプを調べて、fileかaiImageならtextureリストに追加
                        if depNode.typeName() in ["file", "aiImage"]:
                            textures.append(depNode.name())
                        dependIter.next()
                ## mesh名とtextureリストをセットにして辞書にストア
                mesh_tex_dict.update({dag_path.fullPathName():textures})
        shape_iter.next()
    return mesh_tex_dict

このコードを以下のように実行すると、メッシュとテクスチャの辞書が返ってきます。

import dg_traverse_api1 as dgapi1
tex_info = dgapi1.get_texture_from_nodes(["ROOT"])
print tex_info
>> {u'|ROOT|pCube1|pCubeShape1': [u'file1', u'file2']}

ROOTはシーン内のオブジェクト名です。

スピード検証

mayaコマンドでDGをスキャンしたスクリプトとの比較を行ってみました。
スクリプトの処理としては以下の4パターン

  • listHistoryコマンドを用いた検出
  • listConnectionsコマンドで再帰的にコネクションをたどる検出
  • 上記のAPI1.0スクリプト
  • 上記のAPI2.0バージョン

使用したシーンは、pCubeを100個用意し、以下のような適当なShadingNetworkを100個複製し、それぞれアサインしたものです。

sample_network.png

各スクリプトを以下に載せます。

listHistory

dg_traverse_cmdshistory.py
# -*- coding: utf-8 -*-
import maya.cmds as cmds

def get_texture_from_nodes(nodes):
    shapes = cmds.ls(nodes, dag=1, s=1, l=1)
    mesh_tex_dict = {}
    for shape in shapes:
        textures = []
        sgs = cmds.listConnections(shape, type="shadingEngine")
        if sgs:
            for sg in sgs:
                histories = cmds.listHistory(sg)
                for his_node in histories:
                    if cmds.objectType(his_node) in ["file", "aiImage"]:
                        textures.append(his_node)
        mesh_tex_dict.update({shape:textures})
    return mesh_tex_dict

listConnections

dg_traverse_cmdsconnections.py
# -*- coding: utf-8 -*-
import maya.cmds as cmds

def get_texture_from_nodes(nodes):
    shapes = cmds.ls(nodes, dag=1, s=1, l=1)
    mesh_tex_dict = {}
    material_types = cmds.listNodeTypes('shader')
    texture_types = cmds.listNodeTypes('texture')
    utility_types = cmds.listNodeTypes('utility')
    shading_types = material_types + texture_types + utility_types + ["shadingEngine"]
    shading_types = list(set(shading_types))
    for shape in shapes:
        textures = []
        sgs = cmds.listConnections(shape, type="shadingEngine")
        if sgs:
            _find_textures(sgs, textures, shading_types)
        mesh_tex_dict.update({shape:textures})
    return mesh_tex_dict


def _find_textures(nodes, queue, filters):
    for node in nodes:
        node_type = cmds.objectType(node)
        if node_type not in filters:
            continue
        if node_type in ["file", "aiImage"]:
            queue.append(node)
        children = cmds.listConnections(node, s=1, d=0) or []
        if children:
            _find_textures(children, queue, filters)

API2.0

dg_traverse_api2.py
# -*- coding: utf-8 -*-
from maya.api import OpenMaya as om
import maya.cmds as cmds
def get_texture_from_nodes(nodes):
    cmds.select(cmds.ls(nodes, dag=1, s=1))
    mesh_tex_dict = {}
    selection_list = om.MGlobal.getActiveSelectionList()
    shape_iter = om.MItSelectionList(selection_list)
    while not shape_iter.isDone():
        item_type = shape_iter.itemType()
        if item_type == om.MItSelectionList.kDagSelectionItem:
            dag_path = shape_iter.getDagPath()
            if dag_path.hasFn(om.MFn.kMesh):
                mesh_fn = om.MFnMesh(dag_path.node())
                shaders, polyindeces = mesh_fn.getConnectedShaders(dag_path.instanceNumber())
                textures = []
                for sg in shaders:
                    dependIter = om.MItDependencyGraph(sg, om.MFn.kInvalid, om.MItDependencyGraph.kUpstream)
                    while not dependIter.isDone():
                        node = dependIter.currentNode()
                        depNode = om.MFnDependencyNode(node)
                        if depNode.typeName in ["file", "aiImage"]:
                            textures.append(depNode.name())
                        dependIter.next()
                mesh_tex_dict.update({dag_path.fullPathName():textures})
        shape_iter.next()
    return mesh_tex_dict

結果

スクリプト time(sec)
listHistory 0.069798
listConnections 0.812645
API1.0 0.036853
API2.0 0.032879

Maya2018で上記スクリプトを10回実行した平均値です。
API2.0がやはり一番速かったですが、listHistoryも結構速いですね。
ただ経験上listHistoryは最上流まできちんとtraverseしてくれないケースがあるので、あまり使わないです。
今回API2.0で書いてみて意外とPythonicに書けるので直感的に書きやすく、しかも速いというので、これからは2.0だなという感想です。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした