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

Houdini Engine API入門

More than 1 year has passed since last update.

image.png

はじめに

この記事は HoudiniAdventCalender2018 2日目の記事です。
今年は謎多きHoudini Engineを調べてみました。

まずHoudini Engineとは何か。インストール時にUE4やMAYA用を選択できる画面が出るので、他のアプリからHoudiniのデジタルアセットにアクセスできる機能。
というのが一般的な認識ではないでしょうか。
image.png

API

image.png
しかしUE4やMAYAで利用しているのは、あくまでHoudini Engine APIの機能を利用したプラグインです。

最新のドキュメントHoudini Engine 3.2の概要にはこう書かれています。

HoudiniEngineは、Houdiniデジタルアセットをホストアプリケーションの内部で直接使用できるようにするAPIです。

HoudiniEngineとは(OverViewより抜粋)

Houdini EngineはC ABI (Application Binary Interface)で、C言語の関数セットです。コンパイラの選択肢やバージョンが一致しなくても、ホストアプリケーションに統合することができます。たとえば、Houdini Engineは、ネイティブのC++アプリケーションと同様に、C#などの環境に統合することができます。

最後に、HoudiniEngineはフラットで小さなAPIで、簡単に学習できます。

簡単に学習できます。

動かすだけなら、ぎりぎりなんとかなりそうです。ただ、簡単に学習できますは嘘だと思います。

基本的な作業

  • 1)Houdini Engineを初期化する。
  • 2)アセットノードをインスタンス化し、プロセス内のノードIDを取得します。
  • 3)アセットノードのパラメータを問い合わせて表示します。
  • 4)オブジェクトノードが存在する場合問い合わせます。
  • 5)各オブジェクト内のディスプレイジオメトリノードを問い合わせます。
  • 6)パーツを問い合わせ、頂点、面、法線などを取得して表示します。
  • 7)アセットノードおよび任意のネイティブノード入力のOPノードパスパラメータを問い合わせます。
  • 8)パラメータの変更をHoudini Engineに戻します。
  • 9)ノードをcookします。
  • 10)必要に応じて、更新されたノードおよびパーツ情報を取得します。
  • 11)アセットノードが不要になったときにクリーンアップします。
  • 12)完了したらHoudini Engineをクリーンアップします。

おおざっぱすぎてよくわかりません、、。

手を動かして理解するHoudini Engineの基礎

まず基本的な作業についてQtを利用して確認していきます。
C++をコンパイル、デバッグできる環境が必要です。インストール方法は補足:Qtのインストールにて

実行環境は
Houdiniバージョン:17.0.372
Qtバージョン:5.11.1
ビルド:MSVC2017_64bit 
で行います。

Houdini Engineの初期設定

作業に入る前に、HAPIや環境変数の設定が必要です。

重要なファイル

以下のヘッダーファイルがHoudiniEngineディストリビューションに含まれています。

■ HAPI.h - API関数のシグネチャ
■ HAPI_Common.h - 構造体と列挙型
■ HAPI_Version.h - バージョン情報
■ HAPI_Helpers.h - 構造体を初期化するための関数
■ HAPI_API.h - ライブラリのリンクとその他の定義

デフォルトのインストールオプションを使用した場合、これらのファイルは次の場所にあります。
"C:/Program Files/Side Effects Software/Houdini 17.0.372/toolkit/include/HAPI"

ネイティブアプリケーションの場合は、インポートライブラリとリンクする必要があります。
"C:/Program Files/Side Effects Software/Houdini 17.0.372/custom/houdini/dsolib/libHAPIL.lib"

*上記の例では17.0.372としていますが、インストールされているバージョンに合わせて書き換えを行ってください。特にバージョンによる不一致により動作しなくなることがあるので要注意です。 バージョン確認方法は補足:バージョンの確認にて。

HAPIを使用するときに最初に行う必要があるのは、HAPI_Initialize()を呼び出すことです。この機能は、libHAPI.dll内に存在します。またlibHAPI.dllは次の場所にあります。
C:\Program Files\Side Effects Software\Houdini 17.0.372\bin

このbinフォルダ内の全てのdllが適切にロードされるためには環境変数Pathにbinのパスを追加する必要があります。
image.png

HAPIとlibHAPIL.libのパスを通す

HESample.pro
#-------------------------------------------------
#
# Project created by QtCreator 2018-12-02T06:44:33
#
#-------------------------------------------------

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = HESample
TEMPLATE = app

DEFINES += QT_DEPRECATED_WARNINGS

SOURCES += \
        main.cpp \
        mainwindow.cpp

HEADERS += \
        mainwindow.h

FORMS += \
        mainwindow.ui

INCLUDEPATH += "C:/Program Files/Side Effects Software/Houdini 17.0.372/toolkit/include/HAPI"
LIBS += "C:/Program Files/Side Effects Software/Houdini 17.0.372/custom/houdini/dsolib/libHAPIL.lib"

HAPIのincludeとHAPI_Initializeの実行

まず初めに必要なHAPI(HAPI.h、HAPI_API.h、HAPI_Common.h)をincludeします。

次にHAPI_Initialize()を呼び出します。この関数によって必要なすべてのdllがロードされます。

HAPI_Initialize関数
HAPI_DECL HAPI_Initialize(
 const HAPI_Session *   session,//[in] Houdiniのセッション
 const HAPI_CookOptions *   cook_options,//[in] Cookによって使用されるグローバルCookオプション
 HAPI_Bool  use_cooking_thread,//[in] アセットのcookには別のスレッドを使用します
 int    cooking_thread_stack_size,//[in] スレッドのスタックサイズを設定します。スタックサイズをHoudiniのデフォルト値に設定するには、-1を使用します。この値はバイト単位です。
 const char *   houdini_environment_files,//[in] ":"か";"で区切られたパスのリスト(OSで異なります)。Houdiniのuser prefsフォルダのhoudini.envファイルと同じ構文に従った.envファイルに変換します。
 const char *   otl_search_path,//[in] OTLが検索されるディレクトリ
 const char *   dso_search_path,//[in] ジェネリックDSO(カスタムプラグイン)が検索されるディレクトリ
 const char *   image_dso_search_path,//[in] イメージDSO(カスタムプラグイン)が検索されるディレクトリ
 const char *   audio_dso_search_path //[in] オーディオDSO(カスタムプラグイン)が検索されるディレクトリ
)

引数がたくさんありますが、まずは最初の2つの引数であるセッションとCookオプションをポインタ渡しします。

main.cpp
#include "mainwindow.h"
#include <QApplication>
#include <QDebug>

#include <HAPI.h>
#include <HAPI_Common.h>

int main(int argc, char *argv[])
{
    HAPI_Session Session;
    HAPI_CookOptions CookOptions;
    CookOptions = HAPI_CookOptions_Create();
    HAPI_CreateInProcessSession(&Session);
    HAPI_Initialize(&Session,&CookOptions,true,-1,nullptr,nullptr,nullptr,nullptr,nullptr);

    qDebug() << "Hello Houdini Engine!";

//次からmain.cppと書いてあるコードをここにどんどん追加して下さい。

    HAPI_SaveHIPFile(&Session,"../test.hip",nullptr);

    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();
}

