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

PDG Data Layer (PDGD)

データに変更があった時に自動的に購読者に更新情報を送信するPDGデータ定期購読サービス。

On this page

概要

PDGデータレイヤーとは?

PDGデータレイヤーまたは PDGD は、PDGデータにアクセスする別の手段です。

PDGDは、定期購読ベースのシステムです。 つまり、PDGDを使用することで、PDGオブジェクトを Subscribe(定期購読) し、そのオブジェクトのデータを Watch(監視) し、自動更新を介してそのオブジェクトのデータの変更内容を Report(報告) することができます。

基本的な使い方

PDGオブジェクトのデータにアクセスできるようにするには、まず最初に、そのオブジェクトの ビジュアライザ を作成してから、そのオブジェクトの サブスクリプション を作成する必要があります。

コード

挙動

pdgd.utilモジュール

PDGDを簡単に操作できるようにするユーティリティ関数とクラスが備わっています。

get_local_data_layer()メソッド

現行Houdiniインスタンスのデータレイヤーインターフェースを返します。

pdgd.util.PDGDObjectコンストラクタ

次の2つのパラメータを受け取ります:

  • PDGオブジェクトのパス。

  • データレイヤーインターフェース。これは、pdgd.util.get_local_data_layer()が返すローカルのデータレイヤーインターフェース、または、リモートデータレイヤーインターフェースを指定することができます。

サンプル

import pdgd.util 

data_layer = pdgd.util.get_local_data_layer() 
visualizer_object = pdgd.util.PDGDObject('pdg/object/path', data_layer)

def my_callback(pdg_object_path, object, message): 
        pass

# この段階でvisualizer_objectが既にデータを受け取っていることに気づくことが重要です。 
# その初期データを処理する必要がある場合は、一度手動でmy_callbackをコールする必要があります。

visualizer_object.addListener(my_callback)

pdgd.util.PDGDObjectクラスは、PDGDとの通信を抽象化します。 このクラスからPDGDオブジェクトを作成するとすぐに、そのオブジェクトがSubscribe(定期購読)され、そのオブジェクトの内部表現が構築され、すべてのデータがPythonタイプに変換されます。 この オブジェクトメンバー を使用することで、いつでもこの内部表現にアクセスすることができます。 さらには、Listener関数をPDGDObjectに登録することができます。 Listener関数は、そのオブジェクトのデータが変更される度に発動されます。

PDGDプロパティ名は、graph_listidといった単純な文字列になっています。 将来のバージョンのPDGDと互換性を維持できるようにするために、これらのプロパティ名はpdgd.PropertyNamesを介して公開しています。 そのため、graph_listなどのプロパティ名を直接使用するのではなく、代わりにpdgd.PropertyNames.GraphListを使用することを推奨します。

Tip

ここに基本的なPDGDの使い方を示したサンプルが見つかります: $HFS/pdgd/examples/visualizer

PDGグラフ

現行Houdiniインスタンスで作成されたすべてのPDGグラフを追跡するハイレベルなPDGオブジェクトが存在します。 このハイレベルなPDGオブジェクトのパスがpdgです。

サンプル

import pdgd.util

data_layer = pdgd.util.get_local_data_layer() 
visualizer_object = pdgd.util.PDGDObject('pdg', data_layer) 

print(visualizer_object.object)

上記のサンプルは、graph_listプロパティを含んだ辞書をプリントします。 このプロパティは、現行Houdiniインスタンスで作成されたすべてのPDGグラフのリストです。 新しいTOPネットワークを作成すると、このオブジェクトは、その新しいグラフがそのリストに含まれるように更新します。

graph_listプロパティには、グラフ名のリストが格納されています。 これらのグラフ名をpdg/{graph_name}の書式で使用することで、グラフオブジェクトパスを構築することができます。

サンプル

import pdgd 
import pdgd.util

GRAPH_LIST_KEY = pdgd.PropertyNames.GraphList

data_layer = pdgd.util.get_local_data_layer()
visualizer_object = pdgd.util.PDGDObject('pdg', data_layer)

graph_objects = {} 

