HEXA BLOG

研究・開発

HEXA BLOGその他研究・開発2018.2.8

今日のMaya Python API 2.0

こんにちは

Houdini信者となった🎄ヨセミテ🎄です。ブログはMayaです

Houdiniは最高なので皆さんもHoudiniを使いましょう。

―――――――――――――――――――――――――――――――――――――――

今回はAPI 2.0でノードプラグインを作成してみます。
ノードプラグインをAPI 2.0で作成したことは現状、全くありません。
理解不十分な点が随所で見受けられるかも知れませんが温かいまなざしで見守って下さい。

テンプレートは前回同様、Autodesk公式のヘルプページで配布してくれています。
呼びだし時の挙動などをコメントでメモしつつ、とりあえず動くものを目標に作成します。

今回はMPxNodeクラスを継承したクラスを作成してみます。
継承元の分類などは公式ドキュメントに詳細が載っています

―――――――――――――――――――――――――――――――――――――――

以下スクリプトです。
内容としては接続された値に応じて接続先の移動値をsin波で出力するというもの。
現状では利用価値とかはほぼ皆無です

# coding: UTF-8
# ブログ用 OpenMayAPI2.0を利用したノード作成のテスト

import sys
import maya.api.OpenMaya as om

import math

def maya_useNewAPI():
    """
    このスクリプトはMayaプラグインの生産のためのもので、API2.0で利用しています。(英語翻訳)
    説明用の項目かな?
    """
    pass


# Plug-in information:
# サンプルのリンク先、Maya プラグイン エントリ/エグジット ポイント項目の各項目に詳細が記載
kPluginNodeName = 'blog_test_node'  # ノードの名前(1.ノード名参照)
kPluginNodeClassify = 'utility/general'  # ノードの分類(6.ノードの分類参照)
kPluginNodeId = om.MTypeId(0x07EFE)  # ノードごとのユニークな値、MTypeIDを作成。(2.ノードID参照)
# 内部開発用のプラグインは[x00000 - 0x7ffff#」の範囲を指定することを推奨している模様。社内で管理しましょう。

# 各アトリビュートに初期値として入れてる値
sampleDefaultValue = 1


##########################################################
# Plug-in
##########################################################
class BlogTestNode(om.MPxNode):
    # データハンドル設定用のMObject。computeメソッドにノード入出力先を受け渡すために利用されている
    in_attribute_offset_x = om.MObject()
    in_attribute_offset_y = om.MObject()
    in_attribute_offset_z = om.MObject()
    in_attribute_position_x = om.MObject()
    in_attribute_position_y = om.MObject()
    in_attribute_position_z = om.MObject()
    out_attribute_result_x = om.MObject()
    out_attribute_result_y = om.MObject()
    out_attribute_result_z = om.MObject()

    def __init__(self):
        """
        コンストラクタ
        """
        om.MPxNode.__init__(self)

    def compute(self, pPlug, pDataBlock):
        """
        :brief  計算処理用の関数(ノードの動作(Node Behavior参照)
                mayaはMPlugの接続ごとにcomputeを呼び出して、MPlugとMDataBlock(接続先とデータ内容?)を受け渡して
                ドキュメントに含まれている、「ダーティなプラグ」という表現は、
                多分だがデータの関連性が正しくないものをさしていると思われる
        :param  pPlug         一つのノードアトリビュートに関連した接続先
        :param  pDataBlock    計算用のデータを含むもの
        """
        # 出力が刺さっているときの処理をif分で追加していくみたい
        if (pPlug == BlogTestNode.out_attribute_result_x or
            pPlug == BlogTestNode.out_attribute_result_y or
            pPlug == BlogTestNode.out_attribute_result_z):
            # 各々のアトリビュートに対するデータハンドルの取得
            in_offset_data_handle_x = pDataBlock.inputValue(BlogTestNode.in_attribute_offset_x)
            in_offset_data_handle_y = pDataBlock.inputValue(BlogTestNode.in_attribute_offset_y)
            in_offset_data_handle_z = pDataBlock.inputValue(BlogTestNode.in_attribute_offset_z)
            in_position_data_handle_x = pDataBlock.inputValue(BlogTestNode.in_attribute_position_x)
            in_position_data_handle_y = pDataBlock.inputValue(BlogTestNode.in_attribute_position_y)
            in_position_data_handle_z = pDataBlock.inputValue(BlogTestNode.in_attribute_position_z)
            out_result_data_handle_x = pDataBlock.outputValue(BlogTestNode.out_attribute_result_x)
            out_result_data_handle_y = pDataBlock.outputValue(BlogTestNode.out_attribute_result_y)
            out_result_data_handle_z = pDataBlock.outputValue(BlogTestNode.out_attribute_result_z)

            # ======================= 計算式 =======================
            result_x = math.sin(in_offset_data_handle_x.asFloat() + in_position_data_handle_x.asFloat())
            result_y = math.sin(in_offset_data_handle_y.asFloat() + in_position_data_handle_y.asFloat())
            result_z = math.sin(in_offset_data_handle_z.asFloat() + in_position_data_handle_z.asFloat())
            # ======================================================

            # 出力値を設定
            out_result_data_handle_x.setFloat(result_x)
            out_result_data_handle_y.setFloat(result_y)
            out_result_data_handle_z.setFloat(result_z)

            # データハンドルが正常な値であることの記載(?) 計算が不要であることの説明らしい
            out_result_data_handle_x.setClean()
            out_result_data_handle_y.setClean()
            out_result_data_handle_z.setClean()

        else:
            # エラー時の処理。接続したのに接続先が見つからないときに呼び出されていた
            return om.kUnknownParameter


