Houdiniプラグイン開発備忘録4 ~ カスタムSOPにカスタムパラメータを追加する編

今回は、カスタムノードにカスタムパラメータを追加してみます


VerbとProtoヘッダファイル

 最近のHDKでは、SOPノード作成にVerbという仕組みを使うことが多いようです。
 Verbというのは、かなりざっくりいうと、SOPの振る舞いを定義する際、ジオメトリへの処理と、それ以外のパラメータ操作などの処理を分離する仕組みです。

[参考]プログラム的にVerb(動詞)を使ったジオメトリ

 Verbを利用してSOPを作成する際、これまで何度か言及していた、「Protoヘッダファイル」をビルド手順の中で生成し利用することになります。


Protoヘッダファイルの主な役割

 Protoヘッダファイルには、ジオメトリ処理以外の実装が自動的に記述されます。

※これは現時点での理解なので間違っているかもしれませんが、この仕組みにより、開発者は面倒なパラメータとジオメトリ処理をつなぐ実装を省略し、主にジオメトリ操作に関する処理に注力すれば良くなります。

 主なProtoヘッダファイルの役割は以下のようなものになります。

  1. PRM_Templateを生成する
  2. パラメータの操作を簡単化する関数群を自動生成する

Protoヘッダファイルの作り方

 Protoヘッダファイルは何もしなくても勝手に作成されるわけではなく、ソースコードの中にそのノードがどのようなパラメータを持っているかを示す「Dialog Script」を記述、または「*.ds形式のファイル」として用意して、PRM_TemplateBuilderに与える必要があります。

 このDialog Scriptは、ソースコード中で *theDsFile に記述します。

static const char *theDsFile = R"THEDSFILE(
・・・
)THEDSFILE";
//SOP_Starの例

static const char *theDsFile = R"THEDSFILE(
{
    name        parameters
    parm {
        name    "divs"      // Internal parameter name
        label   "Divisions" // Descriptive parameter name for user interface
        type    integer
        default { "5" }     // Default for this parameter on new nodes
        range   { 2! 50 }   // The value is prevented from going below 2 at all.
                            // The UI slider goes up to 50, but the value can go higher.
        export  all         // This makes the parameter show up in the toolbox
                            // above the viewport when it's in the node's state.
    }
    parm {
        name    "rad"
        label   "Radius"
        type    vector2
        size    2           // 2 components in a vector2
        default { "1" "0.3" } // Outside and inside radius defaults
    }
    parm {
        name    "nradius"
        label   "Allow Negative Radius"
        type    toggle
        default { "0" }
    }
    parm {
        name    "t"
        label   "Center"
        type    vector
        size    3           // 3 components in a vector
        default { "0" "0" "0" }
    }
    parm {
        name    "orient"
        label   "Orientation"
        type    ordinal
        default { "0" }     // Default to first entry in menu, "xy"
        menu    {
            "xy"    "XY Plane"
            "yz"    "YZ Plane"
            "zx"    "ZX Plane"
        }
    }
}
)THEDSFILE";

Dialog Scriptを生成する

 頑張って調べてみましたが、この記事を執筆している時点では、Dialog Scriptに関するリファレンスマニュアルのようなものを見つけることは出来ませんでした。
 そのかわり、リファレンスに頼ってフルスクラッチで書かなくとも、簡単にDialog Scriptを生成する方法があったので紹介します。

 やり方は、概ね以下の手順になります。

  1. パラメータ定義を抽出するためHDAを作成
  2. HDAに、カスタムノードに持たせたいパラメータを作成
  3. HDAのhou.ParmTemplateGroup.asDialogScript()でDialog Scriptのコードを取得
# Dialog Scriptを出力するサンプル
hda_node = hou.node("/to/hda/node/path")
hda_type = hda_node.type()
hda_definition = hda_type.definition()
hda_parm_template_group = hda_definition.parmTemplateGroup()
hda_dialog_script = hda_parm_template_group.asDialogScript()
print(hda_dialog_script)

 これにより、そのHDAに定義されたパラメータ定義が、Dialog Scriptの形式で取得できます。

 その後、ソースコードの*theDsFileに、取得したコードをそのままコピペするなどして与えた後、いつも通りの手順でビルドするだけです。


テスト

 以下は、HDAとして標準で用意されているColor SOPからDialog Scriptを頂いて、カスタムSOPにそのまま持たせてみたものです。


以上、カスタムSOPにカスタムパラメータを追加する方法でした。
なにか間違いや、不明な点などあれば、コメントいただけると嬉しいです。

Houdiniプラグイン開発備忘録3 ~ 最小構成のテンプレを書いてみる編

