TouchDesigner Advent Calendar 2017 9日目の記事です。よろしくお願いします。
はじめに
TouchDesigner(以下Touch)で作業していると、人それぞれここはPython(C++かも)でやったほうがいいなと思う箇所が出て来ると思います。俺の場合はDesigner Mode(新規ファイルを作ると出ている画面)でoperatorのサイズを揃えたり等間隔で置いたりといった調整が手動でやりにくいと感じ、そのストレス軽減が最初の目的でした。また、Touchをちょっと触ってみたくらいの時に市原湖畔美術館でのCarsten Nicolaiの展示で作品の転換時に画面が見えて、あ、これTouchDesignerなんだ〜かっこいい〜と思ったのと、ライブパフォーマンスの際にDesigner Modeを見せちゃう人とかもいると知ったことも色々調べるきっかけとなりました。
この記事での座標、サイズ指定などは全てDesigner Mode上のものですので、Perform Modeでの表示には影響しません。そんなわけで以下ご紹介する内容は地味だったり無意味だったりしますが、TouchでのPythonの使い方を理解する一助となると思います。
Touchでの基本的なPythonの使い方は同一Advent CalendarのTouchDesignerでのPython入門(基礎編)、TouchDesignerでのPython入門(応用編)で丁寧に書かれているのでそちらを参考にしてください。俺もPythonが超得意ってわけではないのでもっと効率的な書き方などありましたらツッコミ頂けるとうれしいです。
サンプルファイル
githubにアップしましたので必要に応じて参照してください。
https://github.com/chimanaco/touchdesigner-advent-calendar-2017
対象となるoperatorの指定
まず、対象となるoperatorを指定するには以下のものがあります。
名前で指定
以下のコードではmoviefilein1
というファイル名のoperatorが対象となります。
o = op('moviefilein1')
// your code
同一階層のoperator全部
以下のコードでは実行するスクリプトと同一階層にある全てのoperatorが対象となります。
c = parent().children
for o in c:
// your code
名前に文字列を含むoperatorを指定
以下のコードではmoviefilein
という文字列を含むoperatorが対象となります。
c = parent().children
for o in c:
if "moviefilein" in o.name:
// your code
familyで指定
以下のコードではTOP family
のoperatorが対象となります。
c = parent().children
for o in c:
if(o.family == 'TOP'):
// your code
labelで指定
以下のコードではMovie File In
のoperatorが対象となります。
c = parent().children
for o in c:
if(o.label == 'Movie File In'):
// your code
typeで指定
以下のコードではmoviefilein type
のoperatorが対象となります。label
との使い分けがよく分かっていません。
c = parent().children
for o in c:
if(o.type == 'moviefilein'):
// your code
位置の指定
座標を指定して配置します。
以下のコードではmoviefilein1
operatorの座標を取得し、左揃えでmoviefilein2
operatorの位置を指定します。
marginY = 20
o1 = op('moviefilein1')
x1 = o1.nodeX
y1 = o1.nodeY
o2 = op('moviefilein2')
o2.nodeX = x1
o2.nodeY = y1 - o1.nodeHeight - marginY
ちなみに Edit
-> Preferences
-> Network
の下の方の設定でグリッドにスナップができるようになることに最近気づきました。
サイズのリセット
拡大縮小したり他所からコピペしたoperatorを手動でうまくサイズ調整することができなかったため、リセットボタンを作りました。
例えばmoviefilein1
というoperatorは以下のコードでリセットできます。ドラッグでリサイズする時にもグリッドが出てくれるとありがたいんですが出ないのかしら…。
op('moviefilein1').resetNodeSize()
サイズを指定する
設定する値の単位はピクセル?Help
→Python Example
→ui_examples
を見てみると1 pixel = 1 tile unit
とあるんですが1 tile
って何でしょうね。
以下のコードではmoviefilein1
operatorの幅を500
、高さを50
に指定します。
o = op('moviefilein1')
o.nodeWidth = 500
o.nodeHeight = 50
テストする時には、いじっては戻しいじっては戻しを繰り返したいのでサイズ変更ボタンとリセットボタンを並べて置いています。Python実行だとUndo(Ctrl+z
)が効かないので。
複数operatorのサイズを更新する
以下のコードでは幅120
の16
個のConstantTop
のoperatorを作成、横並びに配置します。
w = 120
for i in range(0, 16):
o = parent().create(constantTOP)
# size
o.nodeWidth = w
# position
o.nodeX = xPos * i
以下のコードでは複数のConstantTop
のoperatorの高さをPosition
という名前のTable
から取得して更新に使用しています。Position
の値を定期的に変更するとアニメーションになります。この場合作成したoperatorを絞り込む条件としてo.label=='Constant'
とo.family=='TOP'
を指定していますが、同一階層上に既存のConstantTop
operatorがあった場合それらにも影響してしまいますので条件に注意してください。
c = parent().children
index = 0
for o in c:
if o.label=='Constant' and o.family=='TOP':
# size
cell = op('Position')[index, 0].val
height = int(float(cell))
o.nodeHeight = height
index += 1
サンプルではnoise
で高さをコントロールしていますが、サウンドリアクティブにしたりセンサーから取得した値を使用したりするともっと楽しくなると思います。
operatorを形に沿って配置する
Pythonで座標を計算して図形を描く場合と、SOPの頂点座標を使用する2パターンやってみました。
Pythonで座標を計算する
以下のコードではoperatorを螺線形に配置します。
radius = 0
for i in range(0, 200):
radius += i * 0.1
addOpSpiral(i, total, radius)
def addOpSpiral(index, total, radius):
angleSpeed = 5
o = parent().create(constantTOP)
# size
setOpSize(o, 120, 67.5)
# positon
angle = toRadians(index * 360 / total * angleSpeed)
x = math.cos(angle) * radius
y = math.sin(angle) * radius
setOpPosition(o, x, y)
return
# Common utilities
def setOpSize(op, w, h):
op.nodeWidth = w
op.nodeHeight = h
return
def setOpPosition(op, x, y):
op.nodeX = x
op.nodeY = y
return
def toRadians(angle):
return angle * (math.pi / 180)
ここでは螺旋形に配置していますが、興味ある方は好きな形に配置してみてください。
SOPの頂点座標を使用する
FontSOP
→ResampleSOP
→SOPtoCHOP
→MathCHOP
→ChoptoDAT
という流れでテキストの頂点データをPosition
と名付けたTableDAT
にインプットし、以下のコードでoperatorの座標に指定します。
c = parent().children
index = 0
for o in c:
if o.label=='Constant' and o.family=='TOP':
# position
x = op('Position')[index, 0].val
y = op('Position')[index, 1].val
setOpPosition(o, x, y)
サンプルではSequence BlendSOP
を使用して元となるテキストを切り替えています。
ネストされたoperatorを追加する
Replicatorを使うのがTouchらしいやり方な気がしますが、Pythonでも同じようなことができます。operatorを追加していくだけですが、connectメソッドでoperator同士をつなげられるところが好きです。
以下のコードはcontainerCOMP
を追加、その中にconstant
とout
の二つのTOP
を追加し、outTOP
からの出力を予め配置しておいたswitch1
につなげています。
w = 240
h = 135
yPos = -(h + 10)
total = 5
for i in range(0, total):
# add container
c = parent().create(containerCOMP)
# size
c.nodeWidth = w
c.nodeHeight = h
# position
c.nodeY = yPos * i
# add op to container
addOpToContainer(c, i, total)
# connect
c.outputConnectors[0].connect(op('switch1'))
def addOpToContainer(op, index, total):
# add
t1 = op.create(constantTOP)
t2 = op.create(outTOP)
t2.nodeX = 240
# connect
t1.outputConnectors[0].connect(t2)
return
Designer Modeでズームする
こちらは実際にoperatorを操作するわけではないのですが、関連ということで。
pane
のzoom
の数値を指定することでDesigner Modeのズーム具合を指定できます。
以下のコードは画面の中心(座標0,0)にある幅240
、高さ130
のoperatorを中心にズームする場合です。
p = ui.panes.current # 現在のpaneを指定
p.zoom = 1 #8くらいでフルスクリーン
p.x = 120 # ターゲットにしたいoperatorのwidth/2
p.y = 67.5 # ターゲットにしたいoperatorのheight/2
zoom
の値を定期的に変更することでアニメーションになります。サンプルは拡大のみですが、一覧表示時、縮小時などにアニメーションをつけたり画面を覆うタイミングでPerform Modeに切り替えたりするなどして作り込むとプレゼンテーションに使えたり、Carsten Nicolaiの展示のような動き(一覧を表示→次の作品にフォーカス→次の作品をフルスクリーンで表示、を繰り返す)も作れます。サンプルのようにズームさせていると各operatorに触りにくいので停止用ショートカットを設定するとか、MIDIコントローラーに停止ボタンを設定しておくなどしたほうが好いかもしれないです。
うまくいかなかったこと
op.activeViewer = 1
という設定があってoperatorのviewerを全域使うためにこれを使用しているのですが、 operatorを作ったすぐのタイミングでは設定できないことが多かったので、アニメーションとして定期的に呼ぶメソッドに設定を入れています。operatorがcookされる前に設定しようとしているからダメだとかかしら。
おまけ
俺得のoperatorのresize、reset、locate、destroyができるtoxを作りました。
https://github.com/chimanaco/touchdesigner-tox にあるOperator_adjust.tox というやつです。確認メッセージは未実装でボタンを押したら即実行されてundoできないので、試される方おられたら現状destroyは注意が必要です。
最後に
お読み頂きありがとうございました。他にもデフォルトのoperatorのサイズがCOMP > TOP > CHOP = SOP = MAT = DAT となっているのとかも気になるところですが、Designer Modeをライブコーディングで使ってみたいのでもっと楽しく魅せる編集画面を目指します(その割にサンプルファイル美しくないですごめんなさい)。
明日は@ToyoshiMoriokaさんの登場です!
引き続きこの後も盛り沢山なTouchDesigner Advent Calendar 2017を楽しんでいきましょう!