if GRAPH_LIST_KEY in visualizer_object.object: 
    for graph_name in visualizer_object.object[GRAPH_LIST_KEY]: 
        graph_objects[graph_name] = pdgd.util.PDGDObject('pdg/' + graph_name, data_layer) 

PDGノード

pdg/{graph_name}/node_{node_id}の書式を使用することで、PDGノードにアクセスすることができます。

PDGノードは、PDGグラフが所有していて、そのPDGグラフをSubscribe(定期購読)することでノードIDリストを取得することができます。

pdgd.PropertyName.NodeListプロパティには、ノードオブジェクトパスの構築に使用可能なIDのリストが格納されています。

PDGワークアイテム

pdg/{graph_name}/work_item_{work_item_id}の書式を使用することで、ワークアイテムにアクセスすることができます。

PDGワークアイテムは、PDGグラフが所有していて、そのPDGグラフをSubscribe(定期購読)することでワークアイテムIDリストを取得することができます。

pdgd.PropertyNames.WorkItemsプロパティには、ワークアイテムオブジェクトパスの構築に使用可能なIDのリストが格納されていて、 各リストには、PDGグラフ内のすべてのワークアイテムが格納されています。 PDGノードオブジェクトにもpdgd.PropertyNames.WorkItemsプロパティがありますが、そのリストにはそのPDGノードのワークアイテムのみが格納されています。

リモートPDGD

リモートPDGインスタンスからデータにアクセスするには、コードにリモートデータレイヤーインターフェースのみが必要になります。

Houdiniには、デフォルトのサーバー/クライアント実装が2つ用意されています。それがWebSocketベースの実装と(TCPソケットを使用した)バイナリ実装です。 PDGDは、データレイヤーサーバーを起動するメソッドとリモートデータレイヤーインターフェース(クライアント)を作成するメソッドを公開しています。

To...Do this

サーバーを起動する

import pdgd

server_manager = pdgd.DataLayerServerManager.Instance()

# 以下のメソッドの1番目の引数がサーバーのタイプで、2番目の引数がサーバーがリッスンするポート番号です。
# ゼロは、システム側が利用可能なポートを選択することを意味します。

server = server_manager.createServer('DataLayerWSServer', 0) 

server.serve() 

print('Server listening at port: {}'.format(server.getPort())) 

リモートインターフェースに接続する

import pdgd

interface_manager = pdgd.DataLayerInterfaceManager.Instance() 
data_layer = interface_manager.createInterface('DataLayerWSClient', 'ws://host:port')

visualizer_object = pdgd.util.PDGDObject('pdg', data_layer)

ローカルインターフェースを使用した場合、このvisualizer_objectはSubscribe(定期購読)されるとすぐにデータを受信することもあり得ますが、 リモートインターフェースを使用した場合はあり得ません。 どちらの場合にも対応できるようにするのが良いです。

import pdgd.util 

data_layer = pdgd.util.get_local_data_layer() 
visualizer_object = pdgd.util.PDGDObject('pdg/object/path', data_layer) 

def my_callback(pdg_object_path, object, message): 
    pass

# 手動でコールバックをコールすることで、Subscribe(定期購読)中に受信したデータを必ずチェックできるようにします。
# これは、ローカルインターフェースを使用している場合におそらく必要となります。

my_callback('pdg/object/path', visualizer_object.object, None)

visualizer_object.addListener(my_callback) 

データレイヤービジュアライザ

PDGDObjectクラスは、 データレイヤービジュアライザ の実装です。 通常ではPDGDObjectがたいていの使用ケースで十分ですが、もっと細かいコントロールが必要であれば、更新メッセージの制御を備えたデータレイヤービジュアライザを実装した方が良い選択肢になります。

データレイヤービジュアライザでは、必ずprocessMessageメソッドを使用する必要があり、オプションでsubscriptionLostメソッドを使用します。 データが変更される度にメッセージが送信され、それらのメッセージには、変更されたプロパティに関する情報が含まれています。

PDGDでのオブジェクトプロパティは常に値のコレクション(listとsetに対応)ですが、ビジュアライザではどちらか一方だけを想定してはいけません。 ローカルでのパフォーマンスの向上にはsetが使用されますが、JSONシリアライゼーションを使用してリモートデータレイヤーに接続する場合は、すべてのプロパティがlistになっています。

