Houdini 20.0 Pythonスクリプト

C++を使ったhouモジュールの拡張

On this page

概要

Houdiniにはinlinecppというモジュールがあり、これを使うとC++で関数を記述することができ、そしてその関数にはPythonからアクセス可能です。C++ソースコードは、Pythonソースコードの中でインラインとして記述します。このコードは自動的にあなた用のライブラリにコンパイルされます。コンパイルされたライブラリは自動的に読み込まれ、ライブラリが既に存在すれば、そのコードがコンパイルされず、ライブラリが既に読み込まれていれば、そのライブラリを読み込みし直さないといった長所があります。

inlinecppモジュールを使えば、Pythonから必要最低限のコードだけでC++ HDK(Houdini Development Kit)に簡単にアクセスすることができます。以下のサンプルでは、PythonからUT_StringmultiMatchメソッドをコールできていることがわかります:

>>> import inlinecpp
>>> mymodule = inlinecpp.createLibrary(
...     name="cpp_string_library",
...     includes="#include <UT/UT_String.h>",
...     function_sources=[
... """
... bool matchesPattern(const char *str, const char *pattern)
... {
...     return UT_String(str).multiMatch(pattern);
... }
... """])
...
>>> string = "one"
>>> for pattern in "o*", "x*", "^o*":
...     print repr(string), "matches", repr(pattern), ":",
...     print mymodule.matchesPattern(string, pattern)
...
'one' matches 'o*' : True
'one' matches 'x*' : False
'one' matches '^o*' : False

このモジュールは、houモジュールのPythonオブジェクトとそれに該当するC++ HDKオブジェクト間を変換することも可能で、HDKで利用可能なメソッドにアクセスすることができます。ただし、houのメソッドにはアクセスできません。このサンプルでは、hou.Geometryオブジェクトが自動的にconst GU_Detail *に変換されています:

>>> geomodule = inlinecpp.createLibrary("cpp_geo_methods",
...     includes="#include <GU/GU_Detail.h>",
...     function_sources=["""
... int numPoints(const GU_Detail *gdp)
... { return gdp->getNumPoints(); }"""])
...
>>> geo = hou.node("/obj").createNode("geo").displayNode().geometry()
>>> geomodule.numPoints(geo)
80

このモジュールは、C++を使ってhouクラスを拡張することも可能です:

>>> inlinecpp.extendClass(hou.Geometry, "cpp_geo_methods", function_sources=["""
... int numPoints(const GU_Detail *gdp)
... { return gdp->getNumPoints(); }"""])
...
>>> geo = hou.node("/obj").createNode("geo").displayNode().geometry()
>>> geo.numPoints()
80

Tip

houクラスの拡張には注意してください。その拡張が原因でHoudiniで用意されたメソッドとあなたが拡張したメソッドをユーザが区別できなくなります。そのため、一般的な接頭辞をメソッドの名前に付けることで、違いをはっきりとさせることができます。

使用方法

以下の関数は、inlinecppモジュールで利用可能です:

createLibrary(name, includes="", function_sources=(), debug=False,
              catch_crashes=None, acquire_hom_lock=False, structs=(),
              include_dirs=(), link_dirs=(), link_libs=())

これは、モジュールのようなオブジェクトを返します。

C++ソースコードからC++関数のライブラリを作成します。ライブラリが既に読み込まれていれば、そのライブラリを返します。まだコンパイルされていなければ、そのソースコードをコンパイルします。

name

共有するオブジェクトファイルの名前を設定するための固有な名前。同じ名前を再利用しないように注意してください。さもないとinlinecppは、同じ名前で他のライブラリを作成するPythonコードに遭遇した時、不必要な再コンパイルによる無駄時間を減らすための処置として、そのライブラリを削除してしまいます。

includes

関数の前に記述する#includeの行を含んだ文字列。この文字列には、あなたの関数からコールできてもPythonからは見えないというヘルパー関数を記述することもできます。

function_sources

文字列のシーケンスであり、シーケンス内の各文字列には、あなたが作成するいくつかの関数の内の1つの関数のソースコードを記述します。その文字列は解読できるように、あなたの関数のシグネーチャから始めなければなりません。

debug

Trueにすると、コードはデバッグ情報付きでコンパイルされます。True且つcatch_crashesの値を指定しなかった場合、HoudiniはC++コード内のクラッシュをPythonのRuntimeError例外に変換しようとします。

catch_crashes

Trueにすると、HoudiniはC++コード内のクラッシュをキャッチして、C++スタックトレースを含めてPythonのRuntimeError例外に変換しようとします。 Houdiniが常にC++コードからクラッシュをリカバリできるという保証はありません。つまり、このパラメータをTrueに設定しても、Houdiniがクラッシュする可能性があるということです。 このパラメータをNone(デフォルト)に設定すれば、デバッグパラメータと同じことになります。

acquire_hom_lock

Trueにすると、コードがHOM_AutoLockを使用し、Pythonコードが別スレッドで実行された時にC++オブジェクトにスレッドセーフでアクセスするように自動的に修正されます。コードがHoudiniの内部状態を修正する場合は、このパラメータをTrueに設定してください。

structs

戻り値として使うことが可能なC 構造の説明のシーケンス。各構造の説明は、1組になっていて、1番目のエレメントが構造の名前で、2番目のエレメントがメンバーの説明のシーケンスです。各メンバーの説明はメンバーの名前とタイプを含む1組の文字列です。

メンバーの説明のシーケンスの代わりに、構造の説明の2番目のエレメントに文字列を設定しても構いません。その場合、タイプはTypedefになります。そのTypedefは値の配列用のタイプ名を作成するのに役に立ちます。

このパラメータの詳細は、以下を参照してください。

include_dirs

includeファイルを検索するディレクトリパスのシーケンス。これらのパスはC++コードをコンパイルする時に、-Iオプションとしてhcustomコマンドに渡されます。

extendClass(cls, library_name, includes="", function_sources=(), debug=False, catch_crashes=None, structs=(), include_dirs=(), link_dirs=(), link_libs=())

C++で実装したメソッドを追加することでhouクラスを拡張します。

clsは、拡張したいhouモジュールクラスです。残りの引数は、acquire_hom_lockが常にTrueであること以外は、createLibraryと同じです。この関数は自動的にベースとなるC++クラス用に#includeの行を追加することを知っておいてください。

C++関数の最初のパラメータは、houオブジェクトに相当するC++オブジェクトへのポインタでなければなりません。

