LoginSignup
7
2

More than 1 year has passed since last update.

#TouchDesigner でのドラッグ&ドロップについて ver.2022

Last updated at Posted at 2022-12-19

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を使用することができます

image.png

  • When Dragging This
  • On Dropping into

という二つのパラメータをUse Drag/Drop Callbacksに変更します
そして、Drag/Drop Callbacksの右側にあるAddをクリックすることでデフォルトのD&D Callbackが作成されPanelCOMPにDockされます

image.png

ドラッグ可能なパネルの設定

まずはドラッグ操作を拡張させるための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の値をパラメータで使ったり、複数のパラメータをリンクさせるために行っている普段からやっているシンプルな操作になります

こういうの
Desktop 2022.12.19 - 23.16.09.02 - frame at 0m9s.jpg

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

onHoverStartGetAcceptdragItems に対して True を返した場合にのみ呼び出されます

comp - ドロップされたパネル
info['dragItems'] - コンポーネント上にドラッグされているオブジェクトのリスト

返り値
この関数からアイテムのDictを返すことはオプションですが、ドラッグ ソースとドロップ ターゲット間の相互にやり取りができます
Dictには、説明的なキーを含む結果が含まれている必要があります
次のキーと値のペアは標準であり、可能な場合は以下の例に従いましょう

'createdOPs':[<list of created ops in order of corresponding drag item>]

どの関数にもデフォルトでDebug()でその関数の情報などがプリントできるようにかかれているので、まず始めはその部分のコメントアウトを解除して確認してみましょう

実践

image.png

今回作成するものはUIからD&Dすることで自分でカスタマイズしたUIをネットワークエディタに複製するというものです

image.png

まずは適当にPanelCOMPでいい感じにUIのパーツとなるコンポーネントを組んでおいてください (Widgetでいいだろとか言わないで)

検索対象にするために作ったコンポーネントにタグをつけます
image.png
今回はmy-panelというタグをつけました

次にそれらのタグのついたオペレータをOP FindDATを用いてすべて取得します
image.png

OPFind DATのパラメータのFilter/Tagsmy-panel(設定したTag)を打ち込みます
ついでに並び順を楽に決められるようにオペレータの置いてある位置を取っておきましょう
OPFind DATのコールバック関数に以下のプログラムを書きます

def onInitGetColumnNames(dat):
	return ['nodeY']

def onFindOPGetValues(dat, curOp, row):
	return [curOp.nodeY]

コールバック関数によってOPFind DATで見つけられた要素に対して追加の情報をTableに入れることが可能となります
image.png
取得したNodeYを基準にSortDATで並べ替えを行います

image.png
この処理をすることによりネットワークエディタ上での並び順をそのまま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にはコンポーネント名とプレビューを出しています

image.png
それぞれのCOMPではDrag/Dropの設定は行っておらず、親の設定を使用する設定を行っています
これにより、一か所でスクリプトを管理することができます
親の設定を使用する場合は一番近い親の設定が使用されます
また共通のスクリプトであったとしても、取れる引数はパネルに応じて来るためしっかりと区別することも可能です

終わりに

このような形でドラッグ&ドロップの仕組みをざっくり解説しました
用途に応じてかなり拡張性のある仕組みなのでVJシステムや様々な人の触る可能性のあるコンテンツを作っている方々はぜひ一度検証してみてください!

Ref

公式Wiki

サンプルファイルとか

2019年の記事

タグって地味だけど便利なんだよーって記事

7
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2