最近、どうしても自作したいSOPがあり、Windows環境でのHoudiniのプラグイン開発を少しずつ学び始めたので、忘れないようにメモしていこうと思います。

今回は、SOP_Starのサンプルコードを参考にしながら、全く何の処理もパラメータも持たない、空っぽのSOPを作る最小構成のコードを書いてみました。

が、事細かにコードの説明を書いてもとりとめが無くなりすぎて、読むに耐えない感じになり非常に微妙だったので、簡単な解説付きのテンプレートコードを転載するに留めます。

いくつか補足が必要そうな箇所もあるので、後でメモを書くかも。

※コメント中の

TODO:は要編集の箇所
NOTE:はメモ

VSCodeでTODO Highlightなどを使うと見やすいかも

名前規則

サンプルを見る限り、以下のようなルールで各要素に名付けを行うのが通例っぽいです
以下は、templateというラベル名のSOPの場合

■各ファイル
SOP_Template.cpp
SOP_Template.hpp
SOP_Template.proto.h

■コード内の各要素
SOP_Template         カスタムSOPクラス名
SOP_TemplateVerb     カスタムNodeVerbクラス名
SOP_TemplateParms    カスタムSOPのパラメータテンプレートクラス名

SOP_Template.hpp(.h)

最小構成のヘッダファイルは以下のようなものになりそう。

// TODO: インクルードガードはカスタムSOPクラス名に合わせ指定
#ifndef __SOP_Template_h__
#define __SOP_Template_h__

// NOTE: 編集不要
#include <SOP/SOP_Node.h>
#include <UT/UT_StringHolder.h>

// TODO: ネームスペースを書き換える
namespace SOP_TEMPLATE
{
    // NOTE: ここではSOP_Nodeクラスを継承し、カスタムのSOPクラスを宣言する
    // TODO: カスタムSOPクラスを指定
    class SOP_Template : public SOP_Node
    {
    public:
        static OP_Node *myConstructor(OP_Network *net, const char *name, OP_Operator *op)
        {
            // NOTE: 自身のインスタンスを返すようにする
            // TODO: カスタムSOPクラスを指定
            return new SOP_Template(net, name, op);
        }

        // NOTE: 以下は編集不要
        static PRM_Template *buildTemplates();                 // パラメータテンプレートクラスを返す
        virtual const SOP_NodeVerb *cookVerb() const override; // Verbの仕組みで処理を行うメソッド
        static const UT_StringHolder theSOPTypeName;           // ノードタイプの内部名を格納する変数

    protected:
        // NOTE: コンストラクタを定義
        // TODO: カスタムSOPクラス名を指定
        SOP_Template(OP_Network *net, const char *name, OP_Operator *op) : SOP_Node(net, name, op)
        {
            mySopFlags.setManagesDataIDs(true);
        }

        // NOTE: デストラクタを定義
        // TODO: カスタムSOPクラス名を指定
        virtual ~SOP_Template() {}

        // NOTE: 以下は編集不要
        virtual OP_ERROR cookMySop(OP_Context &context) override
        {
            return cookMyselfAsVerb(context);
        }
    };
} // namespace SOP_TEMPLATE

#endif

SOP_Template.cpp(.C)

最小構成のソースコードは以下のようになりそう。

// NOTE: このテンプレはprotoヘッダファイルを使用する前提のもの
// TODO: インクルードするファイル名を書き換える
#include "SOP_Template.hpp"
#include "SOP_Template.proto.h" // protoヘッダを使うことが前提

// NOTE: 最低限必要なヘッダファイルのインクルード
#include <UT/UT_StringHolder.h>
#include <UT/UT_DSOVersion.h>
#include <OP/OP_Operator.h>
#include <OP/OP_OperatorTable.h>
#include <PRM/PRM_TemplateBuilder.h>


// TODO: 任意のネームスペースを指定
using namespace SOP_TEMPLATE;

// TODO: カスタムSOPクラスを指定
// TODO: 任意の内部名を指定
const UT_StringHolder SOP_Template::theSOPTypeName("template"_sh);

// NOTE: プラグインロード時にOP_OperatorTableにカスタムSOPを登録する
void newSopOperator(OP_OperatorTable *table)
{
    table->addOperator(new OP_Operator(
        // TODO: 必要に応じて書き換える
        SOP_Template::theSOPTypeName,   // SOPの内部名
        "template",                     // SOPのラベル名
        SOP_Template::myConstructor,    // 自身のインスタンスを返すメソッド
        SOP_Template::buildTemplates(), // パラメータテンプレートを指定
        0,                              // 最低限必要な入力数
        0,                              // 入力プラグの数
        nullptr,                        // Custom local variables (none)
        OP_FLAG_GENERATOR));            // Flag it as generator
}

