Houdini 20.0 Pythonスクリプト

Render Galleryのバックグラウンドプラグイン

On this page

概要

レンダースナップショットギャラリーでは、現在のSolariのビューとネットワークをスナップショットしたり、 バックグラウンドレンダリング を開始してビューをバックグラウンドで完全にレンダリングすることができます。

Pythonプラグインを記述することで、一種のバックグラウンドレンダーを実装することができます。 Houdiniには2つのバックグラウンドレンダー実装が同梱されていて、その1つはバックグラウンドスレッドでのレンダー、もう1つがHQueueファーム上でのレンダーです。 独自のバックグラウンドレンダーを実装して、他のレンダーファームマネージャまたはスタジオ内製のレンダーパイプラインに統合することができます。

  • HoudiniはHOUDINIPATH/husdplugins/backgroundrenderers/内でレンダーインポートプラグインを検索します。

    $HOUDINI_HUSDPLUGINS_PATH環境変数を設定することで、HoudiniがUSD関連のプラグインを検索する場所をカスタマイズすることができます。

  • そのパスから見つかるようにPythonソースコード(.py)ファイルを作成します(例えば、$HOUDINI_USER_PREF_DIR/husdplugins/backgroundrenderers/my_bg_render.py)。

    以下のテンプレートの内容をコピーして、プラグインファイルにペーストしてください。

  • プラグインファイルには最低でも1個のバックグラウンドレンダークラス(husd.backgroundrenderer.BackgroundRendererからサブクラス化)、単一のmanager引数を受け取るregisterBackgroundRenderers()という名前の関数を用意し、そのregisterBackgroundRenderers()関数を使ってそのプラグインファイル内にあるバックグラウンドレンダークラスを登録する必要があります(プラグインファイルテンプレートを参照)。

  • 単一の.pyファイル内に複数のバックグラウンドレンダープラグインを定義して登録することができます(これによって、起動時間が改善されます)。とはいえ、各プラグインを別々のファイルに分けたほうが個々にプラグインをパス内に移したり出したりできてる柔軟性があります。

  • パス内で先に見つかったファイルは、後で見つかった同じ名前のファイルを上書きします。

プラグインファイルテンプレート

Pythonファイル内に以下の骨組みをペーストします:

from husd.backgroundrenderer import BackgroundRenderer


# レンダークラスは、husd.backgroundrenderer.BackgroundRendererをサブクラス化する必要があります。
# おそらく実装するレンダーのタイプに基づいてレンダークラスの名前を付けたいことでしょう。
# 例えば、CloudServiceRendererです。