includeとHAPI_Initialize()が成功しているかどうかを、デバッグもしくはビルドを行って確認してください。HAPI_SaveHIPFileによってtest.hipファイルが作成されていれば成功です。
ここで読み込みが成功したのなら、HoudiniEngine入門の道のりは半分は達したようなものです。

ちなみに中を開くと空っぽ(謎のノードGlobalNodesはありますが)の状態です。
image.png

*このステップでは、ライセンスの確認は行われません。ノード作成時とアセットライブラリを読み込もうとしたときだけ、実際にライセンスをチェックします。 ライセンスについては補足:ライセンス にて
*dllが他で利用されている場合衝突が発生する可能性があります。
*今回はmain.cppにどんどんコードを追加していきます。本気でやる方は名前空間を分けて、クラスの設計もしっかり行う必要があります。
*ポインタ渡しがわからない方はこちら

ノードIDの取得

ここで聞きなれない単語が出てきました。「ノードID」とは何でしょうか。

ノードID(HAPI_NodeId)の基礎

Houdiniのすべての関数とデータはノードとして表されます。HoudiniEngineでは、ノードはHAPI_NodeIdによって識別されます。

HAPI_CreateNode関数でbox作成

例えば、HAPI_CreateNode関数によってboxノードを追加できます。

HAPI_CreateNode関数
HAPI_DECL HAPI_CreateNode(
 const HAPI_Session *   session,//[in] Houdiniのセッション
 HAPI_NodeId    parent_node_id,//[in] 親ノードのネットワークのノードID、または親ネットワークがマネージャ(最上位)ノードの場合は-1
 const char *   operator_name,//[in] ノードオペレータタイプの名前
 const char *   node_label,//[in] 新しく作成されたノードのラベル
 HAPI_Bool  cook_on_creation,//[in] 作成したノードを一度作成するかどうかを設定します
 HAPI_NodeId *  new_node_id //[out] 直前に作成されたノードIDが渡した変数に格納される 次の処理に使うのはこいつ
)   
main.cpp boxノードの作成
        HAPI_NodeId box_node_id;
        HAPI_CreateNode( &Session, -1, "Sop/box", "Box_Node_Name", true, &box_node_id );

parent_node_id == -1を渡した場合、operator_nameにはテーブル名(つまりObject/またはSop/)が含まれていなければなりません。HAPI_CreateNodeの第2引数に親となるジオメトリのnode_idを指定していれば、例えばGeometryネットワーク内の場合は"Sop/box"でなく"box"として作成できます。ここで再度ビルドしてboxが作成されていることを確認します。
image.png

ノードオペレータの名前の調べ方は2017年の@it_ksさんの「ノードっていくつあるんですか?」にあるpythonからのprint出力を使うことで調べることができます。

sopNodeTypeCategory(Python)
sopNodes = hou.sopNodeTypeCategory().nodeTypes()
print sorted( sopNodes.keys() )
objNodeTypeCategory(Python)
objNodes = hou.objNodeTypeCategory().nodeTypes()
print sorted( objNodes.keys() )

毎回pythonで調べるのが面倒!という場合は一度ノードを自分で作成して、その際のノード名から数字(下の図だと1)を抜いた文字(geo)を確認してください。
image.png
全部これでいいんじゃないの?と思われるかもしれませんが、operator_nameパラメータは、名前空間(Object/またはSop/)、名前(polyextrude)、およびバージョン(::2.0)を含める必要があります。例えばpolyextrudeは 'polyextrude'と'polyextrude::2.0'の2種類が存在しており、上記の方法だと新しいpolyextrudeの名前を知ることができません。

何はともあれHAPI_CreateNode関数でノードを作成し、作成後にポインタ渡ししたbox_node_idに作成したノードID(HAPI_NodeInfo)が格納されます。

ネットワークのnode_infoを求める

先ほどは"Sop/box"を利用することで、自動的にObjectsネットワークにGeometryノードが作成され、その中にboxノードを作成しました。次はparent_node_id == -1でなく、Geometryノードをparent_nodeとして同階層にgridノードとcopyノードを作成しましょう。

main.cpp boxと同階層にgridとcopyを作成
        // Check node_info.
        HAPI_NodeInfo box_node_info;
        HAPI_GetNodeInfo(&Session, box_node_id, &box_node_info );

        // Create a grid node.
        HAPI_NodeId grid_node_id;
        HAPI_CreateNode(&Session, box_node_info.parentId, "grid", "grid_Node_Name", true,&grid_node_id );

        // Create a copy node.
        HAPI_NodeId copy_node_id;
        HAPI_CreateNode(&Session, box_node_info.parentId, "copy", "copy_Node_Name", true,&copy_node_id );


"../../"で階層を指定できれば楽なのですが、相対パスは使えないのでnode_infoとparentIdを組み合わせて同階層を導きます。
image.png

ノードの接続

ノードを接続するためにはHAPI_ConnectNodeInput関数を利用します。

HAPI_ConnectNodeInput関数
//2つのノードを接続します
HAPI_DECL HAPI_ConnectNodeInput(
 const HAPI_Session *   session,//[in] Houdiniセッション
 HAPI_NodeId    node_id,//[in] 接続先のノード
 int    input_index,//[in] 入力インデックス。0とto_nodeのHAPI_NodeInfo::inputCount-1 の間でなければなりません。
 HAPI_NodeId    node_id_to_connect,//[in] node_idの入力に接続するノード。
 int    output_index //[in] 出力インデックス。0とto_nodeのHAPI_NodeInfo :: outputCount - 1 の間でなければなりません。
)   
main.cpp ノードの接続
        HAPI_ConnectNodeInput(&Session, copy_node_id, 0, box_node_id,0 );
        HAPI_ConnectNodeInput(&Session, copy_node_id, 1, grid_node_id,0 );

image.png

パラメータの取得

ここでgridのパラメータを覗いてみましょう。
パラメータの取得には段階を踏む必要があります。まずHAPI_GetParameters関数を利用してNodeInfo内に存在する複数のパラメータ群を取得した後、HAPI_GetParm〇〇〇Values関数で個別のパラメータを取り出します。〇〇〇にはInt、Float、Stringが入ります。HAPI_GetParametersで取得できるのは、様々なHAPI_ParmTypeが混ざった状態の配列です。
正確に言えばHAPI_ParmTypeに応じた型が入ります。

HAPI_ParmType
HAPI_PARMTYPE_INT   
HAPI_PARMTYPE_MULTIPARMLIST     
HAPI_PARMTYPE_TOGGLE    
HAPI_PARMTYPE_BUTTON    
HAPI_PARMTYPE_FLOAT     
HAPI_PARMTYPE_COLOR     
HAPI_PARMTYPE_STRING    
HAPI_PARMTYPE_PATH_FILE     
HAPI_PARMTYPE_PATH_FILE_GEO     
HAPI_PARMTYPE_PATH_FILE_IMAGE   
・・・etc

HAPI_ParmTypeの値の型は、Int、Float、Stringに分類されます。例えばHAPI_ParmTypeがToggleで値がInt型、HAPI_ParmTypeがPATH_FILEで値がString型など、パラメータに応じた型が必要です。
そこで、Getを行う前にInt、Float、Stringに分類します。分類はHAPI_ParmInfo_Is〇〇関数(HAPI_ParmInfo_IsInt、HAPI_ParmInfo_IsFloat、HAPI_ParmInfo_IsString)で判定します。