コンパイラのエラーと警告

inlinecpphcustom HDKツールを使ってC++コードのコンパイルとリンクをします。C++コードがコンパイルに失敗すると、inlinecppはhcustomからのフル出力とコンパイルエラーを含んだinlinecpp.CompilerError例外を起こします。

inlinecpphcustomを使用しているので、HDKが利用できる環境をセットアップしなければなりません。Windowsでは、Microsoft Visual Studio C++ (ExpressかProfessional Editionのどちらでも可)をインストールする必要があります。任意ですが、MSVCDir環境変数を設定すれば、inlinecppが使用するコンパイラを指定することができます。この環境変数を設定しなくても、inlinecppは自動的に使用するコンパイラを設定してくれます。LinuxとMacでは、コンパイラの環境をセットアップする必要はありません。

コードをコンパイルすれば、createLibraryが返したオブジェクトの_compiler_outputメソッドをコールすることで、何かコンパイラの警告が出ていないか確認することができます。例:

>>> mymodule = inlinecpp.createLibrary("test", function_sources=["""
... int doubleIt(int value)
... {
...     int x;
...     return value * 2;
... }
... """])
...
>>> print mymodule._compiler_output()
Making /home/user/houdiniX.Y/inlinecpp/test_Linux_x86_64_10.5.313_PM3a6t3irKe+jdb113yHpw.o and
/home/user/houdiniX.Y/inlinecpp/test_Linux_x86_64_10.5.313_PM3a6t3irKe+jdb113yHpw.so from
/home/user/houdiniX.Y/inlinecpp/test_Linux_x86_64_10.5.313_PM3a6t3irKe+jdb113yHpw.C
/home/user/houdiniX.Y/inlinecpp/test_Linux_x86_64_10.5.313_PM3a6t3irKe+jdb113yHpw.C: In function int doubleIt(int):
/home/user/houdiniX.Y/inlinecpp/test_Linux_x86_64_10.5.313_PM3a6t3irKe+jdb113yHpw.C:8: warning: unused variable x

既にコードをコンパイルしているためにcreateLibraryがライブラリをコンパイルする必要がない場合、_compiler_outputをコールするとライブラリが強制的にコンパイルされることに注意してください。

可能なパラメータタイプ

C++関数は、指定されたパラメータタイプのみ使用可能です。有効なパラメータタイプを以下に載せます:

int

Python intに変換可能な何かを渡します

float

Python floatに変換可能な何かを渡します

double

Python floatに変換可能な何かを渡します

const char *

Python strオブジェクトを渡します

bool

Python boolに変換可能な何かを渡します

GU_Detail *

Python SOP内のhou.Geometryを渡します

const GU_Detail *

hou.Geometryオブジェクトを渡します

OP_Node *

hou.OpNodeオブジェクトを渡します

CHOP_Node *

hou.ChopNodeオブジェクトを渡します

COP2_Node *

hou.Cop2Nodeオブジェクトを渡します

DOP_Node *

hou.DopNodeオブジェクトを渡します

LOP_Node *

hou.LopNodeオブジェクトを渡します

OBJ_Node *

hou.ObjNodeオブジェクトを渡します

ROP_Node *

hou.RopNodeオブジェクトを渡します

SHOP_Node *

hou.ShopNodeオブジェクトを渡します

SOP_Node *

hou.SopNodeオブジェクトを渡します

VOP_Node *

hou.VopNodeオブジェクトを渡します

VOPNET_Node *

hou.VopNetNodeオブジェクトを渡します

OP_Operator *

hou.NodeTypeオブジェクトを渡します

OP_OperatorTable *

hou.NodeTypeCategoryオブジェクトを渡します

PRM_Tuple *

hou.ParmTupleオブジェクトを渡します

CL_Track *

hou.Trackオブジェクトを渡します

SIM_Data *

hou.DopDataオブジェクトを渡します

UT_Vector2D *

hou.Vector2オブジェクトを渡します

UT_Vector3D *

hou.Vector3オブジェクトを渡します

UT_Vector4D *

hou.Vector4オブジェクトを渡します

UT_DMatrix3 *

hou.Matrix3オブジェクトを渡します

UT_DMatrix4 *

hou.Matrix4オブジェクトを渡します

UT_BoundingBox *

hou.BoundingBoxオブジェクトを渡します

UT_Color *

hou.Colorオブジェクトを渡します

UT_QuaternionD *

hou.Quaternionオブジェクトを渡します

UT_Ramp *

hou.Rampオブジェクトを渡します

以下に注記すべき重要事項を載せています:

  • 関数がGU_Detail *を受け取る場合、読み取り専用ではないhou.Geometryオブジェクトに渡さなければなりません。そうでない場合に関数をコールするとinlinecpphou.GeometryPermissionError例外を起こします。言い換えると、ジオメトリを修正可能なPython SOPからコールされる関数に対してだけGU_Detail *を使用してください。ジオメトリを修正しない関数に対しては、const GU_Detail *パラメータを使用してください。

  • 上記のリスト以外のパラメータタイプを使った場合、inlinecppは渡されたPythonオブジェクトをvoid *に変換します。例えば、int *のパラメータタイプを使用する場合、整数配列のアドレスを持ったPython integerに渡されます。

  • C++コードからchar *として文字列を受取、そのデータを変更することで、Python文字列の内容を修正しないでください。Pythonの文字列はimmutable(変更不可)なので、文字列の内容を変更すると、Pythonの内部状態が無効になりHoudiniがクラッシュする危険性があります。

可能な戻り値のタイプ

デフォルトで用意されている戻り値のタイプを以下に載せます:

void

Noneに変換されます

int

Python intに変換されます

float

Python floatに変換されます

double

Python floatに変換されます

const char *

Python str(解放されないNull終了文字列を返します)に変換されます

char *

Python str(free()で解放されるNull終了文字列を返します)に変換されます

bool

TrueまたはFalseに変換されます

inlinecpp::String

Python3ではstr、Python2ではunicodeに変換されます(std::stringでコンストラクト)

inlinecpp::BinaryString

Python3ではbytes、Python2ではstrに変換されます(std::stringでコンストラクト)

以下に注記すべき重要事項を載せています:

  • あなたの関数が文字列を返す最善の方法は、inlinecpp::BinaryString (詳細は以下参照)を返すことです。しかし、あなたの関数は、ポインタをNull終了文字配列に返すことで文字列を返すこともできます。あなたの関数の戻り値のタイプは、それをPython文字列に変換した後に配列を解放するかどうか決めます。戻り値のタイプがconst char *なら、配列は解放されませんが、char *ならfree()を使って配列が解放されます。delete []で文字配列を解放することができず、strdupの結果を返し、あなたの関数から配列をdeleteします。