class MyCustomRenderer(BackgroundRenderer):
    # デフォルトの __init__ 実装では、いくつか便利なインスタンス変数をセットアップします:
    # self._lopnet_path:
    #     (str) LOPネットワークのノードパス。例えば、"/stage"。
    # self._item_id:
    #     (str) このレンダーの固有なID文字列。この文字列を使用して、例えばレンダーファームマネージャのジョブ名などの名前または識別子を構築することができます。

    # 以下のヘルパー関数をコールすることができます:
    #
    # self.updateImagePath(image_path)
    #     指定したパスの画像を使用してスナップショットを更新します。
    #
    # self.updateMetadata(metadata)
    #     指定した辞書を使用してスナップショットのメタデータを更新します。
    #     辞書のほとんどのキーは任意ですが、いくつかのキーがHoudiniのUIで使用されています:
    #     "totalClockTime":
    #         (float) 現在の経過時間(秒)。
    #         Render Galleryビューアは、これをレンダー統計内に表示します。
    #     "percentDone":
    #         (float) 完了率(0-100)で、これもレンダー統計内に表示されます。
    #     "peakMemory":
    #         (int) 最大メモリ使用量。

    def pollFrequency(self):
        # システムが(この(浮動小数点)秒数の間隔で)このオブジェクトに対してメソッドをコールしてアップデートを取得する頻度。
        return 1.0

    def getConfigOptions(self, old_options):
        # このメソッドは、構成用インターフェースを起動し、ユーザからオプション値を取得してから、
        # そのオプションのキー/値を辞書として返すことを目的にしています。
        # この引数は、このメソッドが最後にコールされた時に返された内容を含んだ辞書になっているので、
        # ユーザーが以前に入力した値をオプションUIに入力することができます。
        # このメソッドは、ユーザがメニューからレンダープラグインを選択した場合にのみコールされます。
        # ユーザはBackgroundボタンをクリックするだけで、
        # システムは、このメソッドをコールして最初に構成用UIを表示するのではなく、以前の値で直接startBackgroundRender()をコールします。
        return {}

    def startBackgroundRender(self, usd_filepath, options):
        # このメソッドでは、バックグランドレンダラーを開始します。
        # usd_filepath: レンダリングするUSDファイルのファイルパス。
        # options引数は辞書です:
        # - options["plugin"]は、getConfigOptions()が返すユーザオプション辞書。
        # - options["viewport"]["rendersettings_path"]は、スナップショットで使用するRenderSettings Primのシーングラフパス。
        # - options["viewport"]["camera_path"]は、スナップショットで使用するCamera Primのシーングラフパス。
        # - options["viewport"]["res_x"]は、スナップショットRender Productのピクセル幅。
        # - options["viewport"]["res_y"]は、スナップショットRender Productのピクセル高さ。
        pass

    def isRenderFinished(self):
        # システムは、このメソッドを定期的にコールします。
        # このオブジェクトで開始されたレンダリングが終了したらTrueを返してください。
        #
        # このメソッド内でself.updateImagePath(image_path)やself.updateMetadata(metadata_dict)をコールすると、
        # レンダリングの進捗に関するフィードバックを送信することができます。
        pass

    def mouseClick(self, x, y):
        # ユーザがレンダープレビューウィンドウ内をクリックした時にコールされます。
        # xとyは正規化された座標が入るので、例えば、x=0.0は左端、x=0.5は中央、x=1.0は右端を意味します。
        # マウスクリックの通常の挙動では、ユーザがクリックした画像の部分にレンダリングが集中します。
        pass

    def stopBackgroundRender(self):
        # ユーザがライブスナップショットを右クリックしてStop Renderingを選択するとコールされます。
        # このメソッドは、レンダリングが停止されるまで戻りません。
        pass


# プラグインファイルには必ず登録関数を入れなければなりません。これは、Houdiniがプラグインファイルを読み込んだ時に検索される関数です。

def registerBackgroundRenderers(manager):
    # 1つ目の引数は、レンダータイプをわかりやすくしたラベルです。
    # 2つ目の引数は、バックグラウンドレンダークラスです。
    manager.registerBackgroundRenderer('Cloud Service Render', MyCustomRenderer)

Houdiniの更新

pollFrequency()メソッドが返す秒数で制御された間隔で、HoudiniはあなたのプラグインのisRenderFinished()メソッドをコールします。 このメソッドでは、レンダリングが完了するとTrue、そうでない場合はFalseを返してください。

レンダリングが終了したかどうかを返すだけでなく、もしできれば、そこでself.updateMetadata(metadata_dict)self.updateImagePath(image_file_path)のヘルパーメソッドをコールして、レンダリング中にHoudiniを更新しても良いでしょう。

self.updateMetadata(metadata_dict)

スナップショットに関連付けられているメタデータを更新します。 ユーザは、Render Galleryビューア内の Infoボタンをクリックすることで、そのメタデータのキーと値を確認することができます。

以下のキーが辞書にあれば、Houdiniは、そのキーの値をレンダリング統計の一部として画像プレビューの右上に表示します:

"totalClockTime"

(float) 現在の経過時間(秒)。Render Galleryビューアは、これをレンダー統計内に表示します。

"percentDone"

(float) 完了率(0-100)で、これもレンダー統計内に表示されます。

"peakMemory"

(int) レンダラーの最大メモリ使用量(バイト)。

self.updateImagePath(image_file_path)

このヘルパーメソッドをコールすると、指定したファイルパスの画像を読み込み直して、その画像をスナップショット画像として使用するようにHoudiniに伝えます。 レンダープラグインをスナップショットの段階的な更新に対応させたいのであれば、画像ファイルパスが毎回同じであっても、その画像が変更される度にこれをコールしてください。

Note