##########################################################
# プラグインの定義
# クラス外にあるのね
##########################################################
def nodeCreator():
    """
    createNode時に呼び出される場所
    """
    return BlogTestNode()


def nodeInitializer():
    """
    loadPluginを行った際に呼び出される模様
    入出力アトリビュートセット作成するらしいのでここで全入出力を用意する
    データハンドルの関連付けしなくてもここで定義しとけばとりあえずノード表面に入出力先として出てきた
    """
    print("called nodeInitializer define")

    # 同一のMFnNumericAttributeインスタンス内で複数の数値をもてるようだったので、
    # インスタンスは書き込み可能などの設定別に分ければいいのだと思う
    # …と思ったけどサンプルだとoutputでも使いまわしてた。とりあえず今回は設定ごとにインスタンスを作る
    # 使いまわしはミス発生元案件な気がするけど沢山インスタンス作ると処理遅くなっちゃったりするんだろうか
    MFnNAIns_input_offset = om.MFnNumericAttribute()
    MFnNAIns_input_position = om.MFnNumericAttribute()

    MFnNAIns_output_position = om.MFnNumericAttribute()

    # 定数
    default_value = 0.0

    # ==================================
    # 入力アトリビュート
    # ==================================
    # numericAttributeFn:ノード上への数値入力の出来るアトリビュート longName、shortName等を引数として与えている
    BlogTestNode.in_attribute_offset_x = MFnNAIns_input_offset.create('offset_x', 'ox',
                                                               om.MFnNumericData.kFloat, default_value)
    BlogTestNode.in_attribute_offset_y = MFnNAIns_input_offset.create('offset_y', 'oy',
                                                               om.MFnNumericData.kFloat, default_value)
    BlogTestNode.in_attribute_offset_z = MFnNAIns_input_offset.create('offset_z', 'oz',
                                                               om.MFnNumericData.kFloat, default_value)

    BlogTestNode.in_attribute_position_x = MFnNAIns_input_position.create('position_x', 'px',
                                                               om.MFnNumericData.kFloat, default_value)
    BlogTestNode.in_attribute_position_y = MFnNAIns_input_position.create('position_y', 'py',
                                                               om.MFnNumericData.kFloat, default_value)
    BlogTestNode.in_attribute_position_z = MFnNAIns_input_position.create('position_z', 'pz',
                                                               om.MFnNumericData.kFloat, default_value)

    # アトリビュートの属性設定
    # どうやら最後に作成したアトリビュートのみに適用されるみたい。インスタンス分けてない理由はこれか
    # MFnNAIns_input_offset.connectable = False # 綺麗にかけていないので一旦コメントアウト

    # 作成したアトリビュートを追加
    BlogTestNode.addAttribute(BlogTestNode.in_attribute_offset_x)
    BlogTestNode.addAttribute(BlogTestNode.in_attribute_offset_y)
    BlogTestNode.addAttribute(BlogTestNode.in_attribute_offset_z)
    BlogTestNode.addAttribute(BlogTestNode.in_attribute_position_x)
    BlogTestNode.addAttribute(BlogTestNode.in_attribute_position_y)
    BlogTestNode.addAttribute(BlogTestNode.in_attribute_position_z)

    # ==================================
    # OUTPUT NODE ATTRIBUTE(S)
    # 出力アトリビュート
    # ==================================
    BlogTestNode.out_attribute_result_x = MFnNAIns_output_position.create('result_x', 'rx', om.MFnNumericData.kFloat)
    MFnNAIns_output_position.writable = False  # writeble = Falseを設定しないと出力ノードとして認識してくれない模様

    BlogTestNode.out_attribute_result_y = MFnNAIns_output_position.create('result_y', 'ry', om.MFnNumericData.kFloat)
    MFnNAIns_output_position.writable = False  # 最後に作成したもののみに適用されるので何度も書いてる

    BlogTestNode.out_attribute_result_z = MFnNAIns_output_position.create('result_z', 'rz', om.MFnNumericData.kFloat)
    MFnNAIns_output_position.writable = False  # 最後に作成したもののみに適用されるので何度も書いてる

    # 作成したアトリビュートを追加
    BlogTestNode.addAttribute(BlogTestNode.out_attribute_result_x)
    BlogTestNode.addAttribute(BlogTestNode.out_attribute_result_y)
    BlogTestNode.addAttribute(BlogTestNode.out_attribute_result_z)

    # ==================================
    # アトリビュートの依存関係(?)
    # 恐らく計算処理の流れに関連する設定項目だと思う
    # ==================================
    BlogTestNode.attributeAffects(BlogTestNode.in_attribute_offset_x, BlogTestNode.out_attribute_result_x)
    BlogTestNode.attributeAffects(BlogTestNode.in_attribute_offset_y, BlogTestNode.out_attribute_result_y)
    BlogTestNode.attributeAffects(BlogTestNode.in_attribute_offset_z, BlogTestNode.out_attribute_result_z)

    BlogTestNode.attributeAffects(BlogTestNode.in_attribute_position_x, BlogTestNode.out_attribute_result_x)
    BlogTestNode.attributeAffects(BlogTestNode.in_attribute_position_y, BlogTestNode.out_attribute_result_y)
    BlogTestNode.attributeAffects(BlogTestNode.in_attribute_position_z, BlogTestNode.out_attribute_result_z)