サンプル

import pdgd 
import pdgd.util

class MyDataVisualizer(pdgd.DataLayerVisualizer): 

        def __init__(self): 
                super(MyDataVisualizer, self).__init__() 

        def processMessage(self, message, sender): 
                print('Received message from {}: \n{}\n'.format(sender, message.toJson())) 

        def subscriptionLost(self): 
                print('Subscription to data layer was lost') 

my_visualizer = MyDataVisualizer()

data_layer = pdgd.util.get_local_data_layer() 
data_layer.subscribe(my_visualizer, 'pdg') 

メッセージ

ビジュアライザは、データにアクセスできるようにメッセージを処理します。 各メッセージは、オブジェクトのデータに起きている内容をビジュアライザに伝えます。 このメッセージは、オブジェクトのフル状態を保持した スナップショットメッセージ 、または、特定のプロパティがどのように変更されたかを示した 差分(Delta)メッセージ のどれかです。

Python APIはpdgd.Messageクラスを公開しています。

getTypeメソッドをコールすることで、メッセージの タイプ を確認することができます。 返される値は以下のどれかです:

  • pdgd.Message.MessageType.eMessageTypeObjectSnapshot

  • pdgd.Message.MessageType.eMessageTypeAppendValues

  • pdgd.Message.MessageType.eMessageTypeRemoveValues

  • pdgd.Message.MessageType.eMessageTypeSetValues

toPython()メソッドをコールすることで、すべてのメッセージデータをPythonタイプに変換することもできます。 このメソッドによって、メッセージが扱いやすくなり、さらにはC++コードのコールを少なくすることができます。

スナップショットメッセージ

スナップショットメッセージには、オブジェクトのすべてのワークアイテムのプロパティとアトリビュートに関する情報が含まれています。 ほとんどのオブジェクトは、新しいサブスクリプションを追加するとすぐにスナップショットメッセージの送信を開始します。

getObjectSnapshot()メソッドをコールすることで、pdgd.ObjectSnapshotインスタンスを返すことができます。

差分(Delta)メッセージ

差分(Delta)メッセージには、特定のオブジェクトのプロパティに関する情報とその値がどのように変更されたのかを示した情報が含まれています。

getPropertyName()メソッドをコールすることで、どのプロパティが変更されたのかを調べることができます。

他にも、getPropertyArray()メソッドをコールすることで、変更された値のリスト(メッセージタイプは、その値のリストの意味を決めます)を取得することができます。

set値メッセージ

set値メッセージは、オブジェクトプロパティ値が置換されたことを示します。

ビジュアライザは、このメッセージ内の値リストがそのプロパティの完全な値リストとして考慮するはずです。

add値メッセージ

add値メッセージは、新しい値がオブジェクトプロパティに追加されたことを示します。

このメッセージには、追加された値の値リストが含まれています。

remove値メッセージ

remove値メッセージは、その値がオブジェクトプロパティから削除されたことを示します。

このメッセージには、削除された値の値リストが含まれています。

サブスクリプションハンドラー

サブスクリプションハンドラーは、PDGとの通信の役割を担います。 スナップショットの構築方法、または、差分(Delta)メッセージを送信するタイミングを知っているのが、このサブスクリプションハンドラーです。

PDGDには、グラフ、ノード、ワークアイテムのサブスクリプションハンドラーが備わっていますが、独自のサブスクリプションハンドラーを記述することで、PDGDを拡張することも可能です。

PDGDは、PDGと同様にタイプシステムを使用します。 pdgd.TypeRegistryクラスを使用することで、登録済みのサブスクリプションハンドラータイプを確認することができます。

サンプル

import pdgd

type_registry = pdgd.TypeRegistry.Instance() 
registered_handlers = type_registry.typeNames( 
        pdgd.registeredType.DataLayerSubscriptionHandler) 

print('Registered subscription handlers: {}'.format(registered_handlers)) 

PDGDに新しいサブスクリプションハンドラーを追加するには、PyDataLayerSubscriptionHandlerインスタンスを実装するクラスを定義して、PDGDタイプレジストリにそのクラスを登録する必要があります。