プラグインオブジェクトのライフサイクル(例えば、レンダリングが本当に終了する時のisRenderFinished())では、必ず 少なくとも1回self.updateImagePath(image_file_path)をコールしてください。

以下のサンプルを参照してください。

外部プロセスの起動

プラグインで実装するレンダータイプが外部プログラムの起動を必要とする場合、Pythonのsubprocess.Popen() APIを使用することで、外部プロセスを起動して、そのプロセスを監視することができます。

  • そのsubprocess.Popen()が返すPopenオブジェクトからPopen.pidを使用してプロセスIDを取得することができます。

  • そのプロセスIDを使用することで、プラットフォームにより異なりますがCPU使用量や仮想メモリ使用量などのそのプロセスに関する統計情報を取得することができます(クロスプラットフォーム共通でプロセスに関する情報を取り出せるように設計されたpsutilなるサードパーティ製のPythonライブラリがあり、これが便利かもしれません)。

  • Popen.poll()の結果をチェックすることで、そのプロセスが抜けたかどうかを確認することができます(そのプロセスが終了していれば戻りコードが返され、まだ実行中であればNoneが返されます)。

  • Popen.kill()をコールすることで、stopBackgroundRender()を実行することができます。

以下の架空のサンプルでは、fastrenderという外部レンダー実行可能ファイルを起動し、それがまだ実行中かどうかを確認し、必要に応じて強制終了させる方法を説明しています。

import datetime
import json
import subprocess
import tempfile

from husd.backgroundrenderer import BackgroundRenderer


class FastRender(BackgroundRenderer):
    def pollFrequency(self):
        # システムは、2秒おきにこのレンダリングの更新をチェックします。
        return 2.0

    def startBackgroundRender(self, usd_filepath, options):
        self._popen = subprocess.Popen(
            ["fastrender",
             "--image", self._image_filepath]
        )
        # レンダリングが開始された時刻を記録します。
        self._start_datetime = datetime.datetime.utcnow() 

    def isRenderFinished(self):
        # レンダリングのメタデータを更新します。
        # レンダープロセスでレンダリング統計情報を段階的に出力する何かしらの方法がある場合
        # (例えば、stdoutへの出力を解析したり、定期的にJSONファイルを書き出すことができたり)、
        # その方法を使ってメタデータ辞書を収集することができます。
        metadata = {}

        # そのレンダープロセスのWall Clock Time(プロセスの開始から終了までの時間)を独自に非常に簡単に追跡することができます。
        time_delta = datetime.datetime.utcnow() - self._start_datetime
        metadata["totalClockTime"] = time_delta.total_seconds()

        # このサンプルでは、レンダープロセスが段階的にディスク上の画像ファイルを更新することを想定しているので、
        # 画像が更新されたことをシステムに伝え続ける必要があります。
        self.updateImagePath(self._imagepath)

        # Popen.poll()は、レンダープロセスが終了したら戻りコードを返し、
        # それ以外の場合はNoneを返します。
        rc = self._popen.poll()
        return rc is not None

    def stopBackgroundRender(self):
        self._popen.kill()


def registerBackgroundRenderers(manager):
    # 1つ目の引数は、レンダータイプをわかりやすくしたラベルです。
    # 2つ目の引数は、バックグラウンドレンダークラスです。
    manager.registerBackgroundRenderer('Fast Render', FastRender)

上記のサンプルでは、レンダープロセスがレンダリング時に出力画像を断続的に書き換えることを想定しています。 代わりに、レンダープロセスが完了した時に画像が終了することがわかっている場合にのみ、以下のようにisRenderFinished()を実装することができます:

def isRenderFinished():
    # ... メタデータを更新します ...

    # Popen.poll()は、レンダープロセスが終了したら戻りコードを返し、
    # それ以外の場合はNoneを返します。
    rc = self._popen.poll()
    if rc is not None:
        # レンダリングが終了したら、スナップショット画像を更新するようにHoudiniに伝えます。
        self.updateImagePath(self._imagepath)
        return True
    else:
        return False

Pythonスクリプト

はじめよう

次のステップ

リファレンス

  • hou

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

導師レベル

Python Viewerステート

Pythonビューアハンドル

プラグインタイプ