たとえば、gridで取得できるパラメータ名は以下のものになります。

["type","surftype","orient","size","t","r","rows","cols","orderu","orderv","interpu","interpv"]
image.png

では、これからInt型のrowsと、Float型のsizeを取得していきます。
HAPI_GetParametersの内訳はこのようになっています。

image.png

あらかじめ、取得したいパラメータの名前、型がわかっている場合は直接値を取得できます。

HAPI_GetParmIntValue関数
HAPI_DECL HAPI_GetParmIntValue(
 const HAPI_Session *   session,//[in] Houdiniのセッション
 HAPI_NodeId    node_id,//[in] ノードID
 const char *   parm_name,//[in] parmの名前
 int    index,//[in] パラメータの値タプル内のインデックス
 int *  value //[out] 戻り値のint値
)

上記はHAPI_GetParmIntValueですが、同じ要領でHAPI_GetParmFloatValue、HAPI_GetParmStringValueがあります。

main.cpp パラメータの値を取得
    int rows_parm_value;
    HAPI_GetParmIntValue(&Session,grid_node_id,"rows",0,&rows_parm_value);
    qDebug() << "rows_parm_value="<<rows_parm_value;//rows_parm_value=10

    float sizex_parm_value;
    float sizey_parm_value;
    HAPI_GetParmFloatValue(&Session,grid_node_id,"size",0,&sizex_parm_value);
    HAPI_GetParmFloatValue(&Session,grid_node_id,"size",1,&sizey_parm_value);
    qDebug() << "sizex_parm_value=" << sizex_parm_value;//sizex_parm_value=10.0
    qDebug() << "sizey_parm_value=" << sizey_parm_value;//sizey_parm_value=10.0

次に紹介するのはHAPI_GetParmFloatValues関数です。ValueでなくValuesなので、vectorのパラメータのように2つ、3つ同時に配列として取得したい場合に利用します。

HAPI_GetParmFloatValues関数
HAPI_DECL HAPI_GetParmFloatValues(
 const HAPI_Session *   session,//[in] Houdiniのセッション
 HAPI_NodeId    node_id,//[in] ノードID
 float *    values_array,//[out] 浮動小数点数の配列
 int    start,//[in] 範囲の最初のインデックス。少なくとも0で、最大でHAPI_NodeInfo::parmFloatValueCount - 1 
 int    length //[in] 少なくとも1で、多くともHAPI_NodeInfo::parmFloatValueCount - start 
)

仮に、パラメータ名が不明で全てのパラメータを取得する必要がある場合はノードInfo内のパラメータをすべてfor文で読み込みます。その際に名前とパラメータを確認します。

main.cpp 全パラメータ名、パラメータ値の取得
    //Change to the HAPI_NodeId you want to know
    HAPI_NodeId node_id = grid_node_id;

    HAPI_NodeInfo node_info;
    HAPI_GetNodeInfo( &Session, node_id, &node_info ) ;

    HAPI_ParmInfo * parmInfos = new HAPI_ParmInfo[ node_info.parmCount ];
    HAPI_GetParameters( &Session, node_id, parmInfos, 0, node_info.parmCount ) ;

    for( int i = 0; i < node_info.parmCount; ++i ){
        HAPI_StringHandle hsh=parmInfos[ i ].nameSH ;

        int buffer_length;
        HAPI_GetStringBufLength(&Session, hsh,  &buffer_length );

        char * buf = new char[ buffer_length ];
        HAPI_GetString( &Session, hsh, buf , buffer_length);

        //buf is parmname
        QString qstr = QString::fromStdString(buf);
        qDebug("%s", qPrintable("---------------"));
        qDebug("%s", qPrintable(qstr));
        delete[] buf;

        if ( HAPI_ParmInfo_IsInt( &parmInfos[ i ] ) ){//ParmInfo Is Int
            int parmIntCount = HAPI_ParmInfo_GetIntValueCount( &parmInfos[ i ] );
            int * parmIntValues = new int[ parmIntCount ];
            HAPI_GetParmIntValues( &Session,node_id, parmIntValues,parmInfos[ i ].intValuesIndex,parmIntCount ) ;

            for ( int v = 0; v < parmIntCount; ++v ){
                int piv= parmIntValues[ v ];

                qDebug() << piv ;
            }
            delete [] parmIntValues;

        }else if ( HAPI_ParmInfo_IsFloat( &parmInfos[ i ] ) ){//ParmInfo Is Float

            int parmFloatCount = HAPI_ParmInfo_GetFloatValueCount( &parmInfos[ i ] );
            float * parmFloatValues = new float[ parmFloatCount ];
            HAPI_GetParmFloatValues( &Session,node_id, parmFloatValues,parmInfos[ i ].floatValuesIndex,parmFloatCount ) ;

            for ( int v = 0; v < parmFloatCount; ++v ){
                float pfv= parmFloatValues[ v ];
                qDebug() << pfv ;
            }
            delete [] parmFloatValues;


        }else if ( HAPI_ParmInfo_IsString( &parmInfos[ i ] ) ){//ParmInfo Is String

            int parmStringCount = HAPI_ParmInfo_GetStringValueCount( &parmInfos[ i ] );
            HAPI_StringHandle * parmSHValues = new HAPI_StringHandle[ parmStringCount ];
            HAPI_GetParmStringValues( &Session,node_id,true, parmSHValues,parmInfos[ i ].stringValuesIndex,parmStringCount );

            for ( int v = 0; v < parmStringCount; ++v ){
                HAPI_ParmInfo parmInfo=parmInfos[ i ];
                HAPI_StringHandle shv = parmSHValues[ v ];

                int buffer_length;
                HAPI_GetStringBufLength(&Session, shv,  &buffer_length );
                char * buf = new char[ buffer_length ];
                HAPI_GetString( &Session, shv, buf , buffer_length);
                QString qstr = QString::fromStdString(buf);
                qDebug() << qstr;
            }
            delete [] parmSHValues;
        }
    }
    delete [] parmInfos;

パラメータ名の取得はHAPI_ParmInfo::nameSHで行います。取得した段階ではHAPI_StringHandle型なので、文字として確認する場合は、デバッグでの文字変換同様、lengthの確認を経てString型に変換する必要があります。

パラメータの設定

取得ができたので、次はパラメータに値を代入します。セットするためにはHAPI_SetParmIntValue関数を利用します。

HAPI_SetParmIntValue関数
 HAPI_DECL HAPI_SetParmIntValue (
 const HAPI_Session *   session,//[in] Houdiniのセッション
 HAPI_NodeId    node_id,//[in] ノードID
 const char *   parm_name,//[in] parmの名前
 int    index,//[in] パラメータの値タプル内のインデックス
 int    value //[in] int値
)
main.cpp パラメータ値の設定
        HAPI_SetParmIntValue(&Session,grid_node_id,"rows",0,5);

rowsが初期値10であったので、5に上書きされました。
想定としては以下のようになるはずです。
rowdown.png

しかし、実際にhipファイルを開くとboxが1つあるだけです。
image.png

なぜかというと、DisplayFlagがBoxについた状態のため、最終段のcopyまでcookされていないからです。
image.png

ノードのCook