独自の戻り値のタイプを簡単に作成することができます。詳細は、以下を参照してください。

文字列を返す

文字列を返す一番簡単な方法は、C++関数でinlinecpp::Stringを返して、std::stringからそのStringをコンストラクトすることです。以下のサンプルでは、ノードの削除スクリプトを含む文字列を返します:

node_utilities = inlinecpp.createLibrary("node_utilities",
    acquire_hom_lock=True,
    includes="#include <OP/OP_Node.h>",
    function_sources=["""
inlinecpp::String deletion_script(OP_Node *node)
{
    return node->getDelScript().toStdString();
}
"""])

時々、C++コードがNull終了のCスタイル文字列を返す必要があります。このCスタイルの文字列が関数内のローカル変数にあり、関数が戻ると解放される場合、文字列のコピーを返す必要があります。コピーを返す方法が2通りあります。1つ目は単に、Cスタイル文字列からstd::stringをコンストラクトし、inlinecpp::Stringを返すことです。2つ目は、以下に説明しているとおり、inlinecpp::Stringを返し、inlinecpp::as_string関数を使ってそれをコンストラクトすることで、std::stringのコンストラクトを回避します:

node_utilities = inlinecpp.createLibrary("node_utilities",
    acquire_hom_lock=True,
    includes="#include <OP/OP_Node.h>",
    function_sources=["""
inlinecpp::String deletion_script(OP_Node *node)
{
    return inlinecpp::as_string(node->getDelScript().nonNullBuffer());
}
"""])

C++コードが、関数が戻り、且つそれをコール元に戻さない時に解放しないCスタイルの文字列を返す必要があるなら、単に、const char *(char *ではなく)の戻り値タイプを使えばよいです。膨大な文字列を返すと、この手法は、inlinecpp::Stringを返すよりも効率的です。なぜなら、データの不要なコピーを回避できるからです。Pythonが文字列のコピーを作成するので、Cスタイルの文字列の内容は、関数が戻った後も利用可能であることに注目してください。サンプルを以下に載せます:

example_lib = inlinecpp.createLibrary("example_lib",
    includes="#include <unistd.h>",
    function_sources=["""
const char *user_name()
{
    return getlogin();
}
"""])

最後に、C++コードが解放を必要としないCスタイルの文字列を返す必要があれば、char *(const char *ではなく)の戻り値タイプを使用してください。inlinecppはdelete []ではなくデータに対してfree()をコールし、そのコピーをstd::stringに作成し、配列を削除し、inlinecpp::Stringとしてstd::stringを返します。以下のサンプルでは、解放される文字列を返す方法を説明しています:

example_lib = inlinecpp.createLibrary("example_lib",
    function_sources=["""
char *simple_string()
{
    return strdup("hello world");
}
"""])

バイナリデータを返す

C++コードが任意のバイナリデータを返す必要がある場合、Pythonのbytesオブジェクトに変換されるinlinecpp::BinaryStringを返します。例えば、Null文字を含む文字列を返したい場合は、inlinecpp::BinaryStringオブジェクトのsetメソッドをコールして、const char *ポインタにバイト単位でデータとサイズを渡すことができます。以下にサンプルを載せます:

example_lib = inlinecpp.createLibrary("example_lib",
    function_sources=["""
inlinecpp::BinaryString build_binary_data()
{
    char binary_data[] = "embedded\0null\0characters";
    inlinecpp::BinaryString result;
    result.set(binary_data, 24);
    return result;
}
"""])

inlinecpp::BinaryStringを使ってint、float、doubleの配列のバイナリ表現を返すことも可能です。単に値を適切なタイプのstd::vectorに保存し、以下のサンプルのようにinlinecpp::as_binary_stringをコールしてstd::vectorからinlinecpp::BinaryStringをコンストラクトします。Pythonのarrayモジュールを使えば、簡単に文字列を適切なタイプの配列に戻すことができます(配列の戻り値タイプを使えば、inlinecpp::BinaryStringを使用することなく配列を返すことが可能であることに注目してください)。

import array

example_lib = inlinecpp.createLibrary("example_lib",
    function_sources=["""
inlinecpp::BinaryString build_int_array()
{
    std::vector<int> values(10);
    for (int i=0; i<10; ++i)
        values[i] = i;
    return inlinecpp::as_binary_string(values);
}
"""])

>>> data = example_lib.build_int_array()
>>> int_array = array.array("i", data)
>>> for value in int_array:
...     print value

最後に、動的に割り当てたint、float、doubleの配列を返す必要があるなら、単に、配列の最初のエレメントへポインタとバイト単位の配列のサイズを使ってinlinecpp::BinaryStringのsetメソッドをコールします(配列の戻り値タイプを使えば、手法が単純になることに注目してください)。

example_lib = inlinecpp.createLibrary("example_lib",
    function_sources=["""
inlinecpp::BinaryString build_int_array()
{
    int *values = new int[10];
    for (int i=0; i<10; ++i)
        values[i] = i;

    inlinecpp::BinaryString result;
    result.set((const char *)values, 10 * sizeof(int));
    delete [] values;
    return result;
}
"""])

カスタム戻り値タイプ

上記のデフォルトの戻り値タイプ文字列の戻り値タイプに加えて、うまくPythonにオブジェクトとシーケンスに変換される構造体と配列を返すことが可能です。

そのような戻り値のタイプを使用するには、structsパラメータをライブラリの関数から返したいC++の構造体の記述を持つcreateLibraryに渡します。このパラメータをシーケンス内の各エレメントが1組の値になっているシーケンスに設定します。1組の1番目のエレメントが構造体の名前で、2番目のエレメントがメンバーのシーケンスです。各メンバーは、メンバー名とタイプを含んだ1組の文字列です。メンバータイプは、Pythonのstructモジュールで使われるフォーマット文字の1つです(例:iはinteger、dはdouble、fはfloat、cはcharなど)。

以下のサンプルは、double型のxyを持つPosition2Dという名前の構造体を作成し、ノードの位置を返します。

