TouchDesignerでExtensionを使う
TouchDesignerのExtensionsを使うとCOMPに独自のプロパティやメソッドを定義することができます。
Derivativeの公式サイトにExtensionsの使い方とサンプルがまとまっているので、この記事では簡単に使い方を紹介しつつ、自分が使っている中で考えたことを書いていこうと思います。
Extensionの追加
COMP(Base COMPなど)を作成し、右クリックからCustomize Component...
でComponent Editorを開きます。Component EditorのExtension
を展開し、Extensionの名前を入力して、Add
をクリックします。そうすると、内部にExtension名と同じText DATが作成され、COMPのExtensions
タブ内のパラメータに作成されたText DATを参照するように設定がされます。Extensionの名前はCapitalizeして末尾にExt
をつけることが推奨されています(例えば、SampleExt
)。
作成されたText DATの中のスクリプトは以下のようになっており、ここに独自のプロパティやメソッドを定義していきます。
"""
Extension classes enhance TouchDesigner components with python. An
extension is accessed via ext.ExtensionClassName from any operator
within the extended component. If the extension is promoted via its
Promote Extension parameter, all its attributes with capitalized names
can be accessed externally, e.g. op('yourComp').PromotedFunction().
Help: search "Extensions" in wiki
"""
from TDStoreTools import StorageManager
import TDFunctions as TDF
class SampleExt:
"""
SampleExt description
"""
def __init__(self, ownerComp):
# The component to which this extension is attached
self.ownerComp = ownerComp
# properties
TDF.createProperty(self, 'MyProperty', value=0, dependable=True,
readOnly=False)
# attributes:
self.a = 0 # attribute
self.B = 1 # promoted attribute
# stored items (persistent across saves and re-initialization):
storedItems = [
# Only 'name' is required...
{'name': 'StoredProperty', 'default': None, 'readOnly': False,
'property': True, 'dependable': True},
]
# Uncomment the line below to store StoredProperty. To clear stored
# items, use the Storage section of the Component Editor
# self.stored = StorageManager(self, ownerComp, storedItems)
def myFunction(self, v):
debug(v)
def PromotedFunction(self, v):
debug(v)
属性とメソッド
通常のPythonクラスと同じように属性とメソッドを定義します。
# attributes:
self.a = 0 # attribute
self.B = 1 # promoted attribute
def myFunction(self, v):
debug(v)
def PromotedFunction(self, v):
debug(v)
オペレータのext
オブジェクトにExtensionと同名のオブジェクトが作られるので、そこから属性やメソッドにアクセスすることができます。
op('SampleComp').ext.SampleExt.a
op('SampleComp').ext.SampleExt.B
op('SampleComp').ext.SampleExt.myFunction('Hello, World!')
op('SampleComp').ext.SampleExt.PromotedFunction('Hello World!')
CapitalizeされているものはPromotedな属性やメソッドと呼び、以下のようにオペレータから直接利用することができます。
op('SampleComp').B
op('SampleComp').PromotedFunction('Hello, World!')
使い分けとしては、プログラミングでいうところのpublic
やprivate
みたいな感じでCOMP外から利用する属性やメソッドはPromoted、COMP内から利用する属性やメソッドは非Promotedにするとインターフェースが整備されていいのではと思います。
プロパティ
プロパティは属性に似ていますが、値の変更がExpressionなどで参照している箇所にも自動で反映されるDependable Valueにすることができます。
# properties
TDF.createProperty(self, 'MyProperty', value=0, dependable=True, readOnly=False)
属性やメソッドと同様にext
からアクセスできるほか、名前がCapitalizeされているものはPromotedなプロパティなのでオペレータから直接アクセスすることができます。
# クラス定義外から取得
op('SampleComp').ext.SampleExt.MyProperty
op('SampleComp').ext.SampleExt.myProperty
op('SampleComp').MyProperty # Promoted
op('SampleComp').myProperty # エラー
# クラス定義内から取得
self.MyProperty
self.myProperty
値の更新はreadOnly
の設定で異なり、readOnly=False
のときは値の取得の場合と同じような操作感で更新できます。
# クラス定義外から更新
op('SampleComp').ext.SampleExt.MyProperty = newValue
op('SampleComp').ext.SampleExt.myProperty = newValue
op('SampleComp').MyProperty = newValue # Promoted
op('SampleComp').myProperty = newValue # エラー
# クラス定義内から更新
self.MyProperty = newValue
self.myProperty = newValue
readOnly=True
のときはプロパティ名の先頭に_
(アンダースコア)をつけて値にアクセスして更新する必要があります。
# クラス定義外から更新
op('SampleComp').ext.SampleExt._MyProperty.val = newValue
op('SampleComp').ext.SampleExt._myProperty.val = newValue
op('SampleComp')._MyProperty.val = newValue # エラー
op('SampleComp')._myProperty.val = newValue # エラー
# クラス定義内から更新
self.MyProperty = newValue # エラー
self.myProperty = newValue # エラー
self._MyProperty.val = newValue
self._myProperty.val = newValue
dependable
でプロパティをDependable Valueにするかしないかを設定できますが、dependable=False
だと属性と使い勝手が変わらないので、基本的には値を他の箇所から参照して自動で反映したい場合にでdependable=True
でプロパティを利用することになると思います。属性・メソッドのPromotionと同じように、COMP外からも値を変更したいときはreadOnly=True
、COMP内からのみ値を変更したいときはreadOnly=False
にするといいでしょう。
Storage Manager
属性やプロパティはプロジェクトを開き直したときなどExtensionが初期化されると値も初期化されてしまいます。値を永続化するにはStorage Managerを使いますが、COMPのCustomパラメータでも値を永続化することができます。公式ドキュメントでは値を永続化してかつ公開したくない場合にStorage Managerを使うことを推奨しています。
# stored items (persistent across saves and re-initialization):
storedItems = [
# Only 'name' is required...
{'name': 'StoredProperty', 'default': None, 'readOnly': False,
'property': True, 'dependable': True},
]
# Uncomment the line below to store StoredProperty. To clear stored
# items, use the Storage section of the Component Editor
# self.stored = StorageManager(self, ownerComp, storedItems)
Storage Managerで管理するオブジェクトには名前をキーにしてアクセスできます。
self.stored['StoredProperty']
self.stored['StoredProperty'] = newValue
property
がTrue
のときはプロパティと同じように値にアクセスすることができ、名前がCapitalizeされている場合はPromotedになり挙動が変わるのも同様です。
# クラス定義外から取得
op('SampleComp').ext.SampleExt.StoredProperty
op('SampleComp').ext.SampleExt.storedProperty
op('SampleComp').StoredProperty
op('SampleComp').storedProperty # エラー
# クラス定義内から取得
self.StoredProperty
self.storedProperty
property
がTrue
のときのみreadOnly
は機能し、True
にするとプロパティをRead Onlyにできますが、試してみた感じでは通常のプロパティとは異なり_
(アンダースコア)をつけても更新できないようです。
op('SampleComp').ext.SampleExt._StoredProperty.val = newValue #エラー
op('SampleComp').ext.SampleExt._storedProperty.val = newValue #エラー
self._StoredProperty.val = newValue #エラー
self._storedProperty.val = newValue #エラー
dependable
は管理するオブジェクトがリストやディクショナリなどのコレクションのときに Deep Dependable Collectionにするかどうかの設定で、コレクションではない場合はTrue
でもFalse
でもDependable Valueになります。
まとめ
独自のプロパティやメソッドを定義することでCOMP外部からのイベントに対してプログラミングで言うところのインフェースを整備でき、またCOMP内部では乱雑に散らばりがちなスクリプトを一箇所で管理できるというメリットがあります。
COMPに対してコールバックを定義できるCallbackExtという仕組みと合わせて使うと、COMPへのメッセージはExtensionで扱い、COMPからのメッセージはCallbackExtで扱うというようにCOMPに関わるイベントをきれいに整理することができます。
ちなみにCallbackExtもExtensionの仕組みを利用して実装されています。
Discussion