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} )