>>> node_utilities = inlinecpp.createLibrary(
...     acquire_hom_lock=True,
...     name="node_utilities",
...     includes="#include <OP/OP_Node.h>",
...     structs=[("Position2D", (
...         ("x", "d"),
...         ("y", "d"),
...     ))],
...     function_sources=["""
... Position2D node_position(OP_Node *node)
... {
...     Position2D result;
...     result.x = node->getX();
...     result.y = node->getY();
...     return result;
... }
... """])
...
>>> geo_node = hou.node("/obj").createNode("geo")
>>> geo_node.setPosition((3.5, 4.5))
>>> print geo_node.position()
[3.5, 4.5]
>>> position = node_utilities.node_position(geo_node)
>>> print position
<inlinecpp.Position2D object at 0x7f10e7cf0d90>
>>> print position.x
3.5
>>> print position.y
4.5

前のサンプルから、構造体は構造体インスタンスを作成し個々のメンバーに割り当てることでC++で初期化されます。

配列メンバーを持つ構造体を返す

メンバータイプの文字列の頭に*が付いていれば、それらのエレメントの配列を意味します。例えば、*iはintegerの配列です。以下のサンプルでは、2つの数値の配列を返す方法を載せています:

example_lib = inlinecpp.createLibrary(
    "example_lib",
    structs=[("ArrayPair", (
        ("ints", "*i"),
        ("doubles", "*d"),
    ))],
    function_sources=["""
ArrayPair build_array_pair()
{
    std::vector<int> ints;
    ints.push_back(2);
    ints.push_back(4);

    std::vector<double> doubles;
    doubles.push_back(1.5);
    doubles.push_back(3.5);

    ArrayPair result;
    result.ints.set(ints);
    result.doubles.set(doubles);
    return result;
}
"""])

>>> array_pair = example_lib.build_array_pair()
>>> print array_pair.ints[0]
2
>>> print array_pair.doubles[1]
3.5
>>> print zip(array_pair.ints, array_pair.doubles)
[(2, 1.5), (4, 3.5)]

配列メンバーのsetメソッドをコールする上記のサンプルは、std::vectorに渡します。以下の値をsetメソッドに以下の値を渡すことができます:

  • std::vector

  • ポインタとエレメントの数

  • std::string (配列が文字の配列の場合のみ)

  • std::vector of std::vectors (配列が配列の配列の場合のみ)

  • std::vector<std::string> (文字の配列の配列の場合のみ)

配列を返す

メンバー記述のシーケンスの代わりに、構造体記述の組の2番目のエレメントも文字列です。この場合、inlinecppはtypedefを作成し、そのtypedefは値の配列を返すときに役に立ちます。以下のサンプルでは、グローバルノード選択のノードIDに相当するintegerの配列を返す方法を載せています:

node_utilities = inlinecpp.createLibrary(
    "node_utilities",
    acquire_hom_lock=True,
    includes="""
        #include <OP/OP_Director.h>
        #include <UT/UT_StdUtil.h>
    """,
    structs=[("IntArray", "*i")],
    function_sources=["""
IntArray ids_of_selected_nodes()
{
    std::vector<int> ids;
    UTarrayToStdVector(OPgetDirector()->getPickedItemIds(), ids);
    return ids;
}
"""])

def selected_nodes():
    return [hou.nodeBySessionId(node_id)
        for node_id in node_utilities.ids_of_selected_nodes()]

上記のサンプルは、IntArrayオブジェクトを作成せず、setメソッドをコールしてintegerの配列を返しています。代わりに、単純にstd::vectorからIntArrayオブジェクトをコンストラクトすることができます。配列のsetメソッドに渡すことが可能なパラメータもコンストラクターに渡されます(配列でない構造体は、コンストラクタを持たないので、構造体の各エレメントに割り当てなければなりません)。

配列オブジェクトに対してsetメソッドをコールすることもでき、1番目のエレメントへのポインタとエレメントの数を渡します:

example_lib = inlinecpp.createLibrary("example_lib",
    structs=[("IntArray", "*i")],
    function_sources=["""
IntArray build_int_array()
{
    int *values = new int[10];
    for (int i=0; i<10; ++i)
        values[i] = i;

    IntArray result;
    result.set(values, 10);
    delete [] values;
    return result;
}
"""])

以下のサンプルは、文字の配列の配列を返すことで、文字列の配列を返す方法を載せています:

example_lib = inlinecpp.createLibrary("example_lib",
    structs=[("StringArray", "**c")],
    function_sources=["""
StringArray build_string_array()
{
    std::vector<std::string> result;
    result.push_back("one");
    result.push_back("two");
    return result;
}
"""])

>>> example_lib.build_string_array()
('one', 'two')

構造体と配列のネスト化

フォーマット文字に加えて、メンバータイプは構造体のシーケンス内の初期のタイプの名前またはそのようなタイプの配列を使用します。この手法で、他の構造体内の構造体または構造体の配列をネストすることができます。

例えば、以下のシーケンスは2つのintegerメンバーxyそしてtolerance(double)、single_point(Point)、points(Pointの配列。C++関数は、自動的にPythonオブジェクトに変換されるData値を返します)という名前を含むData構造体を持つPoint構造体を作成します。

example_lib = inlinecpp.createLibrary(
    "example_lib",
    structs=(
        ("Point", (
            ("x", "i"),
            ("y", "i"),
        )),
        ("Data", (
            ("tolerance", "d"),
            ("single_point", "Point"),
            ("points", "*Point"),
        )),
    ),
    function_sources=["""
Data build_nested_struct()
{
    std::vector<Point> points;
    for (int i=0; i<5; ++i)
    {
        Point point;
        point.x = i;
        point.y = i + 1;
        points.push_back(point);
    }

    Data result;
    result.tolerance = 0.01;
    result.single_point.x = 4;
    result.single_point.y = 6;
    result.points.set(points);
    return result;
}
"""])

>>> result = example_lib.build_nested_struct()
>>> print result.tolerance
0.01
>>> print result.single_point.x
4
>>> print result.points[2].y
3

構造体の配列や構造体の配列の配列であるtypedefを作成することもできます。以下のサンプルでは、構造体の配列の配列を返す方法を載せています。各構造体には、prim_idvertex_idという名前の2つのintegerを含んでいます。このサンプルは、hou.Geometryオブジェクトを受け取る関数を作成し、各ポイントに対して、そのポイントを参照している頂点(プリミティブと頂点のIDとして)を返します。