// NOTE: SOPに持たせるパラメータを定義する
// TODO: .dsファイル、または.dsファイルと同様のルールで書かれたraw文字列を代入する
static const char *theDsFile = R"THEDSFILE(
{
        name        parameters
}
)THEDSFILE";

// NOTE: *theDsFile で記述された内容をもとにパラメータテンプレートを生成する
// TODO: カスタムSOPクラスを指定
// TODO: 正しいcppファイル名に書き換える
PRM_Template *SOP_Template::buildTemplates()
{
    static PRM_TemplateBuilder templ("SOP_Template.cpp"_sh, theDsFile);
    return templ.templates();
}

// NOTE: Verbの仕組みを使うため、カスタムNodeVerbクラスを宣言
// TODO: カスタムNodeVerbクラス名を指定
class SOP_TemplateVerb : public SOP_NodeVerb
{
public:
    // NOTE: デフォルトコンストラクタとデストラクタ
    // TODO: カスタムNodeVerbクラス名を指定
    SOP_TemplateVerb() {}
    virtual ~SOP_TemplateVerb() {}

    // TODO: protoヘッダで定義されているパラメータテンプレートクラスを指定
    virtual SOP_NodeParms *allocParms() const
    {
        return new SOP_TemplateParms();
    }

    // NOTE: theVerbメンバ変数の宣言
    // TODO: カスタムNodeVerbクラスを指定
    static const SOP_NodeVerb::Register<SOP_TemplateVerb> theVerb;

    virtual UT_StringHolder name() const
    {
        // TODO: カスタムSOPクラスを指定
        return SOP_Template::theSOPTypeName;
    }

    // NOTE: 編集不要
    virtual CookMode cookMode(const SOP_NodeParms *parms) const
    {
        return COOK_GENERIC;
    }

    // NOTE: 編集不要
    virtual void cook(const CookParms &cookparms) const;
};

// NOTE: 静的なメンバ変数の定義はクラス定義の外に書く
// TODO: カスタムNodeVerbクラスを指定
const SOP_NodeVerb::Register<SOP_TemplateVerb> SOP_TemplateVerb::theVerb;

// TODO: カスタムSOPクラスを指定
const SOP_NodeVerb *SOP_Template::cookVerb() const
{
    return SOP_TemplateVerb::theVerb.get();
}

// NOTE: SOPのメイン処理 Verbを使う場合はSOP_NodeVerbのcookに実際の処理を委譲する
// TODO: カスタムNodeVerbクラスを指定
void SOP_TemplateVerb::cook(const SOP_NodeVerb::CookParms &cookparms) const
{
    // TODO: ノードのメイン処理はここに記述する
}

CmakeLists.txt

CMakeLists.txtは、自分の環境ではだいぶいじってしまっているため、サンプルを少し改変しただけの状態ではちゃんとテストできてないので、動作に若干自信なし。
もし実践してみたい方がいれば、と思い、参考程度にコードを載せてみますが、間違ってたらすみません。(大まかには合ってるはずですが)

CMakeLists.txtファイルは、サンプルを以下のような形で編集し、ソースコードと同じフォルダに置けば、以前の説明と同じ手順でビルドできると思われます。

# NOTE: 変更不要 対応するcmakeの最小バージョン
cmake_minimum_required( VERSION 3.6 )

# NOTE: sln内に作成するプロジェクト名
# TODO: 任意の名前を指定。カスタムSOPクラス名が良さそう
project(SOP_Template)

# NOTE: 変更不要 cmakeファイル検索パスを追加
list( APPEND CMAKE_PREFIX_PATH "$ENV{HFS}/toolkit/cmake" )

# NOTE: Houdiniが用意しているcmake関数などを含むパッケージを読みこむ
find_package( Houdini REQUIRED )

# NOTE: ライブラリ名変数にカスタムSOPクラス名の文字列を代入しておく
# TODO: ライブラリ名を変更SOP_TemplateのようにカスタムSOPクラス名にするとよい
set(library_name SOP_Template)

# NOTE: ここの指定でprotoヘッダが作られるようになる
# TODO: ソースコードファイル名(.c/.cpp)を記述
# NOTE: 初投稿で間違いがあったので修正した
houdini_generate_proto_headers( FILES SOP_Template.cpp )

# NOTE: ライブラリに追加するファイルを記述
# TODO: 必要なcpp/hppファイルを羅列する
add_library( ${library_name} SHARED
    SOP_Template.cpp
    SOP_Template.hpp
)

