【DaVinci Resolve】コントロール変更時の処理の設定
まえがき
こんにちは。火注ゆかなです。
今回はコントロールの値を変更した場合の処理を設定する方法についてです。
We Suck Less フォーラムで言及されていました。
コントロールの値が変更された時に特定の処理をするというのはこれまで Fuse (自作プラグイン)でしか実装できませんでしたが、これからはユーザーコントロールだけでなくText+
などの既存ノードのコントロールにも設定できるようになりました。
これにより、できることの幅がグッと広がる……と思いきや、いざ検証してみると思ったより使い勝手が悪いです。特にアニメーションには使用できないので使いどころに悩みます。
そういう注意点も踏まえて使い方の説明をしていきます。
コントロール変更時の処理設定
DaVinci Resolve ver.20.0 からコントロールの属性にINPS_ExecuteOnChange
が追加されました。
例えば、Fusion
ページで適当にTextPlus
ノードを配置し、ノードを選択した状態でコンソールで==ActiveTool.StyledText:GetAttrs()
を実行すると属性一覧を確認できます。
バージョン 20.0 からはコントロール属性にINPS_ExecuteOnChange
が確認できる
INPS_ExecuteOnChange
属性にスクリプトをリテラル文字列で設定すると該当コントロール変更時に処理が実行されますが、デフォルトでは何も設定されていません。
設定方法
ひとまず、試しに設定してみましょう。
-
TextPlus
ノードを選択した状態で右クリックしてコンテキストメニューを表示→コントロールを編集
をクリックしてコントロールを編集画面
を表示します。
- 適当なユーザーコントロールを追加します。とりあえず今回は
ID
をAddControl
、入力
をSliderControl
にします。 - 中央下に
On Change
という欄があるので、そこに下記スクリプトをコピペして保存してください。
On Change
欄にコントロール変更時に実行したいスクリプトを記述
tool:SetInput("StyledText", self.ID.." : "..self[0])) -- 表示テキスト変更
これでAddControl
コントロールを変更すると、その値に連動してStyledText
コントロールのテキストが変更されるようになりました。
AddControl
と連動してテキストが更新されるようになった
self
とtool
について
変数self
はコントロール自身、tool
はコントロールが所属するノードを参照する変数です。
例えば、先ほど実行したコマンドだとself
はAddControl
コントロール、tool
はTextPlus
ノードを参照しています。
tool:SetInput("StyledText", self.ID.." : "..self[0]) -- 表示テキスト変更
現在の再生フレームに応じた処理をしたい場合
キーフレームアニメーションを設定している場合、現在の再生フレームに応じた値を参照して処理したいときもあるでしょう。
現在の再生フレームはcomp.CurrentTime
で参照できます。キーフレームアニメーションが未設定なら0
を指定した場合と同じ挙動になります。(現在のコントロール値が表示される)
例えば、下記のスクリプトでは再生フレームに応じてテキストとコントロールのラベルを更新します。
tool:SetInput("StyledText", self.ID.." : "..self[comp.CurrentTime]) -- 表示テキスト変更
self:SetAttrs({INPS_Name=tostring(self[comp.CurrentTime])}) -- ラベルを再生フレーム番号に変更する
AddControl
の再生中の値でテキストとラベル更新
ローカル変数とグローバル変数の使用、ログ出力
local
を付けて変数を宣言すればローカル変数を使用することもできるため、複雑な処理も記述しやすいです。
下記のようにprint()
dump()
関数も使えるのでコンソールへログ出力して動作確認も可能です。
local v = 100
print("ローカル変数1:".. v)
v = v + 20
print("ローカル変数2:".. v)
ローカル変数の実行結果
変数にlocal
を付けない場合、コンソールからでも参照・操作が可能なグローバル変数として扱われます。
print("グローバル変数:".. v)
変数v
は宣言していないのにコンソールで代入した値が表示された
他のコントロールだけでなく他のノードからもアクセスできてしまうため、誤動作の要因となりやすいことを考えると推奨できません。基本的にはlocal
を付けてスコープを制限するべきでしょう。
どうしても同じノード内で値を共有したい場合、tool:SetData(key, value)
tool:GetData(key)
を使用しましょう。ノードにキーを指定して値を保存・読み込みすることが可能です。
local key = "ValueName" -- "Name1.name2"という風に "."(半角ドット)で区切れば入れ子の連想配列としても扱えます
tool:SetData(key, value) -- ノードへの値の保存
tool:GetData(key) -- ノードに保存した値の読み込み(キーが存在しない場合は nil を返却)
テキストエディタで記述する
On Change
欄はとても小さいので複雑な処理を記述するのには不向きです。
これだけならテキストエディタで編集したスクリプトをコピペするだけで良いのですが、コントロールを編集
画面で編集すると、編集されたコントロールがページの一番下に移動してしまうというデメリットがあります。
ノードをテキストエディタにコピペしてからINPS_ExecuteOnChange
属性を追記すればユーザコントロールの並び順を変更せずに設定・編集が可能になりますし、長い処理を記述する際も視認性が良くなります。
{
Tools = ordered() {
Text1 = TextPlus {
CtrlWZoom = false,
Inputs = {
GlobalOut = Input { Value = 995, },
Width = Input { Value = 1920, },
Height = Input { Value = 1080, },
UseFrameFormatSettings = Input { Value = 1, },
["Gamut.SLogVersion"] = Input { Value = FuID { "SLog2" }, },
Wrap = Input { Value = 1, },
LayoutRotation = Input { Value = 1, },
TransformRotation = Input { Value = 1, },
Softness1 = Input { Value = 1, },
StyledText = Input { Value = "AddControl : 0", },
Font = Input { Value = "Open Sans", },
Style = Input { Value = "Bold", },
VerticalJustificationNew = Input { Value = 3, },
HorizontalJustificationNew = Input { Value = 3, }
},
ViewInfo = OperatorInfo { Pos = { 275, 82.5 } },
UserControls = ordered() { AddControl = {
LINKS_Name = "Addcontrol",
LINKID_DataType = "Number",
INPID_InputControl = "SliderControl",
INP_Default = 0,
INP_Integer = false,
INP_MinScale = 0,
INP_MaxScale = 1,
INP_MinAllowed = -1000000,
INP_MaxAllowed = 1000000,
INP_SplineType = "Default",
INPS_ExecuteOnChange = [[ -- 属性追記
local v = 100
print("ローカル変数1:".. v)
v = v + 20
print("ローカル変数2:".. v)
]]
}
}
}
},
ActiveTool = "Text1"
}
エクスプレッションとの違い
他のコントロールの値と連動させる機能としては既にエクスプレッション
が存在します。
エクスプレッションでは属性値を参照できないなどの制限があるため、INPS_ExecuteOnChange
の方がちゃんとしたLua
で記述できる分、自由度は段違いです。
ではINPS_ExecuteOnChange
属性はエクスプレッションの上位互換だから、エクスプレッションはもう不要で、これからはINPS_ExecuteOnChange
属性に全て置き換えるべきなのでしょうか?
いいえ、エクスプレッションはINPS_ExecuteOnChange
属性に完全に置き換えることはできません。INPS_ExecuteOnChange
属性はアニメーションには使えないからです。
アニメーションには使えない
先ほどの例ではINPS_ExecuteOnChange
でコントロールの値を変更していましたが、結果的にアニメーションには使えません。
INPS_ExecuteOnChange
に設定したスクリプトはレンダリング時に動作していないようです。 なんならインスペクタ欄で該当コントロールを表示していないだけでも動作しないことがあります。
例えば、AddControl
のINPS_ExecuteOnChange
属性に下記のスクリプトを設定します。
tool:SetInput("StyledText", self.ID.." : "..self[comp.CurrentTime]) -- 表示テキスト変更
self:SetAttrs({INPS_Name=tostring(self[comp.CurrentTime])}) -- ラベルを再生フレーム番号に変更する
これをレンダリングすれば再生時間に応じて0
から1
へ徐々に数値が増加していくはずですが、レンダリングしても0
のまま変化しません。(正確には最後に表示した値がそのままとなる)
アニメーションに利用できない以上、エクスプレッションの代替はできませんね。
Edit
ページやFusion
ページでは動いてるから大丈夫! と思っていてもいざレンダリングすると動かないなんてことを防ぐためにも INPS_ExecuteOnChange
を設定するコントロールにはキースプライン(アニメーション)やエクスプレッションの設定はしない方が無難かと思います。
他のコントロール操作を効率化できる?
アニメーションには利用できませんが、一つのコントロールから他のコントロールを操作する場合には効率化できるかもしれません。
手動操作との併用
図のようにコントロール同士で値の連動をさせたいことがあります。
コントロール間の値の連動はエクスプレッションがありますが、エクスプレッションが設定されたコントロールは値の手動変更ができません。
(正確には手動変更した直後に即エクスプレッションで設定された値で上書きされる?)
また、コントロールA を参照するコントロールが複数存在する場合、エクスプレッションで連動させようとすると コントロールB,C,D......といった連動させたいコントロール全てにエクスプレッションを設定する必要がありました。
INPS_ExecuteOnChange
ならコントロール A 変更時にコントロール B,C,D を操作しつつ、手動でも操作することができますし、コントロールA のINPS_ExecuteOnChange
で対象コントロールを一括操作するようにすれば編集箇所をまとめることができます。
常時アニメーションさせる必要はないけど、複数のコントロールの値をまとめて変更したいときもある……という場合に向いてるかもしれません。
例えば、複数のコントロールの値セット①②③~と複数保存しておいて、コンボボックスを①に変更したらデータセット①を初期値として読み込む……みたいなセーブ・ロード機能みたいなのとか。
(でもこれは今までもButtonControl
と組み合わせれば普通にできたし、そっちの方が安全だからやるべきではないかも……)
データセットを変更するとコントロール 1~3 の初期値が読み込まれる
ただし、エクスプレッションと違ってどのコントロールにINPS_ExecuteOnChange
が設定されているのか、どのコントロールがINPS_ExecuteOnChange
で変更されるのか一目ではわからないため、管理の仕方に注意する必要があります。
後述するようにラベルをHTML
で記述することで文字色を変更して可視化するのも良いかもしれません。
それからINPS_ExecuteOnChange
の応用として、コントロール A と B を相互に変更するようなことも可能ではあるでしょう。でも推奨はしません。
A を更新→ B を更新→ A を更新→……という無限ループが発生しかねないのでフラグ管理をしっかりする必要がありますし、処理が複雑になる上に意図せぬ挙動をしやすくなるだろうことは想像に難くないからです。コントロール間の主従関係はシンプルにするべきです。
推奨はしませんが後々必要になることもあるかもしれないので、一応書き残しておきます。
属性の一時変更
エクスプレッションは値の変更しかできませんが、INPS_ExecuteOnChange
ならInput:SetAttrs()
で属性を一時的に変更することができます。
パッと思いつくところでは、以前紹介したインスペクタ欄に画像を表示する場合です。
コントロールのラベルはHTML
を記述できることを利用して画像を表示するというテクニックですが、エクスプレッションでは属性値を変更できないので Fuse 以外はコントロールの値に合わせてラベルの動的な書き換えができないという問題がありました。
INPS_ExecuteOnChange
ならInput:SetAttrs({INPS_Name=[[HTML文]]})
という風にINPS_Name
属性を書き換えられるのでこの問題を解決できます。
AddControl
の値が0.5以上ならHTML
を書き換えて画像表示、そうでないなら非表示に切り替えるようなスクリプトを設定すると下記のようになります。
{
Tools = ordered() {
Text1 = TextPlus {
CtrlWZoom = false,
Inputs = {
GlobalOut = Input { Value = 995, },
Width = Input { Value = 1920, },
Height = Input { Value = 1080, },
UseFrameFormatSettings = Input { Value = 1, },
["Gamut.SLogVersion"] = Input { Value = FuID { "SLog2" }, },
Wrap = Input { Value = 1, },
LayoutRotation = Input { Value = 1, },
TransformRotation = Input { Value = 1, },
Softness1 = Input { Value = 1, },
StyledText = Input { Value = "AddControl : 0.069346733668342", },
Font = Input { Value = "Open Sans", },
Style = Input { Value = "Bold", },
Size = Input { Value = 0.2677, },
VerticalJustificationNew = Input { Value = 3, },
HorizontalJustificationNew = Input { Value = 3, },
AddControl = Input { Value = 0.2677, }
},
ViewInfo = OperatorInfo { Pos = { 165, 49.5 } },
UserControls = ordered() {
AddControl = {
LINKS_Name = "0.069346733668342",
LINKID_DataType = "Number",
INPID_InputControl = "SliderControl",
INP_Default = 0,
INP_Integer = false,
INP_MinScale = 0,
INP_MaxScale = 1,
INP_MinAllowed = -1000000,
INP_MaxAllowed = 1000000,
INP_SplineType = "Default",
INPS_ExecuteOnChange = [[
-- 値が0.5以上なら画像表示
if self[comp.CurrentTime] >= 0.5 then
tool.HTMLLabel:SetAttrs({INPS_Name=[=[<img src="I:/Image.png" width="100">]=]}) -- 画像表示
else
tool.HTMLLabel:SetAttrs({INPS_Name=[=[<span style="color:red;">画像非表示</span>]=]}) -- リテラル文字列の入れ子になるので [=[ ]=] と言う風に括弧の間に = を挟む
end
]],
},
HTMLLabel = { -- 画像表示用ラベルコントロール
LINKS_Name = [[<span style="color:red;">ラベル</span>]],
LINKID_DataType = "Number",
INPID_InputControl = "LabelControl",
INP_Integer = false,
INP_SplineType = "Default",
LBLC_DropDownButton = false,
INP_External = false,
LBLC_MultiLine = true,
IC_NoLabel = true, -- ←ラベル非表示
},
}
}
},
ActiveTool = "Text1"
}
値が 0.5 以上なので画像非表示
値が 0.5 未満なので画像非表示
HTML
なら文字色の変更などもできますが、HTML
が動作するのはユーザコントロールのみで既存のコントロール(TextPlus
ノードならStyledText
やSize
等)だとHTML
が上手く動作しないようです。
下記のようにSize
コントロールのラベル文字色を変更しようとしても素のHTML
文がそのまま表示されます。
tool.Size:SetAttrs({INPS_Name=[=[<span style="color:red;">サイズ</span>]=]}) -- リテラル文字列の入れ子になるので [=[ ]=] と言う風に括弧の間に = を挟む
ラベル文字色を変えるはずの HTML が反映されない
HTML
で文字色を変更したりしたいならユーザコントロールを利用しましょう。
余談:エクスプレッションについて
今回の記事を書くためにエクスプレッションについて調べ直したので、そのあたりをちょっと書いておきます。
マルチステートメントで複数のコントロールの値変更は可能(非推奨)
マルチステートメントに関してはこちらを参考にさせていただきました。
エクスプレッションってマルチステートメント(1行に複数の命令を記述)ができるんですね。エクスプレッションは最初の頃に少し触れたっきりだったので全く知りませんでした。
で、マルチステートメントを使えば一つのコントロールのエクスプレッションで複数コントロールの値を一括変更することもできないかなって試してみました。
イメージとしてはこんな感じですね。
:
Size = AddControl * 10
CharacterSpacingClone = AddControl
LineSpacingClone = AddControl
return AddControl * 10
試してみたらできたにはできたんですが……なんか挙動がおかしいのでやらない方が良いですね。
再生するとコントロールの値は変更されているんですが、ユーザコントロールに設定するとレンダリングに反映されなかったり、ユーザコントロールじゃなくても手動で弄ると実際の値とレンダリング結果が一致しなくなったりして何がなにやら。
変なことせず、1つのエクスプレッションで変更するのは設定コントロール一つだけにするべきですね。
エクスプレッションで if 文が使えた
エクスプレッションってif
文で条件分岐できるんですね。条件分岐はiif()
関数しか使えないものかと思っていましたが、なんとなく試してみたら普通に動いてビックリしました。
:
v = 0
-- 指定のコントロールの値が0.5以上なら1、0.5未満なら0に設定
if AddControl >= 0.5 then
print("big")
v = 1
elseif AddControl == 0 then
print("zero")
else
print("less")
end
return v
他所様の記事では「条件分岐はif
じゃなくてiif
だよー」って書いてあるところばかりなので、どこかでシレっと追加されたんですかね?
まあ条件分岐処理が書きやすくなったならいつからでも構いませんけれども。
あとがき
INPS_ExecuteOnChange
についての説明は以上となります。
Fuse のNotifyChanged
イベントをイメージしていたのですが、思ったより機能が制限されてて使いづらいなぁという印象ですね。
今年は暑くなるのが早いので大変だと思いますが、皆さん体調管理にお気を付けて。
冷房はケチらず使いましょうね。節約するのも良いことですが、冷房ケチって熱中症になったら節約した電気代以上の医療費が飛んでいきますし、最悪死んじゃいますからね。
それでは、今回の内容が皆さんのお役に立てば幸いです。
Discussion
すごくいいことを聞きました、ありがとうございます👍
ComboControlとかは選択したときに処理をしたいとずっと思ってたので色々できそうです🤔
色々試してみますー
これはシングルステートメントだとiifじゃないと戻り値返せないのでiifじゃないとだめだよーって言われてるんだと思います
ComboControl との組み合わせについては間違って変更しちゃいやすいかなと思いましたが、一々ボタン押さずに済むのでサクサク変更できるのは確かに良いかもしれません。
なるほど、if 文に関しては以前から使えたけど、そもそも前提条件としてのマルチステートメント自体が知られてなかったってことなんですかね。