point_ref_utils = inlinecpp.createLibrary(
    "point_ref_utils",
    acquire_hom_lock=True,
    structs=(
        ("ReferencingVertex", (
            ("prim_id", "i"),
            ("vertex_id", "i"),
        )),
        ("ReferencesToPoint", "*ReferencingVertex"),
        ("ReferencesToAllPoints", "*ReferencesToPoint"),
    ),  
    includes="""
#include <GU/GU_Detail.h>
#include <GB/GB_PointRef.h>

int vertex_index(GB_Vertex &vertex, GB_Primitive &prim)
{   
    GEO_Vertex &geo_vertex = dynamic_cast<GEO_Vertex &>(vertex);
    GEO_Primitive &geo_prim = dynamic_cast<GEO_Primitive &>(prim);
    int num_vertices = geo_prim.getVertexCount();
    for (int i=0; i<num_vertices; ++i)
    {
        if (geo_prim.getDetail().getVertexMap() == geo_vertex.getIndexMap()
            && geo_prim.getVertexOffset(i) == geo_vertex.getMapOffset())
        {
            return i; 
        }
    }
    return -1;
}   
""",
    function_sources=["""
ReferencesToAllPoints vertices_referencing_points(const GU_Detail *gdp)
{   
    GB_PointRefArray point_ref_array(gdp, /*group=*/NULL);

    std::vector<ReferencesToPoint> references_to_points;
    for (int i=0; i<point_ref_array.entries(); ++i)
    {       
        std::vector<ReferencingVertex> referencing_vertices;
        for (const GB_PointRef *ref = point_ref_array(i); ref; ref=ref->next)
        {   
            ReferencingVertex referencing_vertex;
            referencing_vertex.prim_id = ref->prim->getNum();
            referencing_vertex.vertex_id = vertex_index(*ref->vtx, *ref->prim);
            referencing_vertices.push_back(referencing_vertex);
        }
        references_to_points.push_back(referencing_vertices);
    }
    return references_to_points;
}
"""])

def vertices_referencing_points(geo):
    """ジオメトリ内のポイント毎に1エントリーの値のリストを返します。
    そのリスト内の各エントリーは、頂点のリストで、各頂点はそれに相当するポイントを参照します。
    """
    vertices_for_each_point = []
    for references_to_point in point_ref_utils.vertices_referencing_points(geo):
        vertices = []
        for reference_to_point in references_to_point:
            prim = geo.iterPrims()[reference_to_point.prim_id]
            vertices.append(prim.vertex(reference_to_point.vertex_id))
        vertices_for_each_point.append(vertices)
    return vertices_for_each_point

>>> geo = hou.node("/obj").createNode("geo").createNode("box").geometry()
>>> for point, vertices in zip(geo.points(), vertices_referencing_points(geo)):
...     print point
...     for vertex in vertices:
...         print "    ", vertex

配列パラメータ

配列を埋める

inlinecppを使用する時、後でPythonで使用するためにC++コードを使って配列の中身を埋めることがよくあります。例えば、Pythonを使ったgenerator COPはC++を使って、すべてのピクセルの内容を計算し、Pythonを使ってノードのパラメータを評価して、ピクセルをCOPに保存します。

cpp_lib = inlinecpp.createLibrary("example", function_sources=["""
void fill_array(float *array, int length)
{
    for (int i=0; i<length; ++i)
        array[i] = i;
}
"""])

length = 10

# 以下のコードは、Pythonと一緒に既に入っている配列モジュールを使用して、
# 配列を作成してから、それをC++関数に渡す方法を説明しています。
import array
a = array.array("f", [0] * length)
cpp_lib.fill_array(a.buffer_info()[0], len(a))
print a

# 以下のコードは、Pythonのnumpyモジュールを使用して
# 上記と同じ事をする方法を説明しています:
import numpy
a = numpy.zeros(length, dtype=numpy.float32)
cpp_lib.fill_array(a.ctypes.data, len(a))
print a

# C++コードは配列の内容を初期化する必要がないので、
# 非常に大きな配列を扱う時はnumpy.zeroの代わりにnumpy.emptyを使用した方が速いです:
a = numpy.empty(length, dtype=numpy.float32)
cpp_lib.fill_array(a.ctypes.data, len(a))
print a

Note

  • numpyモジュールはarrayモジュールよりも機能がたくさんあります。しかしarrayモジュールはすべてのPythonディストリビューションに入っているのに対してnumpyは入っていないのでインストールする必要があります。

  • ほとんどの処理では、numpyを使って配列の内容を修正することができるのでinlinecppをまったく使う必要がありません。

  • numpy.zerosは配列をすべてゼロに初期化し、numpy.emptyは配列を初期化しません。

  • "f"はarrayモジュールで32ビットのfloatの配列を作成します。同様に、numpy.float32(または"f4")はnumpyモジュールで32ビットのfloatの配列を作成します。"d"はarrayモジュールで64ビットのdoubleを使用します。numpy.float64(または"f8")はnumpyで同じく64ビットのdoubleを使用します。

  • array.arrayのbuffer_infoメソッドは2つのintegerのタプル(配列のアドレスとバイト単位のサイズ )を返すので、C++関数にraw配列のアドレスであるそのタプルの1番目の値を渡します。同様にnumpy.arrayオブジェクトのctypes.dataアトリビュートには、numpy配列のrawデータへのポインタが入っています。

  • C++ 関数はポインタだけでは配列の長さを判断できないので、パラメータとして長さを渡さなければなりません。

  • 大きい配列に対しては、numpyモジュールがarryaモジュールよりも効率的です。その理由は、arrayモジュールはゼロの配列や初期化しない配列を効率的に作成する方法がないからです。代わりに、arrayモジュールはまず最初に[0] * lengthで一時的なリストを記述し、リストの内容で配列を初期化します。Pythonは小さい整数(例えばゼロ)のリストを作成するように最適化されていますが、非常に大きい配列を扱う時はarrayオブジェクトの作成に時間がかかります。

Tip

LinuxとMac OSでは、Houdiniはコンピュータで利用可能なPythonディストリビューションを使おうとします。つまり、numpyをシステムのPythonディストリビューションにインストールするだけで、Houdiniがそのモジュールを拾います。しかし、Windowsでは、HoudiniはHoudiniと一緒にインストールされたPythonディストリビューションを使用します。Houdiniからnumpyを使用するには、それを$HFS内の適切なPythonディレクトリ(例:$HFS/python27)にインストールしなければなりません。

array.arraysとnumpy.arraysの両方がPythonシーケンスのように動作します。つまり、Pythonから配列を繰り返してエレメントを取り出すことができます。必要であれば、配列をバイナリ文字列に変換することもできます。array.arraysを使った場合は、以下のコードを記述します。