# NOTE: 変更不要
target_link_libraries( ${library_name} Houdini )

# NOTE: 変更不要
target_include_directories( ${library_name} PRIVATE
    ${CMAKE_CURRENT_BINARY_DIR}
)

# NOTE: 変更不要
# TODO: 必要に応じてオプションを追加
houdini_configure_target( ${library_name} )

Houdiniプラグイン開発備忘録2 ~ CMakeでのビルド編

最近、どうしても自作したいSOPがあり、Windows環境でのHoudiniのプラグイン開発を少しずつ学び始めたので、忘れないようにメモしていこうと思います。

HDKリファレンスには、「Visual Studio IDEからビルドする場合は、代わりにCMakeを使用することをお勧めします。」との記載があります。

また、「hcustomによるビルドでは単一のソースファイルしか扱えない」旨の記述もあり、hcustomは手軽ではあるけど不便だろうなぁと想像しています。

というわけで、今後使い続けることになりそうなCMakeでのビルドを知るため、前回のhcustomによるビルドに引き続き、CMakeを使うビルドを試してみようと思います。

今回もサンプルの「SOP_Star」を使います。


■CMakeとは?

CMakeは、プログラムのビルド処理を自動化してくれる便利ツールです。
今回CMakeで行う処理は、大きく分けて以下の2つです。

・Visual Studioのソリューションとプロジェクトの自動生成
・自動生成されたそれらのファイルを使ってビルドを行う


■環境の準備

まず、手元の環境ですが、以下のようなツールがインストールされています。

Houdini 18.0.532 Indie
Visual Studio 2019 / 2017 / 2015 Community
CMake 3.18.1


■ビルド手順

・ソースコードをコピーする

以下のフォルダを、任意の場所にコピーします。
今回は、以下のようにコピーしました。

コピー元:$HFS\toolkit\samples\SOP\SOP_Star
コピー先:D:\temp\HDK\build_cmake\SOP_Star

※$HFSは、Houdiniのインストールフォルダで、デフォルトでは以下のようなパス

C:\Program Files\Side Effects Software\Houdini 18.0.532

・Command Line Toolsを起動する

スタートメニューで「Command Line Tools」と検索し、起動します。
「Command Line Tools 18.0.532」というタイトルの、Houdini関連の環境変数が一通り自動的にセットされたコマンドプロンプトが立ち上がります。

※どんな環境変数がセットされているかは「set」と打ち込んでEnterを押すことで一覧表示、確認できます。


・ソリューションとプロジェクトを生成する

CMakeでソリューションの生成を実行すると、カレントのフォルダに、Visual Studioのソリューションファイル以外にも、いろいろなファイルが大量に生成されます。

これらの生成されたファイルがソースコードと混ざってしまうと、生成前の状態に戻すことが難しくなります。

そのため、予めビルド生成物をまとめるbuildフォルダをつくり、もとのソースコードとCMakeの生成ファイルを切り分けて管理できるようにします。

まずソースコードと同じフォルダにbuildという名前のフォルダを作ります。

D:\temp\HDK\build_cmake\SOP_Star\build

Command Line Toolsで以下を実行し、カレントのフォルダをbuildフォルダに移動します

cd /d D:\temp\HDK\build_cmake\SOP_Star\build

次に、以下を実行し、ソリューションとプロジェクトを生成します。

cmake ..

カレントのbuildフォルダに、各種ファイルが生成されます。

ここで生成されるソリューションとプロジェクトは、SOP_Starフォルダの直下にあるCMakeLists.txtファイルに記述された設定に基づいて作成されます。

カスタムプラグインを作成するためのCMakeLists.txtを書く際は、このCMakeLists.txtを参考にすれば良さそうです。


・プラグインをビルドする

続いて、以下を実行します。

cmake --build . --clean-first

これで、カレントのフォルダにあるソリューションとプロジェクトファイルを使い、プラグインが以下のフォルダにビルドされます。

%USERPROFILE%\Documents\houdini18.0\dso
SOP_Star.dll
SOP_Star.exp
SOP_Star.ilk
SOP_Star.lib
SOP_Star.pdb

・Houdiniでプラグインの動作を確認する

・Houdiniを起動
・Geometryを内でTABメニューから[star]と検索し[Star]ノードを作成
・無事ビューポート内に星型のジオメトリが現れればOKです。


ここまでで、hcustomとCMakeのそれぞれを使って、手軽にプラグインがビルドできることを確認してみました。

同じ要領で、サンプルにある各種プラグインをビルドすることができそうです。