DisplayFlagやRenderFlagについては、HoudiniEngine側としてはあまり関係がありません。
あくまでこれらのFlagはHoudini内のGUIとして機能するからです。

HoudiniEngineから欲しい結果をcookする場合はHAPI_CookNode関数を利用します。

HAPI_CookNode関数
HAPI_DECL HAPI_CookNode (
const HAPI_Session *    session,//[in] Houdiniのセッション
HAPI_NodeId     node_id,//[in] ノードID
const HAPI_CookOptions *    cook_options //[in] cookオプション
)   

fileSOPからobj出力でcook確認

さきほどのネットワークだと、本当にcookされているのか確認し辛いので、fileSOPノードを追加してcookが行われているかを確認します。

image.png

main.cpp fileノード追加
        // Create a file node.
        HAPI_NodeId file_node_id;
        HAPI_CreateNode(&Session, box_node_info.parentId, "file", "file_Node_Name", true,&file_node_id );

        HAPI_ConnectNodeInput(&Session, file_node_id, 0, copy_node_id,0 );

        HAPI_SetParmIntValue(&Session,file_node_id,"filemode",0,2);
        HAPI_ParmId file_parm_id;
        HAPI_Result aaarsutlt=HAPI_GetParmIdFromName(&Session,file_node_id,"file",&file_parm_id);
        HAPI_SetParmStringValue(&Session,file_node_id,"../testoutput.obj",file_parm_id,0);

        HAPI_CookNode ( &Session, file_node_id, &CookOptions );

        int cookStatus;
        HAPI_Result cookResult;
        do{
        cookResult = HAPI_GetStatus( &Session, HAPI_STATUS_COOK_STATE, &cookStatus );
        }
        while (cookStatus > HAPI_STATE_MAX_READY_STATE && cookResult == HAPI_RESULT_SUCCESS);

以上のコードでfilemodeがReadMode(読み込みモード)からWriteMode(書き込みモード)に変わり、fileパラメータがデフォルトの"default.bgeo"から"../testoutput.obj"に書き換わりました。
image.png

ビルドさせてcookした瞬間にファイルが生成されました。成功してますね。
image.png

本来はHAPI_CookNodeだけでいいのですが、後に処理が続いています。これは、HAPI_CookNode関数が非同期処理のため、cook出来ていない状態で次の処理に行くことを防いでいます。(cookが完了したことを確認して次に進みます。)
do-while内でHAPI_GetStatus関数を利用してHAPI_StatusHAPI_Stateが適正な返り値となっているかどうか確認します。問題なければcookStatusはHAPI_STATE_READYである0となっているはずです。

HAPI_State
 HAPI_STATE_READY, //エラーなくすべてが正常にcookされた
 HAPI_STATE_READY_WITH_FATAL_ERRORS, //cook失敗
 HAPI_STATE_READY_WITH_COOK_ERRORS, //一部のオブジェクトのみが失敗
 HAPI_STATE_STARTING_COOK,
 HAPI_STATE_COOKING,
 HAPI_STATE_STARTING_LOAD,
 HAPI_STATE_LOADING,
 HAPI_STATE_MAX,
 HAPI_STATE_MAX_READY_STATE = HAPI_STATE_READY_WITH_COOK_ERRORS

do-whileはまず文を実行してから、継続条件の判定を行います。つまり少なくとも1回はHAPI_GetStatusの確認処理を実行します。そして、継続条件で偽になると脱出します。

HAPI_GetStatus関数
HAPI_DECL HAPI_GetStatus(
 const HAPI_Session *   session,//[in] Houdiniセッション
 HAPI_StatusType    status_type,//[in] HAPI_STATUS_CALL_RESULT や HAPI_STATUS_COOK_STATE 
 int *  status //[out] 実際のステータスコード HAPI_ResultやHAPI_State
)

ここまでboxノードやcopyノードを作成してきました。しかし、boxやcopyなどの処理はHoudini内部で初めから用意されている処理で、外部との連携という意味でいえば「ノードを作成しろ」「パラメータを変更しろ」という命令しか出していません。

HAPI_CreateInputNode関数でジオメトリ入力受付

たとえば、別のアプリで作成したジオメトリの情報をfileに書き出すことなくHoudiniEngineに渡す場合どうすればいいでしょうか。

ジオメトリを渡す場合は、HAPI_CreateInputNode関数を利用することで、ジオメトリ入力を受け入れることができるGeometrySOPノードを作成できます。これにより、Null SOPを持つダミーのOBJノードが作成され、ジオメトリSET APIを使用するジオメトリを設定できます。
その後、このノードをジオメトリ入力として他のノードに接続することができます。

HAPI_SaveHIPFile()を使用してHoudiniシーンを保存すると、このメソッドで作成されたノードは緑色になり、 "input"という名前で始まります。

HAPI_CreateInputNode関数
HAPI_DECL HAPI_CreateInputNode  (
const HAPI_Session *    session,//[in] Houdiniのセッション
HAPI_NodeId *   node_id,//[out] 新しく作成されたノードのID
const char *    name //[in] 簡単なデバッグのために、この入力ノードに名前を付けます。
//ノードの親OBJノードとNullSOPノードは、この名前の前に "input_"が付加された名前を取得します。
//NULLを渡すこともできます。その場合、名前は "input#"になります。#は数字です。
)   
main.cpp input_読み込み
    HAPI_NodeId newNode;
    HAPI_CreateInputNode( &Session, &newNode, "Cube" );

HAPI_CreateInputNodeでinput_Cubeが出来ましたが、実態は何もありません。
なぜなら何も入力していないからです。

image.png

そこで、このNullにメッシュ情報を追加していきます。

main.cpp input_読み込み
        // Creating the triangle vertices
        HAPI_PartInfo newNodePart = HAPI_PartInfo_Create();

また新しい単語が出てきました。「Part」とはなんでしょうか?パーツを直訳すると部品ですが、、

PartInfoの作成

Partsは、Houdiniのジオメトリ内のプリミティブグループに基づいて計算されます。
そして、Partsにはmesh/geometry/attributeデータが含まれています。

main.cpp input_読み込み
        newNodePart.type = HAPI_PARTTYPE_MESH;
        newNodePart.faceCount = 6;
        newNodePart.vertexCount = 24;
        newNodePart.pointCount = 8;

        HAPI_SetPartInfo( &Session, newNode, 0, &newNodePart ) ;

よって、HAPI_PartInfo_Create関数で作ったnewNodePartオブジェクトに対して、
どんな型か?(Mesh、CURVE、VOLUME、INSTANCERなど)、faceの数は?(例えば立方体だと6面)、vertexの数は?(例えば立方体だと24vertex)、pointの数は?(立方体だと8頂点) というようにプロパティ情報を付与していきます。

まとまった時点で、先ほどの空っぽのnullノード(newNode)に対してnewNodePartオブジェクトをSetします。

PartInfoにAttribute付与

次は、Attribute情報を付与します。Attributeを付与するにはHAPI_AddAttribute関数内で、ノードIDに加えてPartInfoが必要になります。

HAPI_AttributeInfo構造体PublicAttributes
 HAPI_Bool  exists//存在確認
 HAPI_AttributeOwner    owner//所有者、、というよりも、どのRun Overか?というほうがわかりやすいかもしれません
 HAPI_StorageType   storage//格納される型 INT型か?など HAPI_StorageTypeを参照
 HAPI_AttributeOwner    originalOwner//GAジオメトリ形式からGTジオメトリ形式に変換された場合のため

 int    count
