TouchDesigner でのドラッグ&ドロップについて
公式Wiki
TouchDesignerでUI構築、ツール制作を行う際に便利なドラッグ&ドロップについて解説します
サンプルファイルとか
2019年のアドカレで同じ内容の記事を書いていたみたいですが、機能が更新されていたり使いやすくなっていたので改めてドラッグ&ドロップについてまとめていきたいと思います
動作環境
TouchDesigner 2022.31030
2019年の記事
ファイルやエディタ上でのD&Dについては上の記事に詳しく書いてあります(ここら辺の機能は変わってません)
以前のD&Dシステム(Legacy Drag/Drop System)との違い
- 以前はドラッグ・ドロップがされたタイミングで実行でしたが、現在のバージョンではそれ以外にも特定の実行タイミングを持つ
- Legacy Drag/Drop Systemは新しいバージョンでも使えるので以前の記事を参照してください
ドラッグ&ドロップの実装
では、実際にD&Dの仕組みを見ていきましょう!
これまでの仕組みと同様すべてのPanel ComponentsでD&DのPython Callbackを使用することができます
When Dragging This
On Dropping into
という二つのパラメータをUse Drag/Drop Callbacks
に変更します
そして、Drag/Drop Callbacks
の右側にあるAdd
をクリックすることでデフォルトのD&D Callbackが作成されPanelCOMPにDockされます
ドラッグ可能なパネルの設定
まずはドラッグ操作を拡張させるためのCallbackの設定をします
自動で作成されたCallbackの書かれたTextDATの中のドラッグに関わるコールバック関数を見ていきましょう
def onDragStartGetItems(comp, info):
"""
Called when information about dragged items is required.
Args:
comp: the panel clicked on to start drag
info: A dictionary containing all info about drag
callbackPanel: the panel Component pointing to this callback DAT
Returns:
A list of dragItems: [object1, object2, ...]
"""
dragItems = [comp] # drag the comp itself
#debug('\nonDragStartGetItems comp:', comp.path, '- info:\n', info)
return dragItems
def onDragEnd(comp, info):
"""
Called when a drag action ends.
Args:
comp: the panel clicked on to start drag
info: A dictionary containing all info about drag, including:
accepted: True if the drag was accepted, False if not
dropResults: a dict of drop results. This is the return value of
onDropGetResults
dragItems: the original dragItems for the drag
callbackPanel: the panel Component pointing to this callback DAT
"""
#debug('\nonDragEnd comp:', comp.path, '- info:\n', info)
onDragStartGetItems
UIをクリックし、ドラッグしたときにドラッグされている情報を伝える関数
ドラッグされたアイテムの情報が必要なときに呼び出される
引数
comp
- ドラッグを開始するためにクリックされたパネル
info
- ドラッグに関するすべての情報を含む辞書
ドラッグ操作を開始するためには、このコールバック関数からアイテムのリストを返す必要があります
これらは通常オペレータになりますが、必要に応じて、パラメータやCHOPのチャンネル、Pythonオブジェクトをアイテムリストに入れることができます
返り値
onDragStartGetItems
記述例 | |
---|---|
return [comp] |
ドラッグしたオペレータ自身(デフォルト設定) |
return [comp.parent()] |
ドラッグしたパネルの親 |
return [op('custom1'}, op('custom2')] |
2つのオペレータ |
return [comp.par.x] |
ドラッグしたオペレータのパラメータ |
return [op('myCHOP')['speed']] |
CHOPの特定のチャンネル |
この返り値をカスタマイズすることで、好きなものをドラッグさせることができます
オペレータをドラッグするというのは簡単に想像ができると思います
ただパラメータやチャンネルをドラッグするというと文面では???
となるかもしれません
これはCHOPの値をパラメータで使ったり、複数のパラメータをリンクさせるために行っている普段からやっているシンプルな操作になります
onDragEnd
ドラッグ完了時に呼び出される関数
comp
- ドラッグをされたパネル
info
- ドラッグに関するすべての情報を含む辞書(以下の情報を含む)
キー | 内容 |
---|---|
Accepted | ドラッグが受け入れられた場合は true |
dropResults | ドロップ結果の辞書, onDropGetResults(後述) の返り値 |
dragItems | ドラッグされているオブジェクトのリスト |
このコールバックはドロップが発生した後に実行されるため、ドロップの失敗成功を監視して処理したり、自動で読み込みなどを行えます
ただドロップされる側にもコールバック関数を設定できるので紛らわしくならないような実装が大切です
パネルをドロップ ターゲットとして設定
ドラッグと同様にいくつかのコールバック関数が設定されています
# callbacks for when associated Panel is being dropped on
def onHoverStartGetAccept(comp, info):
"""
Called when comp needs to know if dragItems are acceptable as a drop.
Args:
comp: the panel component being hovered over
info: A dictionary containing all info about hover, including:
dragItems: a list of objects being dragged over comp
callbackPanel: the panel Component pointing to this callback DAT
Returns:
True if comp can receive dragItems
"""
#debug('\nonHoverStartGetAccept comp:', comp.path, '- info:\n', info)
return True # accept what is being dragged
def onHoverEnd(comp, info):
"""
Called when dragItems leave comp's hover area.
Args:
comp: the panel component being hovered over
info: A dictionary containing all info about hover, including:
dragItems: a list of objects being dragged over comp
callbackPanel: the panel Component pointing to this callback DAT
"""
#debug('\nonHoverEnd comp:', comp.path, '- info:\n', info)
def onDropGetResults(comp, info):
"""
Called when comp receives a drop of dragItems. This will only be called if
onHoverStartGetAccept has returned True for these dragItems.
Args:
comp: the panel component being dropped on
info: A dictionary containing all info about drop, including:
dragItems: a list of objects being dropped on comp
callbackPanel: the panel Component pointing to this callback DAT
Returns:
A dictionary of results with descriptive keys. Some possibilities:
'droppedOn': the object receiving the drop
'createdOPs': list of created ops in order of drag items
'dropChoice': drop menu choice selected
'modified': object modified by drop
"""
debug('\nonDropGetResults comp:', comp.path, '- info:\n', info)
return {'droppedOn': comp}
onHoverStartGetAccept
dragItems がドロップとして受け入れられるかどうかを知る必要がある場合に使用します
comp
- ドロップされたパネル
info['dragItems']
- コンポーネント上にドラッグされているオブジェクトのリスト
返り値
True/False
dragItems
がドロップできるならTrue、そうでなければFalse
dragItems
の確認例
# ドラッグアイテムの要素数の確認
if len(info['dragItems']) == 1:
debug('要素が一つのもののみ受け付けます!')
# isinstance関数を用いてドラッグアイテムがオペレータかどうかを判断(特定のクラスを判別)
if isinstance(info['dragItems'][0], OP):
debug('アイテムの一つ目はオペレータです!')
# 確認によく使うクラス:
# OP: オペレータ
# COMP, TOP, CHOP, SOP, MAT, DAT: オペレータのカテゴリ
# panelexecDAT, containerCOMP etc.: 特定のオペレータタイプ
# Channel: CHOPのチャンネル
# Par: パラメータ
# tdu.FileInfo: ファイル
# all関数、リストの内包表現、isinstance関数を用いて、ドラッグアイテムのすべてがDATであるかを判別
if all([isinstance(item, DAT) for item in info['dragItems']]):
debug('ドラッグされたアイテムは全部DATだよ!')
上記のスクリプトなどを用いて、特定のもののみドラッグを受け付けることが可能です
onHoverEnd
dragItems がコンプのHover領域を離れたときに呼び出されます
comp
- ドロップされたパネル
info['dragItems']
- コンポーネント上にドラッグされているオブジェクトのリスト
onDropGetResults
onHoverStartGetAccept
がdragItems
に対して True
を返した場合にのみ呼び出されます
comp
- ドロップされたパネル
info['dragItems']
- コンポーネント上にドラッグされているオブジェクトのリスト
返り値
この関数からアイテムのDictを返すことはオプションですが、ドラッグ ソースとドロップ ターゲット間の相互にやり取りができます
Dictには、説明的なキーを含む結果が含まれている必要があります
次のキーと値のペアは標準であり、可能な場合は以下の例に従いましょう
'createdOPs':[<list of created ops in order of corresponding drag item>]
どの関数にもデフォルトでDebug()
でその関数の情報などがプリントできるようにかかれているので、まず始めはその部分のコメントアウトを解除して確認してみましょう
実践
今回作成するものはUIからD&Dすることで自分でカスタマイズしたUIをネットワークエディタに複製するというものです
まずは適当にPanelCOMP
でいい感じにUIのパーツとなるコンポーネントを組んでおいてください (Widgetでいいだろとか言わないで)
検索対象にするために作ったコンポーネントにタグをつけます
今回はmy-panel
というタグをつけました
次にそれらのタグのついたオペレータをOP FindDATを用いてすべて取得します
OPFind DAT
のパラメータのFilter/Tags
にmy-panel
(設定したTag)を打ち込みます
ついでに並び順を楽に決められるようにオペレータの置いてある位置を取っておきましょう
OPFind DAT
のコールバック関数に以下のプログラムを書きます
def onInitGetColumnNames(dat):
return ['nodeY']
def onFindOPGetValues(dat, curOp, row):
return [curOp.nodeY]
コールバック関数によってOPFind DAT
で見つけられた要素に対して追加の情報をTableに入れることが可能となります
取得したNodeYを基準にSortDATで並べ替えを行います
この処理をすることによりネットワークエディタ上での並び順をそのままUIの並び順に使用できます
続いて本題のドラッグドロップの処理
OPFind
を基に作成されたTableからUIを作成します
そこで作成されたCompをドラッグすることによってカスタマイズされたコンポーネントを好きなところに持っていく仕組みです
def onDragStartGetItems(comp, info):
# それぞれの要素に応じてカスタマイズされているCOMPをドラッグする
dragItems = [op(comp.op('select_info')[1, 'path'].val)]
return dragItems
def onDragEnd(comp, info):
if info['accepted']:
create_op = info['dropResults']['createdOPs'][0]
# ドロップされ作成されたオペレータに対して処理を行う
# ここではExpressionが書かれているところを参照切れなどを起こさないように定数に変換
for o in create_op.ops('*'):
for p in o.pars():
if p.mode == ParMode.EXPRESSION:
p.val = p.eval()
p.expr = ''
p.mode = ParMode.CONSTANT
表示用のUIにはコンポーネント名とプレビューを出しています
それぞれのCOMPではDrag/Dropの設定は行っておらず、親の設定を使用する設定を行っています
これにより、一か所でスクリプトを管理することができます
親の設定を使用する場合は一番近い親の設定が使用されます
また共通のスクリプトであったとしても、取れる引数はパネルに応じて来るためしっかりと区別することも可能です
終わりに
このような形でドラッグ&ドロップの仕組みをざっくり解説しました
用途に応じてかなり拡張性のある仕組みなのでVJシステムや様々な人の触る可能性のあるコンテンツを作っている方々はぜひ一度検証してみてください!
Ref
公式Wiki
サンプルファイルとか
2019年の記事
タグって地味だけど便利なんだよーって記事