a.tostring()

numpy.arraysを使った場合は、以下のコードを記述します。

str(a.data)

これらの文字列は、hou.Cop2Node.setPixelsOfCookingPlaneFromStringhou.Geometry.setPointFloatAttribValuesFromStringhou.Volume.setAllVoxelsFromStringなどのメソッドに渡すことができます。しかし、array.arrayやnumpy.arrayオブジェクトのメソッドに文字列に変換する必要なく直接渡すこともできます。そうする方がメモリの割り当てやデータのコピーが必要ないので効率的です。

バイナリデータから配列を構築する

Houdiniで返されたバイナリ文字列データから配列を簡単に構築することができます。例えば、array.array関数を使う場合は、以下のコードを記述します:

a = array.array("f", geo.pointFloatAttribValuesAsString("P"))

numpy.frombuffer関数の場合は以下の通りです:

a = numpy.frombuffer(geo.pointFloatAttribValuesAsString("P"), dtype=numpy.float32)
# aは読み取り専用です。

または

a = numpy.frombuffer(geo.pointFloatAttribValuesAsString("P"), dtype=numpy.float32).copy()
# aの内容を修正することができます。

Note

  • arrayモジュールで作成した配列は常に読み書き可能です。しかし、numpyで作成した配列は場合によって読み書き可能な時や読み取り専用の時があります。numpy.zerosやnumpy.emptyで配列を作成する時、または既存の配列に対してcopyメソッドをコールした時は、返される配列は常に書き込み可能です。しかし、frombufferでnumpy配列を作成すると、numpyはデータのコピーを作成せず、配列は読み取り専用になります。

配列の修正

以下のPython SOPのサンプルでは、Houdiniが返したバイナリデータから配列を作成し、C++で配列の内容を修正し、そのデータをHoudiniに戻す方法を載せています:

import inlinecpp
cpp_module = inlinecpp.createLibrary("example", function_sources=["""
void modify_point_positions(float *point_positions, int num_points)
{
    // 各ポイントのYコンポーネントに0.5を加算します。
    for (int point_num=0; point_num < num_points; ++point_num)
    {
        point_positions[1] += 0.5;
        point_positions += 3;
    }
}
"""])

geo = hou.pwd().geometry()
num_points = len(geo.iterPoints())

# 以下のコードは、位置を修正するための配列モジュールを使用しています:
import array
positions = array.array("f", geo.pointFloatAttribValuesAsString("P"))
cpp_module.modify_point_positions(positions.buffer_info()[0], num_points)
geo.setPointFloatAttribValuesFromString("P", positions)

# 以下のコードは、numpyを使用して位置を再度修正しています:
import numpy
positions = numpy.frombuffer(geo.pointFloatAttribValuesAsString("P"), dtype=numpy.float32).copy()
cpp_module.modify_point_positions(positions.ctypes.data, num_points)
geo.setPointFloatAttribValuesFromString("P", positions)

構造体の配列

Numpyの構造体の配列には、その構造体の配列をC++関数に渡す仕組みがあります。以下のサンプルでは、すべてのポイントのP (position)とCd (diffuse color)アトリビュートを2つのnumpy配列に格納し、その配列を1つに結合し、raw配列データをC++関数に渡します。C++関数は、そのデータを、構造体の配列として受取り、その配列の各エレメントにはx,y,z,red,green,blueのfloat値が入っています。

import numpy
import inlinecpp

geo = hou.pwd().geometry()
num_points = len(geo.iterPoints())

positions_array = numpy.frombuffer(
    geo.pointFloatAttribValuesAsString("P"), dtype="f4,f4,f4")
positions_array.dtype.names = ("x", "y", "z")

colors_array = numpy.frombuffer(
    geo.pointFloatAttribValuesAsString("Cd"), dtype="f4,f4,f4")
colors_array.dtype.names = ("red", "green", "blue")

attribs_array = numpy.empty(num_points, dtype="f4,f4,f4,f4,f4,f4")
attribs_array.dtype.names = ("x", "y", "z", "red", "green", "blue")
for component in "x", "y", "z":
    attribs_array[component] = positions_array[component]
for component in "red", "green", "blue":
    attribs_array[component] = colors_array[component]

cpp_lib = inlinecpp.createLibrary("example",
    includes="""
#pragma pack(push, 1)
struct AttribValues {
    float x;
    float y;
    float z;
    float red;
    float green;
    float blue;
};
#pragma pack(pop)
""",
    function_sources=["""
void process_attribs(AttribValues *attrib_values_array, int length)
{
    for (int i=0; i<length; ++i)
    {
        AttribValues &values = attrib_values_array[i];

        // アトリビュート値を解析するために、ここで何かを実行します。
        cout << values.x << ", " << values.y << ", " << values.z << " "
             << values.red << ", " << values.green << ", " << values.blue
             << endl;
    }
}
"""])

cpp_lib.process_attribs(attribs_array.ctypes.data, len(attribs_array))

Note

  • numpy.frombufferに渡すdtypeパラメータがカンマ区切りの文字列の場合、numpyは構造体の配列を作成します。"f4"numpy.float32と同じです。

  • numpy.emptyは正しいサイズと正しいフィールドの配列を割り当てますが、データを初期化しません。

  • フィールド名を配列のインデックスとして使用することで、配列のフィールド毎にビューを作成することができます。これらのビューを使うことで、フィールドのすべての値を含む配列から目的の配列のフィールドに割り当てることができます。

  • 不明なパラメータタイプを持つinlinecpp C++関数を作成すると、 そのタイプはポインタとして扱われます。つまり、エレメント毎に24バイトを含むnumpy配列データのアドレスに渡すことで、ポインタから、24バイトサイズでnumpyフィールドが揃っているC++構造体を作成することで、それらのエレメントのC++配列を受け取ることができます。

  • C++コンパイラは構造体エレメント間を桁埋めすることで、指定したバイトサイズに揃えることができます。上記の#pragma packマクロを宣言すると、構造体を桁埋めしません。

  • Numpyの構造体配列には、フィールド値別にエレメントを並べ替える仕組みがあります。

Python SOP

inlinecppはPython SOPを記述する時に非常に役に立ちます。これを使うことでC++ GU_Detailクラスで利用可能な高レベル処理にアクセスすることができます。さらに、C++でコードを実装することで遅いループの処理を速くすることができます。この手法は、 Type Propertiesダイアログでパラメータを作成し (C++ PRM_Templatesを使う必要がありません)、 C++でパフォーマンスを出すという組み合わせができるメリットがあります。