//count: Attributeの数。この数はownerに与えられた値の数と一致します。
//たとえば、所有者がHAPI_ATTROWNER_VERTEXの場合、このカウントはHAPI_PartInfo :: vertexCountと同じになります。
//これはAttribute内の値の数ではなく、Attributeの数です。
//ジオメトリに3つの3Dポイントがある場合、HAPI_AttributeInfo::tupleSizeは3になりますが、
//countは3(3 * 3ではなく)になります。

 int    tupleSize
//tupleSize: Attributeごとの値の数。これはAttributeのメモリサイズではないことに注意してください。
//Attributeごとの値の数です。これにHAPI_AttributeInfo::storageのサイズを掛ければ、属性ごとのメモリサイズが得られます。

 HAPI_AttributeTypeInfo     typeInfo
HAPI_AttributeOwner
HAPI_ATTROWNER_INVALID  
HAPI_ATTROWNER_VERTEX   
HAPI_ATTROWNER_POINT    
HAPI_ATTROWNER_PRIM     
HAPI_ATTROWNER_DETAIL 
HAPI_StorageType
HAPI_STORAGETYPE_INVALID    
HAPI_STORAGETYPE_INT    
HAPI_STORAGETYPE_INT64  
HAPI_STORAGETYPE_FLOAT  
HAPI_STORAGETYPE_FLOAT64    
HAPI_STORAGETYPE_STRING 
HAPI_AddAttribute関数
HAPI_DECL HAPI_AddAttribute(
 const HAPI_Session *   session,//[in] Houdiniセッション
 HAPI_NodeId    node_id,//[in] SOPノードID
 HAPI_PartId    part_id,//[in] 現在使用されていません。ただ0を渡します。
 const char *   name,//[in] Attribute名
 const HAPI_AttributeInfo *     attr_info //[in] Attributeのプロパティが格納された  HAPI_AttributeInfo 
)   
HAPI_SetAttributeFloatData関数
 HAPI_DECL HAPI_SetAttributeFloatData(
 const HAPI_Session *   session,
 HAPI_NodeId    node_id,
 HAPI_PartId    part_id,
 const char *   name,
 const HAPI_AttributeInfo *     attr_info,
 const float *  data_array,
 int    start,
 int    length 
)   

以上を踏まえたうえで、newNodePointInfoのHAPI_AttributeInfoプロパティを見ていきます。
これから入れるAttributeはポジション情報のPointAttributeである"P"に関する情報です。
countはAttributeの数なので、PointAttributeの場合当然立方体の頂点数と同じ8になります。
existsはtrueにして、storageは浮動小数点のHAPI_STORAGETYPE_FLOATを選択します。
ownerはHAPI_AttributeOwnerの中からHAPI_ATTROWNER_POINTを選択します。

HAPI_AddAttributeした時点では値は何も入っていません。続けてSetAttributeが必要です。

storageをHAPI_STORAGETYPE_FLOATとしたので、HAPI_SetAttributeFloatData関数を利用しましたが、この他に、HAPI_SetAttributeFloat64Data、HAPI_SetAttributeInt64Data、HAPI_SetAttributeIntData、HAPI_SetAttributeStringDataがあります。storageに応じて変更する必要があります。

main.cpp input_読み込み
        HAPI_AttributeInfo newNodePointInfo = HAPI_AttributeInfo_Create();
        newNodePointInfo.count = 8;
        newNodePointInfo.tupleSize = 3;
        newNodePointInfo.exists = true;
        newNodePointInfo.storage = HAPI_STORAGETYPE_FLOAT;
        newNodePointInfo.owner = HAPI_ATTROWNER_POINT;

        HAPI_AddAttribute( &Session, newNode, 0, "P", &newNodePointInfo );

        float positions[ 24 ] = { 0.0f, 0.0f, 0.0f,   // 0
                      0.0f, 0.0f, 1.0f,   // 1
                      0.0f, 1.0f, 0.0f,   // 2
                      0.0f, 1.0f, 1.0f,   // 3
                      1.0f, 0.0f, 0.0f,   // 4
                      1.0f, 0.0f, 1.0f,   // 5
                      1.0f, 1.0f, 0.0f,   // 6
                      1.0f, 1.0f, 1.0f }; // 7

        HAPI_SetAttributeFloatData( &Session, newNode, 0, "P", &newNodePointInfo, positions, 0, 8  );

ここまでで、頂点の作成はできました。しかし、まだ面が生成できていません。
faceを作成するにはHAPI_SetVertexList関数とHAPI_SetFaceCounts関数を組み合わせて最終的にHAPI_CommitGeoする必要があります。

HAPI_SetVertexList関数
HAPI_DECL HAPI_SetVertexList(
//頂点と点の関連を含む配列を設定します。ここで、配列のi番目の要素は、i番目の頂点が関連する点のインデックスです。
 const HAPI_Session *   session,//[in] Houdiniのセッション
 HAPI_NodeId    node_id,//[in] ノードID
 HAPI_PartId    part_id,//[in] 現在使用されていません。ただ0を渡します。
 const int *    vertex_list_array,//[in] 少なくともlengthの大きさの整数の配列。
 int    start,//[in] 範囲の最初のインデックス。少なくとも0で、最大でHAPI_PartInfo::vertexCount-1 でなければなりません。
 int    length //[in] 少なくとも0で、最大でHAPI_PartInfo::vertexCount-startでなければなりません。
)
HAPI_SetFaceCounts関数
//配列のn番目の整数がn番目の面にある頂点の数である面の配列を設定します。
HAPI_DECL HAPI_SetFaceCounts(
 const HAPI_Session *   session,//[in] Houdiniセッション
 HAPI_NodeId    node_id,//[in] ノードID
 HAPI_PartId    part_id,//[in] 現在使用されていません。ただ0を渡します。
 const int *    face_counts_array,//[in] 少なくとも配列の長さの整数配列
 int    start,//[in] 最初のインデックス。少なくとも0で、最大でHAPI_PartInfo::faceCount - 1 
 int    length //[in] 長さ、少なくとも0で、多くともHAPI_PartInfo::faceCount - startでなければなりません。
)   
main.cpp input_読み込み
        int vertices[ 24 ] = { 0, 2, 6, 4,
                   2, 3, 7, 6,
                   2, 0, 1, 3,
                   1, 5, 7, 3,
                   5, 4, 6, 7,
                   0, 4, 5, 1 };

        HAPI_SetVertexList( &Session, newNode, 0, vertices, 0, 24  );

        int face_counts [ 6 ] = { 4, 4, 4, 4, 4, 4 };

        HAPI_SetFaceCounts( &Session, newNode, 0, face_counts, 0, 6 ) ;

        HAPI_CommitGeo( &Session, newNode );

image.png

ここでビルドします。フリーズ状態でboxが保持されていることが確認できます。

デジタルアセット

入力したboxとデジタルアセットを接続してみます。
そのために、まずはデジタルアセットの準備を行います。

今回はテストなのでsubdivideとmountainのみのシンプルなHDAを作成します。
asset0.png