サブスクリプションハンドラーは、子サブスクリプションハンドラーを追加することができるので、データ階層を作成することができます。 例えば、PDGグラフのサブスクリプションハンドラーは、子ノードサブスクリプションハンドラーを作成します。

pdgd.util.SimpleHandlerの使い方

pdgd.util.SimpleHandlerは、新しいサブスクリプションハンドラーの作成に使用可能な基底クラスです。 pdgd.util.PDGDObjectと同様に、この基底クラスは、PDGDコードの上層にある抽象レイヤーです。

サンプル

import pdgd 
import pdgd.util 

class MySubscriptionHandler(pdgd.util.SimpleHandler):

        def __init__(self, handler): 
                super(MySubscriptionHandler, self).__init__(handler) 

                self.addValues('TestProperty', [0, 1, 2, 3, 4]) 
                self.addValues2('TestProperty2', ["test_string"]) 

pdgd.util.register_new_handler(MySubscriptionHandler, 'MySubscriptionHandlerName', ' my_subscription_handler_address') 

data_layer = pdgd.util.get_local_data_layer() 
visualizer_object = pdgd.util.PDGDObject(data_layer, 'my_subscription_handler_address')

上記のサンプルでは、独自のサブスクリプションハンドラーを定義してから、pdgd.util.PDGDObjectを使用してそのハンドラーをSubscribe(定期購読)しています。

pdgd.util.register_new_handlerメソッドは3つのパラメータを受け取ります:

  • サブスクリプションハンドラー定義。

  • このタイプのハンドラーのインスタンス化に使用される名前。

  • (オプション)PDGDがそのサブスクリプションハンドラーのインスタンスを作成するパス。

コマンド

データレイヤーは、双方向通信に対応しています。 サブスクリプションハンドラーは、コマンドを公開するので、あなたはそれらのコマンドを使用することで、汎用パラメータを受け取り、オプションで値を返すことができます。

サンプル

import pdgd 
import pdgd.util