# ここから下のイニシャライズ系はドキュメントで触れていなかったので、特殊な動作を必要としない現状は
# 触る必要性は無さそう

def initializePlugin(mobject):
    ''' Initialize the plug-in '''
    # プラグインの初期化
    mplugin = om.MFnPlugin(mobject)
    try:
        mplugin.registerNode(kPluginNodeName, kPluginNodeId, nodeCreator,
                             nodeInitializer, om.MPxNode.kDependNode, kPluginNodeClassify)
    except:
        sys.stderr.write('Failed to register node: ' + kPluginNodeName)
        raise


def uninitializePlugin(mobject):
    ''' Uninitializes the plug-in '''
    # プラグインの停止
    mplugin = om.MFnPlugin(mobject)
    try:
        mplugin.deregisterNode(kPluginNodeId)
    except:
        sys.stderr.write('Failed to deregister node: ' + kPluginNodeName)
        raise

 

―――――――――――――――――――――――――――――――――――――――

画像だけだと判りづらいですが、触っているオブジェクトを動かすと接続先も動きます。うん、まぁ一応使えそうです。

作ってみたの感想ですが、
・最初からアトリビュートをノードエディタに表示させる方法がわからなかった
・アトリビュートごとに設定を書く必要があるので現状、そのまま書いてるだけだとかなり冗長
・アトリビュートをまとめる方法を判らないと接続が大変
と現状、実用には程遠いポンコツな出来です。

―――――――――――――――――――――――――――――――――――――――

1回分でまとまる量でもなかったので次回は整理しながらの書き方を模索しつつ
ちゃんと実用性を考えたノードを作成したいと思います。
では。🎄

RECRUIT

大阪・東京共にスタッフを募集しています。
特にキャリア採用のプログラマー・アーティストに興味がある方は下のボタンをクリックしてください

RECRUIT SITE