外に出すパラメータとしてmountainのHeightとElement Sizeを置きます。
image.png

結果の確認
asset.png

HAPI_LoadAssetLibraryFromFile関数でotl・hda読み込み

アセットを読み込んでノードのインスタンス化をする場合は、HAPI_LoadAssetLibraryFromFileでotl、hdaを読み込み、boxと同様にHAPI_CreateNodeします。デジタルアセットがObject/かSop/かどうかは適宜変更が必要です。

main.cpp mytestsubnet6.otlの読み込み
    // Load the library from file.
       HAPI_AssetLibraryId assetLibId = -1;
       HAPI_Result result = HAPI_LoadAssetLibraryFromFile(
           &Session, "C:/Users/jyour/Documents/houdini17.0/otls/testAsset.hdalc",
           false, &assetLibId );

       HAPI_NodeId hda_node_id = -1;
       result = HAPI_CreateNode(
           &Session, -1, "Sop/testAssetName",
           nullptr, true, &hda_node_id );

今回は自分でデジタルアセットを作ったので、利用できる事もアセットの名前も自明でしたが、例えばotlを随時読み込んで処理するとなった場合はもうひと手間必要です。HAPI_LoadAssetLibraryFromFileを行った後にまず、HAPI_GetAvailableAssetCount関数でアセットライブラリに含まれるアセットの数を取得します。その後にHAPI_GetAvailableAssets関数を利用してotl、hdaでのString_Handle、アセットの名前を取得します。詳細については割愛します。

デジタルアセットと接続

ノードを接続するためには先ほどboxとcopyの接続にも使用したHAPI_ConnectNodeInput関数を利用します。

main.cpp ジオメトリ入力受付とデジタルアセットの接続
HAPI_ConnectNodeInput(&Session, hda_node_id, 0, newNode,0 );

ビルド実行するとデジタルアセットのノードとInput用のノードが作成されていることが確認できます。
image.png
HAPI_ConnectNodeInputによって、ObjectMergeが実行されています。
先ほどboxとcopyをつなげた際は、同一ネットワーク内でしたが、今回は別のジオメトリを跨ぐためObjectMergeとなっています。

image.png

デジタルアセットのパラメータ取得

パラメータの取得や設定は、これまでやってきた方法と同じ方法です。パラメータをセットした後は忘れずcookを行います。

main.cpp hdaのパラメータ取得
        HAPI_SetParmFloatValue(&Session,hda_node_id,"height",0,5.0f);
        HAPI_SetParmFloatValue(&Session,hda_node_id,"elementsize",0,1.5f);

        HAPI_CookNode ( &Session, hda_node_id, &CookOptions );

        int cookStatus2;
        HAPI_Result cookResult2;
        do
        {
        cookResult2 = HAPI_GetStatus( &Session, HAPI_STATUS_COOK_STATE, &cookStatus2 );
        }
        while (cookStatus2 > HAPI_STATE_MAX_READY_STATE && cookResult2 == HAPI_RESULT_SUCCESS);

mountainに大きい値が入ったので、ちゃんと変形が確認できました。
image.png

この変更をホストアプリケーションに戻す場合は、HAPI_GetPartInfo関数で必要なAttributeなどの情報を戻します。

HAPI_GetPartInfo関数
HAPI_DECL HAPI_GetPartInfo(
const HAPI_Session *    session,// [in] Houdiniセッション
HAPI_NodeId     node_id,// [in] ノードID
HAPI_PartId     part_id,// [in] Part ID
HAPI_PartInfo *     part_info// [out] HAPI_PartInfo戻り値
)

クリーンアップ

HAPI_PartInfoの情報をホストに戻すことで一件落着と、その前に忘れずセッションのクリーンアップをする必要があります。

クリーンアップ
HAPI_Cleanup(&Session);

QtのUIと紐づけ

せっかくQtを利用しているので、フォームに値を入れて戻る動作を確認します。
mainwindows.uiをダブルクリックしてQtDesigner画面に遷移します。
image.png

image.png

今回はサンプルなので、限定した機能として
・ノードを作成するボタン
・値を調整するパラメータエリア
・Cookするためのボタン
を用意します。これを、otlを動的に変えたり、3Dビュー上で表示しながらHoudiniEngineのフィードバックを得る場合は、汎用的な設計が必要になるため割愛します。というよりも僕の実力ではまだ作れません。公式のgitに上がっているプラグインや@K240さんが作成したHoudiniEngineQtを参考に設計をしていく必要があります。

main.cppから引っ越し

せっかく最後まで動いたところですが、このままだと実行のたびにすべての処理が走ってしまいます。
そこで、ボタンを押したらBoxだけ作成。パラメータを変更したら値にSet。など処理ごとに仕分けをしていきます。
そのためにはmain.cppでは厳しいので、mainwindow.hとmainwindow.cppへ中身をごっそり持っていきます。

*あくまで今回のサンプルの作り方なので、ベストプラクティスはまた別に存在します。

まず、Clickボタンを配置します。
image.png
そして右クリックでスロットに追加します。
image.png

そうすると、mainwindow.hとmainwindow.cppに関数が追加されているので、必要な処理を追加します。
SessionやCookOptionsなどほかの関数でも流用する場合はmainwindow.hのクラス内でメンバを指定する必要があります。

あとは基本的に同じ動作を繰り返します。
HAPI_Cleanup(&Session);はmainwindowのデストラクタ内にひとまず置きました。

これでビルドを行えば完成です。
image.png

ファイルはここに置いておきます
HESample
*一部機能が実装できなかったのでまた差し替えます。

以上、完成です。お疲れさまでした。

HoudiniEngineAPI入門いかがでしたでしょうか。簡単ではないけれども一切理解できない状態から、少しは霧が晴れたのではないでしょうか?2016年の@satoruhigaさんが紹介した「HDKについて」同様に、この技術がすぐに役に立つことはなかなか無いとは思います。
しかしHoudiniEngineAPIという外部からHoudiniの力を引き出せる強力な機能として、Hythonでは出来なかったホストアプリケーションとの連携や速度向上など様々なメリットがHoudiniEngineにはあります。この記事が皆様のお役に立てれば幸いです。

次のアドベントカレンダーは@sugiggyさんの「失ったTransformを取り戻す。」です。

補足

ここでは、一連の流れで横道にそれるために省略した内容を補足としてまとめています。

補足:Qtのインストール

Qtのダウンロードは以下の場所から行います。
https://www.qt.io/download
Commercial版とOpenSource版がありますが、今回はOpenSourceを選びます。>Go Open Source
Ot1.PNG

Nextとskipでどんどん進みます。
setup00.png

コンポーネントの選択ではQt 5.11.2/MSVC 2017 64-bitと
Tools/Qt Creator 4.7.2 CDB Debugger Support
を選択します。
setup4.PNG

どんどん次に進みます。
setup01.png
ここで、数十分待てばインストール完了です。
setup9-1hlater.PNG

補足:Qtのビルド