以下のサンプルでは、平面を基準にジオメトリをクリップするPython SOP内のコードを載せています。法線用パラメータ(normalという3つのfloatを持つベクトル)と法線沿いの距離用のパラメータ(distanceというfloat)があることを前提に、その法線と原点からの距離を使って平面を作成し、その平面でジオメトリをクリップします。

import inlinecpp

geofuncs = inlinecpp.createLibrary(
    "example_clip_sop_library",
    acquire_hom_lock=True,
    includes="""
#include <GU/GU_Detail.h>
#include <GQ/GQ_Detail.h>
""",
    function_sources=["""
void clip(GU_Detail *gdp, float nx, float ny, float nz, float distance)
{
    GQ_Detail *gqd = new GQ_Detail(gdp);
    UT_Vector3 normal(nx, ny, nz);
    gqd->clip(normal, distance, 0);
    delete gqd;
}
"""])

nx, ny, nz = hou.parmTuple("normal").eval()
geofuncs.clip(hou.pwd().geometry(), nx, ny, nz, hou.ch("distance"))

inlinecppを使ったPythonとHDKを使ったC++を比較してみてください。これらのサンプルでは、inlinecppバージョンはHDKバージョンと同じ速さですが、自動コンパイルを使用しているためコードが短いです。

以下の別のサンプルでは、destroyMeメソッドをhou.Attrib(hou.Attrib.destroyが既に存在していることに注目してください)に追加しています:

import types

geomodule = inlinecpp.createLibrary("geomodule",
    acquire_hom_lock=True,
    includes="""
        #include <GU/GU_Detail.h>

        template <typename T>
        bool delete_attribute(GU_Detail *gdp, T &attrib_dict, const char *name)
        {
            GB_Attribute *attrib = attrib_dict.find(name);
            if (!attrib)
                return false;
            gdp.destroyAttribute(attrib->getOwner(), attrib->getName());
            return true;
        }
    """, function_sources=[ """
        int delete_point_attribute(GU_Detail *gdp, const char *name)
        { return delete_attribute(gdp, gdp->pointAttribs(), name); }
    """, """
        int delete_prim_attribute(GU_Detail *gdp, const char *name)
        { return delete_attribute(gdp, gdp->primitiveAttribs(), name); }
    """, """
        int delete_vertex_attribute(GU_Detail *gdp, const char *name)
        { return delete_attribute(gdp, gdp->vertexAttribs(), name); }
    """, """
        int delete_global_attribute(GU_Detail *gdp, const char *name)
        { return delete_attribute(gdp, gdp->attribs(), name); }
    """])

attrib_type_to_delete_function = {
    hou.attribType.Point: geomodule.delete_point_attribute,
    hou.attribType.Prim: geomodule.delete_prim_attribute,
    hou.attribType.Vertex: geomodule.delete_vertex_attribute,
    hou.attribType.Global: geomodule.delete_global_attribute,
}

def destroyAttrib(attrib):
    return attrib_type_to_delete_function[attrib.type()](attrib.geometry(), attrib.name())

hou.Attrib.destroyMe = types.MethodType(destroyAttrib, None, hou.Attrib)

hou.Nodeクラスの拡張

以下のサンプルでは、hou.OpNodeクラスやそのサブクラスを拡張する方法を載せています:

この1番目のサンプルでは、パターン文字列を受け取るexpandGroupPatternというメソッドを追加し、そのパターンに一致するグループ内の子ノードの名前をカンマ区切りのリストで返します。

>>> inlinecpp.extendClass(hou.OpNode, "cpp_node_methods", function_sources=["""
... inlinecpp::BinaryString expandGroupPattern(OP_Node *node, const char *pattern)
... {
...     UT_String result;
...     node->expandGroupPattern(pattern, result);
...     return result.toStdString();
... }"""])
...
>>> hou.node("/obj").expandGroupPattern("@group1")
'geo1,geo2,geo3'

この2番目のサンプルでは、オブジェクトノードオブジェクトにsetSelectableメソッドを追加しています(このメソッドは既にhou.ObjNode.setSelectableInViewportとして利用可能であることを覚えておいてください)。

inlinecpp.extendClass(
    hou.ObjNode,
    "node_methods",
    includes="#include <UT/UT_UndoManager.h>",
    function_sources=["""
void setSelectable(OBJ_Node *obj_node, bool selectable)
{
    if (!obj_node->canAccess(PRM_WRITE_OK))
        return;

    UT_AutoUndoBlock undo_block("Setting selectable flag", ANYLEVEL);
    obj_node->setPickable(selectable);
}
"""])

Python COP

Python COPでinlinecppの使い方のサンプルに関しては、Python COPの章でCOPの一部をC++で記述するを参照してください。

例外を発生させる

C++ ソースコードではPythonへ例外を発生させることができません。しかし、例外を発生させるテクニックが1つあり、C++関数がコードを返すようにして、そのコードをチェックするwrapperのPython関数を作成して、必要に応じて例外を発生させることです。以下のサンプルは上記と同じですが、hou.PermissionError例外を発生させます。

import types

cpp_node_methods = inlinecpp.createLibrary(
    "node_methods",
    acquire_hom_lock=True,
    includes="""
#include <OBJ/OBJ_Node.h>
#include <UT/UT_UndoManager.h>
""",
    function_sources=["""
bool setObjectSelectable(OBJ_Node *obj_node, bool selectable)
{
    if (!obj_node->canAccess(PRM_WRITE_OK))
        return false;

    UT_AutoUndoBlock undo_block("Setting selectable flag", ANYLEVEL);
    obj_node->setPickable(selectable);
    return true;
}
"""])

def setObjectSelectable(obj_node, selectable):
    if not cpp_node_methods.setObjectSelectable(obj_node, selectable):
        raise hou.PermissionError()

hou.ObjNode.setSelectable = types.MethodType(setObjectSelectable, None, hou.ObjNode)

houオブジェクトを返す

C++関数が返すものを返すことはできません。C++関数が返すものは、自動的にそれに該当するhouオブジェクトに変換されます。しかしPython wrapper関数を使えば、変換することができます。

例えば、OP_Node::getParmsThatReferenceはパラメータ名を受け取り、PRM_ParmポイントのUT_PtrArrayを返します。以下のように、hou.Parmオブジェクトを受け取り、hou.ParmTupleオブジェクトのシーケンスを返すPython関数を作成することができます:

parm_reference_lib = inlinecpp.createLibrary(
    "parm_reference_lib",
    acquire_hom_lock=True,
    structs=[("StringArray", "**c")],
    includes="""
#include <OP/OP_Node.h>
#include <PRM/PRM_Parm.h>

// このヘルパー関数は、Pythonから直接コールされません:
static std::string get_parm_tuple_path(PRM_Parm &parm_tuple)
{
    UT_String result;
    parm_tuple.getParmOwner()->getFullPath(result);
    result += "/";
    result += parm_tuple.getToken();
    return result.toStdString();
}
""",
    function_sources=["""
StringArray get_parm_tuples_referencing(OP_Node *node, const char *parm_name)
{
    std::vector<std::string> result;
    UT_PtrArray<PRM_Parm *> parm_tuples;
    UT_IntArray component_indices;
    node->getParmsThatReference(parm_name, parm_tuples, component_indices);

    // たとえコンポーネントインデックスの配列を返しても、ほとんど-1になるので、
    // これらを使用することができません。
    for (int i=0; i<parm_tuples.entries(); ++i)
        result.push_back(get_parm_tuple_path(*parm_tuples(i)));

    return result;
}
"""])

def parm_tuples_referencing(parm):
    return [hou.parmTuple(parm_tuple_path)
        for parm_tuple_path in parm_reference_lib.get_parm_tuples_referencing(
            parm.node(), parm.name())]

このサンプルのC++関数は、スペース区切りの文字列を返します。それを文字列のリストに分割し、hou.ParmTupleオブジェクトのリストに変換します。

このサンプルでは、includesパラメータ内にソースコードを記述することで、Python側に表示されないヘルパー関数を作成していることに注目してください。

コンパイルしたライブラリの配布

ユーザが適切なコンパイラ環境をセットアップする必要がないように、コンパイルしたライブラリを配布したいのであれば、単にinlinecppが作成した$HOME/houdiniX.Y/inlinecppディレクトリ下にある共有オブジェクト(.so/.dll/.dylib)を配布すればいいだけです。ユーザがC++ソースコードを修正しない限り、チェックサムは同じで共有オブジェクトのファイル名も同じにします。

Houdiniは共有オブジェクトを検索する時に$HOUDINI_PATHにあるinlinecppサブディレクトリをすべて調べます。つまり、$HOUDINI_PATH以下のどこかの場所に共有ライブラリを配置することができます。

共有ファイルのパスを調べるには、モジュールのようなオブジェクトの_shared_object_pathメソッドをコールします。 例:

>>> mymodule = inlinecpp.createLibrary("test", function_sources=["""
... int doubleIt(int value) { return value * 2; }"""])
...
>>> mymodule._shared_object_path()
'/home/user/houdiniX.Y/inlinecpp/test_Linux_x86_64_11.0.313_3ypEJvodbs1QT4FjWEVmmg.so'

ライブラリはプラットフォームとアーキテクチャ別に名前が異なります。つまり、共有オブジェクト名の干渉を気にすることなく複数のマルチプラットフォームに対応できるように複数の共有オブジェクトを配布することができます。

デジタルアセット外へのコードの保存

バージョン管理システムでソースコードの編集と管理を簡単にするために、デジタルアセット外にPythonソースコードを保存する方法のサンプルは、Python SOPのドキュメントを参照してください。

クラッシュの修復

C++コードはHoudiniプロセス内で実行されるので、コード内にバグがあればHoudiniを簡単にクラッシュさせてしまいます。ライブラリを作成する時にdebug=Trueを渡すことで、Houdiniはコードを最適化なしでデバッグ情報を含めてコンパイルするので、標準のデバッガーをHoudiniに追加することでコードをデバッグするのが簡単になります。

デフォルトでは、debug=Trueを渡してcatch_crashesパラメータの値を指定しなかった場合、HoudiniはC++ソースコードのクラッシュをリカバリーしようとし、それをC++スタックトレースを含むPython RuntimeError例外に変換します。例えば、以下のコードはNullポインタの参照先を取得しているのでクラッシュします:

>>> mymodule = inlinecpp.createLibrary("crasher", debug=True, function_sources=["""
... void crash()
... {
...     int *p = 0;
...     *p = 3;
... }
... """])
>>> mymodule.crash()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/opt/hfs/houdini/python3.9libs/inlinecpp.py", line 620, in __call__
    return self._call_c_function(*args)
  File "/opt/hfs/houdini/python3.9libs/inlinecpp.py", line 589, in call_c_function_and_catch_crashes
    return hou.runCallbackAndCatchCrashes(lambda: c_function(*args))
RuntimeError: Traceback from crash:
pyHandleCrash
UT_Signal::UT_ComboSignalHandler::operator()(int, siginfo*, void*) const
UT_Signal::processSignal(int, siginfo*, void*)
_L_unlock_15 (??:0)
crash (crasher_Linux_x86_64_10.5.313_fpk9yUhcRwBJ8R5BAVtGwQ.C:9)
ffi_call_unix64+0x4c
ffi_call+0x214
_CallProc+0x352
<unknown>

トレースバックのPythonの部分には、例外が発生する直前のコールとC++スタックトレースのコールを含む例外が入っています。

そのトレースで注目すべき行は、crash(crasher_Linux_x86_64_10.5.313_fpk9yUhcRwBJ8R5BAVtGwQ.C:9)です。これは、$HOME/houdiniX.Y/inlinecpp/crasher_Linux_x86_64_10.5.313_fpk9yUhcRwBJ8R5BAVtGwQ.Cの9行目に問題があることを意味しています。そのファイルを探すと、以下の内容を確認することができます:

#include <UT/UT_DSOVersion.h>
#include <HOM/HOM_Module.h>

extern "C" {

void crash()
{
    int *p = 0;
    *p = 3;
}

}

9行目は、Nullポインタの参照先を取得するステートメントです。

デフォルトでは、debug=Trueの場合のみ、クラッシュのハンドリングが有効になります。 catch_crashesパラメータに値を設定すれば、debug設定に関係なく、クラッシュのハンドリングを有効にするかどうか制御することができます。

Pythonスクリプト

はじめよう

次のステップ

リファレンス

  • hou

    Houdiniにアクセスできるサブモジュール、クラス、ファンクションを含んだモジュール。

導師レベル

Python Viewerステート

Pythonビューアハンドル

プラグインタイプ