Houdini 19.5 PDG/TOPsを使ってタスクを実行する方法

独自のファイルタグとハンドラー

PDGはファイルタグを使用して出力ファイルのタイプを決めます。

On this page

概要

PDGワークアイテム上の出力ファイルとファイルアトリビュートにはタグが割り当てられるので、PDGはそれらのファイルタイプを識別できるようになっています。 タグは、何のアプリケーションを使用してアトリビュートペインからファイルを開くのか、さらには、ディスク上の既存ファイルをチェックする時にどのキャッシュハンドラー(s)を実行するのかを決めます。 PDG pdg.TypeRegistryを使って独自のタグを登録して、それらのタグをビルトインノードで使用することができます。 他にも、Pythonで記述された独自のキャッシュハンドリング関数を登録して、PDGのキャッシュ化の仕組みを手動で制御することもできます。

pdg.TypeRegistryを介して追加された独自のタグは、ファイルタグを設定するパラメータを持ったノードのタグ選択ドロップダウンメニューに自動的に表示されます。

独自のファイルタグ

独自のファイルタグは、PDG Type Registryを介して登録します。 必要に応じてPython Shellから、または、$HOUDINI_PATH/pdg/typesディレクトリに登録スクリプトを追加することでそうすることができます。 Houdiniを起動した時にPDGがHoudini検索パスのpdg/typesディレクトリからすべてのスクリプトとモジュールを自動的に読み込みます。 例えば、独自のタグを読み込むスクリプトを作成して、そのスクリプトを$HOME/houdiniX.Y/pdg/types/custom_tags.pyとして保存するとします。 そのスクリプトファイルの中身には、PDGがスクリプトを読み込んだ時に自動的にコールされるregisterTypes関数を定義する必要があります。