インストールが完了したら、ビルドができるかを確認します。
新しいプロジェクトをクリックしてプロジェクトを作成します。
newProject.PNG
ここは好みだとは思いますが、今回はQtウィジェットアプリケーションを選択します。
newProject2.PNG
プロジェクトの名前とパスを入れます。
newProject3.PNG
キットは先ほどインストールしたMSVC 2017 64-bitを選択します。
newProject4.PNG
クラス情報とプロジェクト管理はそのままに、完了します。
newProject5.PNG
newProject6.PNG
main.cpp含むフォームが出るだけの初期状態プロジェクトが表示されました。
newProject7.PNG
ビルドが走るかを確認します。
build.PNG
完了したら、実行させます
run.PNG

これで空っぽのフォームが出てくれば、ビルドは成功しています。

補足:バージョンの確認

HoudiniEngineを使用してアプリケーションを作成する場合、環境変数を間違ってしまいHoudiniEngineの他のバージョンを指してしまう可能性があります。メモリにロードされるHoudini Engineのバージョンが、実際にコンパイルしたHoudini Engineのバージョンであることを確認する必要があります。*自明の場合は不要です。
確認方法として、HAPI_GetEnvInt(HAPI_EnvIntType int_type, int *value)を利用します。

Houdini 16.5.571の場合、HAPI_Version.hは

HAPI_Version.h
#define HAPI_VERSION_HOUDINI_MAJOR 16
#define HAPI_VERSION_HOUDINI_MINOR 5
#define HAPI_VERSION_HOUDINI_BUILD 571

#define HAPI_VERSION_HOUDINI_ENGINE_MAJOR 3
#define HAPI_VERSION_HOUDINI_ENGINE_MINOR 1
#define HAPI_VERSION_HOUDINI_ENGINE_API 14

となり、メジャーバージョンが16、マイナーバージョンが5、ビルドバージョンが571。
HoudiniEngineのバージョンは3.1であることがわかります。

HAPI_Versionを他から参照する場合はHAPI_GetEnvInt関数で欲しいバージョンを引数として、値を取得します。

main.cpp
    int Version_H_Major=0;
    int Version_H_Minor=0;
    int Version_H_Build=0;
    HAPI_GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_MAJOR,&Version_H_Major);
    HAPI_GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_MINOR,&Version_H_Minor);
    HAPI_GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_BUILD,&Version_H_Build);
    qDebug("Houdini %d. %d. %d",Version_H_Major,Version_H_Minor,Version_H_Build);

HoudiniEngineのバージョンによって、関数の引数が変わることがあります。
よって、昔のコードをそのまま持ってきても使えないケースが出てくることがあります。
その場合は、ドキュメントを参照して型に合った値を入れなければなりません。
特にHoudiniEngine2.0 からHoudiniEngine3.1に変わったときに大きな変更があったようです。

補足:環境変数

HoudiniEngineは、初期化時に次の環境変数を探します。
HAPI_LICENSE_MODE:
デフォルトではHoudiniEngineが最初にHoudiniEngineライセンスを検索し、見つからない場合は、Houdiniライセンスを取得します。どちらかのライセンスのみ取得する設定も可能です。

HAPI_PYTHON_VERSION:
特定のバージョンのPythonを強制的に使用するには、 '2.6'または '2.7'に設定します。これは、主にWindows上でホストPythonバージョンとHoudiniのPythonバージョンとの間で衝突する場合に便利です。

HFS(HoudiniFileSystem):
Houdiniインストールフォルダのパスを設定します。この変数は、HAPI_Initialize()またはHAPI_CreateInProcessSession()を呼び出すときにHAPI.dllを見つけるために使用されます。

補足:デバッグ 

HoudiniEngineが文字列をホストアプリケーションに返す場合、HoudiniEngineは文字列を直接処理しません。その代わりに、呼び出し元関数は返す文字列にHAPI_StringHandleを渡します。
HAPI_GetStringBufLength()を呼び出して、文字列を保持するために必要なバッファのサイズを取得。適切なサイズのバッファを割り当てた後、HAPI_GetString()を呼び出しせば文字列を取得できます。

戻り値HAPI_Resultとエラー文字列

HoudiniEngineのすべての関数は、HAPI_Resultの値を返します。HAPI_RESULT_SUCCESS以外の戻り値は、何らかの形式の失敗を示します。
最初にHAPI_GetStatusStringBufLength()を呼び出し、次にHAPI_GetStatusString()を呼び出すことによって、問題の詳細を得ることができます。

今回は標準入出力を出すのに工夫がいるQtを利用したため割愛しましたが、SideFXのドキュメントではstd::stringでのデバッグを推奨しています。
まず関数呼び出しを次のようなマクロにラップして

関数呼び出し
#define ENSURE_SUCCESS( result ) \
if ( (result) != HAPI_RESULT_SUCCESS ) \
{ \
    std::cout << "Failure at " << __FILE__ << ": " << __LINE__ << std::endl; \
    std::cout << getLastError() << std::endl; \
    exit( 1 ); \
}

HAPI_ResultがSUCCESSでないとき以下の関数を呼び出します。

最後のエラーを取得する
static std::string
getLastError()
{
    //必要なバッファのサイズを取得するためのint型の変数宣言    
    int bufferLength;

    //HAPI_GetStatusStringBufLength(inセッション,inステータスタイプ,in冗長,outバッファサイズ) 
    HAPI_GetStatusStringBufLength( nullptr,
                   HAPI_STATUS_CALL_RESULT,
                   HAPI_STATUSVERBOSITY_ERRORS,
                   &bufferLength );

    //char*型を使うことで文字列変数bufを宣言 バッファサイズを代入 
    char * buffer = new char[ bufferLength ];

    //HAPI_GetStatusString(inセッション,inステータスタイプ,out文字列の値,inバッファイサイズ)
    HAPI_GetStatusString( nullptr, HAPI_STATUS_CALL_RESULT, buffer, bufferLength );

    //char*型のbufから動的にサイズ変更可能な文字列resultに代入
    std::string result( buffer );

    //バッファを削除
    delete [] buffer;

    //バッファを削除
    return result;
}

実際にHAPI_RESULTが知りたい関数などに対してENSURE_SUCCESSでエラー確認を行います。

ENSURE_SUCCESS
ENSURE_SUCCESS( HAPI_LoadAssetLibraryFromFile( &session, hdaFile, true, &assetLibId ) );

詳しく知りたい方は、SideFXドキュメント内のSmapleに詳しく乗っているので、そちらを参照してください。
SideFXドキュメント内のSample

BreakPoint

ではQtでデバッグする場合はどうするかというとを利用するのでBreakPointを利用するのが最も楽にデバッグが可能です。
コードの左を左クリックすることでBreakPointの作成が可能です。
breakpoint.PNG

この状態でデバッグすると
debugPNG.PNG
BreakPoint時のパラメータ参照が可能です。

debug2.PNG

補足:その他のパラメータ問い合わせ

ノードIDを取得した後は、HAPI_GetNodeInfo関数を利用してノード情報にアクセスする必要があります。
HAPI_GetNodeInfo関数の呼び出し時にHAPI_NodeIdを指定すると、HAPI_NodeInfo構造体を埋めることができます。

  • アセットを取得する場合:HAPI_GetAssetInfo()を使用してHAPI_AssetInfoを取得。
  • オブジェクトを取得する場合:HAPI_GetObjectInfo()を使用してHAPI_ObjectInfoを取得。
  • ジオメトリを取得する場合:HAPI_GetGeoInfo()を使用してHAPI_GeoInfoを取得。
  • マテリアルを取得する場合:HAPI_GetMaterialInfo()を使用してHAPI_MaterialInfoを取得。