class MySubscriptionHandler(pdgd.util.SimpleHandler):

        def __init__(self, handler): 
                super(MySubscriptionHandler, self).__init__(handler) 

        @pdgd.util.SimpleHandler.command('MyCommand') 
        def my_command(self, params): 
                print('MyCommand command called with parameters: {}'.format(params)

pdgd.util.register_new_handler(MySubscriptionHandler, 'MySubscriptionHandlerName', 'my_subscription_handler_address')

data_layer = pdgd.util.get_local_data_layer() 
data_layer.sendCommand('MyCommand', 'Parameter', 'my_subscription_handler_address') 

子ハンドラー

サブスクリプションハンドラーは子ハンドラーインスタンスを持つことができます。 SubscriptionHandlerManagerインスタンスを使用することで、新しいサブスクリプションハンドラーを作成することができます。

サンプル

import pdgd 
import pdgd.util 

class MySubscriptionHandler(pdgd.util.SimpleHandler): 

        def __init__(self, handler): 
                super(MySubscriptionHandler, self).__init__(handler) 

                this_handler_name = self.getObjectName() 
                child_handler_name = 'child' child_handler_path = '{}/{}'.format( 
                        this_handler_name, child_handler_name)

                handler_manager = pdgd.SubscriptionHandlerManager.Instance() 
                child_handler = handler_manager.createSubscriptionHandler(child_handler_path, 'MySubscriptionHandlerName') 

                self.addChildHandler(child_handler_name, child_handler)

pdgd.util.register_new_handler(MySubscriptionHandler, 'MySubscriptionHandlerName') 

クエリシステム

Queryオブジェクトは、動的な検索結果を提供する特別なオブジェクトです。 Queryオブジェクトは、Queryのパラメータを満たしたオブジェクトパスすべてを含んだ結果プロパティを持ちます。 デフォルトのデータレイヤー実装は、特別なquery?サブスクリプションパスの後にJSONフォーマットのクエリ定義を追加することによって、クエリに対応します。

クエリの入力には、プロパティパスのリストを格納します。 クエリは、そのプロパティの値とフォーマット文字列を使用して(このフォーマット文字列の中括弧をそのプロパティ値に置換して)オブジェクトのリストを構築します。そのリストは、フィルタリングしたりソートすることができます。

Query format example

query? 
{ 
    'property_paths': [ 
            'path/to/obj1/prop1', 
            'path/to/obj1/prop2', 
            'path/to/obj2/prop1' 
    ], 
    'format_string': 'path/to/other/obj/{}', 
    'sort': true,
    'reverse_sort': false,
    'sort_property': 'SortProperty',
    'filters': [ 
        [{ 
            'property_name': 'filter_prop',
            'filter_value': 'include_this',
            'negate': false 
        }, 
        { 
            'property_name': 'filter_prop_2',
            'filter_value': 'do_not_include_this',
            'negate': true 
        }] 
    ] 
}

上記のサンプルでは、filtersプロパティがリストのリストになっていて、その外側のリストはAND演算を意味し、その内側のリストはOR演算を意味し、 そのフィルター定義の[[A, B, C], [D, E, F], [G, H, I]]は、((A or B or C)and (D or E or F)and(G or H or I))のテストに相当します。

クエリのサンプル

PDGDで以下のオブジェクトが存在すると仮定します:

{ 
        'obj_a': { 
                'prop_a': [1, 3, 5]
        },
        'obj_b': { 
                'my_children_ids': [0, 1, 2, 3, 4, 5, 6, 7]
        }, 
        'obj_b/child0': { 'my_filter_prop': 0 },
        'obj_b/child1': { 'my_filter_prop': 1 },
        'obj_b/child2': { 'my_filter_prop': 2 },
        'obj_b/child3': { 'my_filter_prop': 3 },
        'obj_b/child4': { 'my_filter_prop': 0 },
        'obj_b/child5': { 'my_filter_prop': 1 },
        'obj_b/child6': { 'my_filter_prop': 2 },
        'obj_b/child7': { 'my_filter_prop': 3 } 
} 

obj_bの子オブジェクト内のobj_aprop_aプロパティで表現されたサブセットのうち、my_filter_prop1 のサブセットに興味があるのであれば、以下のクエリを構築すると良いでしょう:

import pdgd 
import pdgd.util 

query_string = 
""" 
query? 
{ 
    'property_paths': [ 
            'obj_a/prop_a',
    ],
    'format_string': 'obj_b/child{}',
    'filters': [ 
        [{ 
            'property_name': 'my_filter_prop',
            'filter_value': 1, 
            'negate': false 
        }] 
    ] 
} 
""" 

data_layer = pdgd.util.get_local_data_layer() 
query_visualizer = pdgd.util.PDGDObject(query_string, data_layer) 

上記のサンプルでは、クエリが他のPDGDオブジェクトと同じように動作します。 このクエリに影響を与えるデータが変更される度に、結果がどのように変更されたのかを示したメッセージが送信されます。

PDG Data Layer Panel

このペインは、 New Tab > New Pane Tab Type > TOPs メニューから開くことができます。

PDG Data Layer Panelペイン

このペインを使用することで、データレイヤーの独自のインテグレーションをテストすることができます。

PDG Data Layer Panel ペインでは以下のことができます:

  • 独自のポートまたは空きポートを使用してPDGデータレイヤーサーバーを起動することができます。

  • 複数のPDGデータレイヤーサーバーをスタックすることができます。

  • PDGデータレイヤーサーバーを停止することができます。

  • 使用するデータレイヤーバックエンドを選択することができます。現在のところ、バックエンドは1つしか選択できません。

  • データレイヤーバックエンドの内容のJSON変更のログバージョンを作成し、それをクリップボードにコピーすることができます。 これは、デバッグ用途で役立ちます。例えば、JSONを使用して、データレイヤーの独自インテグレーションの問題が ローカル にあるのかどうか(データレイヤーのクライアントなのかサーバーなのか)を識別することができます。

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

基本

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

次のステップ

リファレンス

  • すべてのTOPsノード

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

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

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

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

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

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

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

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

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

  • Python API

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

  • Job API

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

  • ユーティリティAPI

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