def registerTypes(type_registry):
    type_registry.addTag("file/geo/collision")
    type_registry.addExtensionTag(".py, "file/text/pythonscript")
    type_registry.addTagViewer("file/bgeo", "gplay")

独自のファイルタグを登録するのに使用可能なAPIメソッドが2つあります:

  • 1つ目のメソッドは、pdg.TypeRegistry.addTagです。これはタグをグローバルリストに直接追加します。ファイル拡張子との関連付けは行なわれません。

  • 2つ目のメソッドは、pdg.TypeRegistry.addExtensionTagです。これは独自のタグを特定のファイルタイプに使用することができます。 タグがグローバルリストに追加されるだけでなく、ファイル拡張子とタグのマッピングが行なわれます。 するとPDGは、ファイルを追加する際に明示的に別のタグを指定しない限り、その拡張子が付いたファイルに対してその独自のタグを自動的に使用するようになります。

特定のタグに対してビューアアプリケーションの名前を指定することもできます。 このビューアアプリケーションは、ワークアイテムのペイン内のファイルリンクの開き方を決めます。 ビューアアプリケーションを指定した場合、PDGはそのアプリケーションを使ってそのファイルを開くようになります。 指定しなかった場合、代わりにそのファイルを含んだディレクトリが開かれます。 pdg.TypeRegistry.addTagViewerメソッドを使って独自のビューアを追加することができます。

独自のキャッシュハンドラー

PDGの多くのノードはディスクキャッシュに対応しています。 ワークアイテムで必要な出力ファイルが既にディスク上に存在していれば、そのワークアイテムは再実行せずにキャッシュからクックをすることができます。 ノード単位でキャッシュ化を有効にすることができ、常に読み込み、常に書き込み、ファイルが存在した場合のみキャッシュから読み込むのか設定することができます。 Houdiniを起動する前にHOUDINI_PDG_CACHE_DEBUG環境変数を設定すると、PDGはグラフをクックする時にキャッシュファイルのデバッグ情報をプリントします。

内部的にPDGはキャッシュファイルがディスク上で見つかるかどうかチェックすることでそのファイルを確認します。 これは、どのアプリケーションもどのファイルタイプにも適して いません 。 例えば、出力ファイルがクラウドストレージシステム上に保存されていたり、キャッシュチェックの一部としてさらにファイル検証を行ないたい場合には、これは適さないです。

別の方法として、(前のセクションで説明している)独自のファイルタグを登録するのと同様に、独自のキャッシュハンドラーを登録することで、キャッシュファイルの有無を検証することができます。 例えば、独自のキャッシュハンドラーを定義したスクリプトを作成して、そのスクリプトを$HOME/houdiniX.Y/pdg/types/custom_handlers.pyとして保存するとします。

import os

def simple_handler(local_path, raw_file, work_item):
    print(local_path)
    return pdg.cacheResult.Skip

def custom_handler(local_path, raw_file, work_item):
    # 正しいアトリビュートを持たないワークアイテムをスキップします。
    if work_item['usecustomcaching'].value() == 0:
        return pdg.cacheResult.Skip
    try:
        if os.stat(local_path).st_size == 0:
            return pdg.cacheResult.Miss
        return pdg.cacheResult.Hit
    except:
        return pdg.cacheResult.Miss

def registerTypes(type_registry):
    type_registry.registerCacheHandler("file/geo", custom_handler)
    type_registry.registerCacheHandler("file/geo/usd", simple_handler)

各キャッシュハンドラーには3つの引数が渡されます: キャッシュファイルのローカルパス、Raw pdg.Fileオブジェクト、ファイルを所有しているpdg.WorkItem。 このファイルオブジェクトには、Rawパスやファイルタグといったファイルに関連付けられているすべてのメタデータが含まれています。

Warning

キャッシュハンドラーフック中にワークアイテムを変更 しないでください 。 グローバル変数にワークアイテムを保存してハンドラーメソッド外からそのワークアイテムにアクセス しないでください 。 これは無効です。

キャッシュハンドラーは特定のファイルタグに対して登録されます。 上記の例だと、simple_handlerを使ってまず最初にfile/geo/usdのタグが付いたファイルがチェックされます。 このハンドラーはpdg.cacheResult.Skip戻りコードを返すので、キャッシュシステムは次の候補のfile/geoのハンドラーに移ります。 このハンドラーは、ファイルサイズがゼロでないかどうか検証するのですが、そのファイルを所有しているワークアイテムにusecustomcachingアトリビュートが設定されている場合にのみ検証します。 どちらのハンドラーもSkipを返した場合、代わりにPDGのビルトインキャッシュチェックの仕組みが使用されます。

ハンドラーがpdg.cacheResult.Hitまたはpdg.cacheResult.Missを返すとすぐに、そのハンドラー評価が停止し、その結果が使用されます。 最も明確なマッチングタグパターンが常に最初に評価されます。

Note

fileタグを追加することで、すべてのファイルタイプに対してハンドラーを登録することができます。

Note

ファイルがバッチの一部として生成されていた場合、バッチサブアイテムを使ってキャッシュハンドラーがコールされます。 work_item.batchParentを見ることでバッチの親を取得することができます。

独自のファイル転送ハンドラー

背景

PDGには、ファイル転送に関してデフォルトの想定と挙動がいくつか決められています。 PDGは、あなたが共有ファイルシステムを使用していて、それがすべてのマシン上に存在し、それらのマシンを特定のグラフ内のタスクにのみ動作させることを想定しています。 例えば、スケジューラ系ノードは通例では、現行.hipファイルなどのローカルファイルをそのスケジューラで指定された作業ディレクトリ(ローカルファイルパス、または、NFSマウントポイントからアクセス可能なパス)にコピーを試みます。

しかし、あなたのセットアップがPDGで想定しているものと大きく異なる場合があります。 例えば、作業マシンがオブジェクトストレージシステムまたはデータベースを使用しないとデータにアクセスできない場合とか、単にマウントポイントをジョブ投入マシンと共有できない場合とかです。

そのような場合には、 独自の PDGファイル転送ハンドラーを作成することで、あなた独自のファイル転送ロジックを実装することができます。

挙動

PDGは、あなた独自のPythonロジックで上書き可能なファイル転送 フック を露出して、独自のファイル転送の挙動を実行します。 このフックは、ワークアイテムにアクセスすることができ、スケジューラとファイル転送を関連付け、そのファイルに関するすべての情報を転送することができます。 そして、そのファイル転送ハンドラーは、意図した場所(例えば、一連のリモートマシンなど)にファイルをコピーするのに必要なPythonコードを使用することができます。

独自のファイル転送ハンドラーでは、独自のキャッシュ化ロジックを実装するかどうか、特定のファイルを制御しないようにするかどうか決めることができます。 このような挙動によって、複数の独自のハンドラーを導入して、入力ワークアイテムからのアトリビュートを使ってそれらのハンドラーを制御することができます。 どれかの独自のハンドラーで特定のファイル転送操作を制御したくない場合、PDGのデフォルトハンドラーが自動的にファイルをそれに関連付けられているスケジューラの作業ディレクトリに直接コピーを試みます。

ファイル転送ハンドラーシステムは、ローカルファイルパスをユーザ定義のキャッシュIDにマップするスレッドセーフなキャッシュを保存します。 ハンドラーがファイルを転送する度に、そのハンドラーは、そのファイルからレポートされた最後のキャッシュID、または、そのファイルにIDが記録されていなければ 0 を渡します。 次に、このハンドラーはそのIDを使用して、そのファイルを再度コピーする必要があるのか、それとも、キャッシュ済み/既にコピー済みと見なすのかを決めることができます。 このキャッシュチェックロジックは、ファイル転送ハンドラーで管理されますが、PDGは、ハンドラーでグローバル変数を必要としないようにキャッシュIDの保存を制御します。

実装のサンプル

以下のコードでは、PDGに組まれているデフォルトのファイル転送ハンドルを真似たPythonの実装を載せています。

このコードを現在のHoudiniセッションでテストしたいのであれば、ファイルに保存してください(例えば、$HOME/houdiniX.Y/pdg/types/custom_file_transfer.py)。

import pdg
import os
import shutil

def transferCopy(src_path, dst_path, file_object, work_item, scheduler, cache_id):
    dst_exists = os.path.exists(dst_path)

    # 両方のパスが同じで存在する場合は早期に終了。
    is dst_exists and os.path.samefile(src_path, dst_path):
        return pdg.TransferPair.skip()

    # 無条件でディレクトリをコピーします。
    if os.path.isdir(src_path):
        try:
            shutil.copytree(src_path, dst_path)
            return pdg.TransferPair.success()
        except:
            return pdg.TransferPair.failure()

    # 古い更新時刻と現在の更新時刻が同じであれば、コピーをスキップして、
    # その転送がキャッシュ化されなかったことを示します。
    src_mtime = int(os.path.getmtime(src_path))
    if dst_exists:
        if os.path.getsize(src_path) == os.path.getsize(dst_path):
            if src_mtime == cache_id:
                return pdg.TransferPair.cached()

    dst_dir = os.path.dirname(dst_path)

    # 必要に応じて中間ディレクトリを作成します。
    try:
        if not os.path.exists(dst_dir):
            os.makedirs(dst_dir)
    except OSError:
        pass

    # ファイルをコピーします。
    shutil.copyfile(src_path, dst_path)
    return pdg.TransferPair.success(src_mtime)

# すべてのファイルのタイプに対してハンドラーを登録します。
def registerTypes(type_registry):
    type_registry.registerTransferHandler("file", transferCopy)

キャッシュ化でデフォルトのローカルファイル更新時刻を使用できるように、このレジストレーション関数にuse_mod_time引数を渡すこともできます。例:

def registerTypes(type_registry):
    type_registry.registerTransferHandler("file", transferCopy. use_mod_time=True)

PDGは、ファイルが特定のスケジューラでまだ 転送されていない 時、または、ローカルファイルの更新時刻が最後の転送で記録された時刻よりも新しい時にのみハンドラーを呼び出します。

Warning

独自のハンドラー関数に渡されたワークアイテムを変更しようと しないでください 、そして、そのワークアイテムをグローバル変数に格納してからその関数のスコープ外でそのワークアイテムにアクセスしようと しないでください 。これは無効です。

独自のファイル情報取得関数

ワークアイテムの出力として保存されるファイルまたはアトリビュートはどちらもフィールドサイズがあり、PDGがそのファイルが古いかどうかを判断する際に使用する64ビット整数フィールドが含まれています。 例えば、ディスク上のファイルを変更した後にFile Pattern TOPノードを再クックすると、その変更したファイルに呼応したワークアイテムは自動的にクックの一部としてDirty(変更あり)となります。 デフォルトでは、PDGはファイルの詳細情報を取得してファイルのサイズを判断し、ファイルの更新日時を使用して追加フィールドを設定します。 しかし、Pythonで独自スキームを使用することで、 独自のファイル情報取得関数 を登録することができます。

サンプル

キャッシュハンドラーと同様に、独自のファイル情報取得関数は出力ファイルタグに基づいて登録します。 独自のファイル情報取得関数は単一の値または値のペアのどちらも返すことができます。 単一の値を返した場合、その値がファイルのハッシュフィールドに割り当てられ、ファイルサイズはディスク上のファイルパスの詳細情報の取得によって判断されます。 値のペアを返した場合、その2つ目の値が代わりにそのファイルのサイズフィールドに設定されます。

以下のコードでは、テキストファイルに対してCRCチェックサムを行なう方法を説明しています。 現在のHoudiniセッションでこれをテストしたいのであれば、このコードをファイル(例えば、$HOME/houdiniX.Y/pdg/types/custom_file_hash.py)に保存してください。

import zlib

def crc_handler(local_path, raw_file, work_item):
    try: 
        with open(local_path, 'rb') as local_file:
            return zlib.crc32(local_file.read())
    except:
        return 0

def registerTypes(type_registry):
    type_registry.registerHashHandler("file/txt", crc_handler)

この独自のファイル情報取得関数は3個の引数(ファイルのローカルパス、Rawpdg.Fileオブジェクト、そのファイルを所有しているpdg.WorkItem)を使って呼び出します。 この関数がゼロ以外の値を返すと、その値がファイルのハッシュとして保存されます。 ゼロでない値のペアを返すと、その2つ目の値がファイルのsizeフィールドに割り当てられます。 その戻り値がゼロなら、PDGは他に該当するファイル情報取得関数が存在するかどうかチェックし、見つからなければ、PDGはファイルの更新日時/ディスクサイズを使用するビルトインの実装を代用します。

この関数の実装内でノードタイプをフィルタリングすることで、この関数を特定のノードタイプに適用することが可能です。 例えば、以下のファイル情報取得関数はすべてのファイルタイプに適用されますが、File Pattern内のワークアイテムにのみ適用されます:

import zlib

def crc_handler(local_path, raw_file, work_item):
    if work_item and work_item.node.type.typeName != 'filepattern':
        return 0

    try: 
        with open(local_path, 'rb') as local_file:
            return zlib.crc32(local_file.read())
    except:
        return 0

def registerTypes(type_registry):
    type_registry.registerStatHandler("file", crc_handler)

Warning

独自のファイル情報取得関数内でワークアイテムを 修正 しないでください。 そして、グローバル変数にワークアイテムを保存してその関数のスコープ外でそのワークアイテムにアクセスするといったことは しないでください 。これは無効です。

PDG/TOPsを使ってタスクを実行する方法

基本

初心者向けチュートリアル

次のステップ

リファレンス

  • すべてのTOPsノード

    TOPノードは、データをネットワークに送り込んでワークアイテムに変換し、色々なノードでそれを制御するワークフローを定義します。たいていのノードは、ローカルマシンまたはサーバーファーム上で実行可能な外部プロセスを表現しています。

  • プロセッサ系ノードコールバック

    プロセッサ系ノードはスケジューラで実行可能なワークアイテムを生成します。

  • パーティショナー系ノードコールバック

    パーティショナー系ノードは複数の上流ワークアイテムを単一パーティションにグループ化します。

  • スケジューラ系ノードコールバック

    スケジューラ系ノードはワークアイテムを実行します。

  • 独自のファイルタグとハンドラー

    PDGはファイルタグを使用して出力ファイルのタイプを決めます。

  • Python API

    ディペンデンシーグラフを扱うためのPython PDGパッケージのクラスと関数。

  • Job API

    ジョブスクリプトで使用するPython API

  • ユーティリティAPI

    Python pdgutilsパッケージのクラスと関数は、PDGノードでの使用だけでなく、スクリプトやプロセス外のジョブスクリプトでの使用も想定されています。