HAPI_NODEFLAGS_EDITABLEHAPI_NODEFLAGS_NETWORKHAPI_ComposeChildNodeList()を使用して編集可能なノードネットワークのリストを問い合わせることができます。

デジタルアセットの場合通常はロックされていて編集できませんが、
image.png
Operator Type Propertiesの設定でEditableNodesを指定することでパラメータが編集可能になります。
image.png

補足:ライセンス

HoudiniEngineは、これらのライセンスが見つかるまで、次の順序でこのライセンスをチェックします。

1)Houdini Engine
2)Houdini
3)Houdini FX
4)Houdini Engine Indie
5)Houdini Indie

非営利のライセンス(つまり、Houdini Apprentice)はサポートされていません。さらに、ティアリングは、HoudiniEngineでは許可されていません。たとえば、商用ライセンスを使用している間はエンジンでIndie(* lc)アセットを実行することはできません。
Indieアセットを実行するには、Indieライセンスをチェックアウトする必要があります。つまり、下位層ライセンスは、上位層ライセンスで作成されたアセットを実行できます。たとえば、Indieライセンスで動作するHoudiniEngineは、商用アセットを実行できます。Indieライセンスの使用は、承認されたプラグイン(Unity、Unreal、Maya、Max、C4D)に限定されます。

ライセンスチェック

HoudiniEngineは、以下の2パターンのAPIコール中に有効なライセンスを確認します。

ライブラリファイルのロード中:Asset Library Files
ノードの作成中:Node Creation および Create Node

image.png

ライブラリファイルのロード中に、有効なライセンスが見つからない場合は、次のHAPI_Resultを取得できます。

HAPI_Result 内容
HAPI_RESULT_NO_LICENSE_FOUND ライセンスが見つかりませんでした。
HAPI_RESULT_DISALLOWED_NC_LICENSE_FOUND サポートされていない非商用ライセンスのみが見つかりました。

ノードの作成中に、次のHAPI_Resultのライセンスに関する情報を取得できます。

HAPI_Result 内容
HAPI_RESULT_NO_LICENSE_FOUND ライセンスが見つかりませんでした。
HAPI_RESULT_DISALLOWED_NC_LICENSE_FOUND サポートされていない非商用ライセンスのみが見つかりました。
HAPI_RESULT_DISALLOWED_NC_ASSET_WITH_C_LICENSE 汚染している間は認められません、非商業アセットは商用ライセンスでは稼動できません。
HAPI_RESULT_DISALLOWED_NC_ASSET_WITH_LC_LICENSE 汚染している間は認められません、非商業アセットは限定商業ライセンスでは稼動できません。
HAPI_RESULT_DISALLOWED_LC_ASSET_WITH_C_LICENSE 汚染している間は認められません、限定された商業アセットは商用ライセンスでは稼動できません。
HAPI_RESULT_DISALLOWED_HENGINEINDIE_W_3PARTY_PLUGIN Indieライセンスは、承認されたプラグインでのみ使用できます。

参考

SideFXがHoudiniEngineを紹介している動画 2013年にSIDEFXLabsで開発され発表されました。
Introducing the Houdini Engine
image.png

HoudiniEngine紹介ページ

HoudiniEngineAPI 

SideFXドキュメント内のSample
以下の場所にあります。(見つけにくい!)
here.png

@K240さんが作成したHoudiniEngineQt

インストールしたディレクトリ内にもサンプルがあります。(見つけにくい!)
C:\Program Files\Side Effects Software\Houdini 17.0.372\engine\QtParameterUI
image.png

UE4 

SideFX紹介ページ
Houdini Engine for Unreal Documentation
SideFX GitHub HoudiniEngineForUnreal

Houdini Engine for Unrealのインストール(Houdini Apprenticeユーザー向け)
Unreal EngineにHoudini Engieをインストールする

Maya

SideFX紹介ページ
Houdini Engine for Maya Documentation
SideFX GitHub HoudiniEngineForMaya

2017年のAdventCalenderには@yuya_toriiさんがHoudiniEngine for Mayaについての記事を作成されています。
Houdini Engine For Maya を調べてみる

@suzukiMYさんの記事
Houdini Engine for Maya 16.5 – Test1

Unity

SideFX紹介ページ
Houdini Engine for Unity Documentation
SideFX GitHub HoudiniEngineForUnity

価格

Houdini Engine Indieは2016年3月まで 99米ドルで提供していましたが、無償となりました。
インストールは以下の場所から可能です。
https://www.sidefx.com/buy/#houdini1
indie.png
ただし、IndieでないHoudini Engineはレンタルライセンスで購入する必要があります。
Workstation Licenses(ノードロック)の場合スタジオでのライセンスの使用は5ライセンスまでに制限されています。また、代理店での購入はそれぞれ最低オーダー数が存在します。25本以上の購入はアカウントマネージャーに連絡してください。

*価格は2018年12月時点のものです。

SideFX

製品名 備考 価格:1本 価格:5本 価格:10本 価格:25本
HOUDINI ENGINE Indie ノードロック 最大3本まで FREE
HOUDINI ENGINE ノードロック 最大5本まで $499.00 USD
HOUDINI ENGINE (GAL:レンタル年単位) フローティング $795.00 USD $3875.00 USD $7500.00 USD $16495.00 USD
HOUDINI ENGINE (GAL:レンタル月単位) フローティング $175.00 USD $875.00 USD $1750.00 USD $4375.00 USD
HOUDINI ENGINE (GAL:レンタル週単位) フローティング $75.00 USD $375.00 USD $750.00 USD $1875.00 USD

代理店 Indyzone

http://indyzone.jp/catalog/advanced_search_result.php?keywords=HOUDINI%20ENGINE

製品名 本体価格 備考 オーダー数
HOUDINI ENGINE [GAL] ¥9,936 フローティング (7日間ライセンス) 最低10本から
HOUDINI ENGINE [GAL] ¥19,872 フローティング (14日間ライセンス) 最低5本から
HOUDINI ENGINE [GAL] ¥22,356 フローティング (30日間ライセンス) 最低5本から
HOUDINI ENGINE [GAL] ¥44,712 フローティング (60日間ライセンス) 最低2本から
HOUDINI ENGINE [GAL] ¥67,068 フローティング (90日間ライセンス) 最低2本から
HOUDINI ENGINE [GAL] ¥99,360 フローティング (1年間ライセンス) 25本以上も可能
HOUDINI ENGINE [WS] ¥62,100 ノードロック (1年間ライセンス) 最大5本まで

代理店 Borndigital

https://www.borndigital.co.jp/software/houdini-engine.html
※価格は税別です。

製品名 本体価格 備考 オーダー数
Houdini Engine(GAL:レンタル週単位) ¥9,200 7日間/フローティング 最低10本から
Houdini Engine(GAL:レンタル月単位) ¥20,700 30日間/フローティング 最低5本から
Houdini Engine(GAL:レンタル年単位) ¥92,000 1年間/フローティング 25本以上も可能
Houdini Engine(WS:レンタル年単位) ¥57,500 1年間/ノードロック 最大5本まで
Why do not you register as a user and use Qiita more conveniently?
  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
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