🐙

DaVinci FusionのOn Changeと実行の挙動についてあれこれ

に公開
4

マクロ化すると2回以上実行される

print(tool.Name)

と書いて保存します。

toolは変更が行われたノードを指してくれる変数で、.Nameはその名前です。
マクロにするとき、このコントロールを操作できるように指定して、操作できるマクロを作ったとします。

そしてコントロールを操作すると、マクロからでも、もとのノードからでも、どちらから操作してもprintが2回実行されます
オリジナルのノード名とマクロのノード名が表示されると思います。

このようにtoolが変わって2度実行される挙動になっていることを知っておくとよいでしょう。
なお、ButtonExecuteからは2回実行されないようです。

対策

これはスクリプトの書かれた大本のコントロールからの実行である、といった情報の取得方法はたぶんないと思います。これができない以上はどの対策も限定的なものになります。
なんらかの特徴を紐付けて判定するしかないでしょう。

もしノード名で判定するならtool.Name:gsub("[_%d]+$", "")とすれば末尾のアンダーバーと数字を除いた文字列を取得できます。

一番の対策は可能な限り、2回実行されても影響がないようにすることです。

※追記:対策としてActiveToolも使えます。親子関係を辿る方法も良さそうです。コメントで教えてもらいました!

自動リネームされない

エクスプレッションはリネームされます。

コピー後↓↓

このとき、5_1のボタンの実行にprint(Transform5:GetInput("Size"))と書いていました。コピー先の5_1_1を見ても変更されていませんでした。

対策

追跡用コントロールを作りエクスプレッションから取得するのが確実だと思います。
上記の例でもエクスプレッションはリネームされていたので、その性質を使おうということですね。
※追記、下記のスクリプトの冒頭2行は、読み取り専用のTextEditControlを使う方法に変更したほうが良さそうです。コメント欄にて教えてもらいました。

local expression_str = tool.Track:GetExpression() -- なんでもよいので追跡用のパラメーターを用意しておく
local nodeName = string.match(expression_str, "^([^.]+)") -- ピリオドより左側を取得 
local node = FindTool(nodeName)
print(node.Name)


コピー前に実行し、コピー後に新しくコピーしたノードで実行しました。コピー先を追跡できていますね。

ほかにもGetChildrenList()で子の一覧を取得してフィルタリングしたり、GetConnectedOutput()で前後を辿るなどの、あれやこれやといった方法もありますが、確実性に欠けます。

Pythonも書ける

スクリプトの先頭に!Py3: (←半角スペースも必須)と入力することでスクリプトをPythonを書くことができます。参考記事:【DaVinci Resolve】AIを使用してButtonControlのExecuteに設定するスクリプトを作る話

1000文字の制限

1000文字までしか入力させてくれません。ちょっと長めのものを書こうとするとすぐに制限が来てしまいます。

対策

文字数を抑える対策です。

  • タブをスペースからインデントにする
  • AIにコードを圧縮してもらう

これに対して、根本的に記述場所を変えるという方法があります。

!Py3: import sys
import os
import importlib

# ホームディレクトリを動的に取得(オプション)
home_dir = os.path.expanduser('~')
script_dir = os.path.join(home_dir, r'davinci_python') # 自分のpythonを置いてある場所
if script_dir not in sys.path:
    sys.path.append(script_dir)

# インポートして使用できる
import python_filename
python_filename.func_name(self, tool, comp)

配布する予定などがある場合は、コントロールに記述して読み込ませる方法がよさそうです。

Discussion

MugMug

2回実行される問題は、操作してる側のツールのときは~ってするのが良さそうかなって思いました
これでいつもうまくいくかどうかは未確認です💦

comp.ActiveTool.Name == tool.Name
kcdlrkcdlr

ありがとうございます!
そういえばセカイルさんの記事を見た記憶がなんとなくあって
それで記事にしたほうがいいという気がして書いたのでした。
改めて確認もしてきました。
いろいろと思い出せてよかったです🙌

※追記
マクロの内部にマクロがある、という場合、3回実行されるのですが、その際は
アクティブマクロ、非アクティブマクロ、非アクティブノードという並びになります。
1回だけ実行、という条件なら問題なかったのですが、
例えば非アクティブマクロにおいてパススルーをONにしたい、という処理をしたい場合には
また対応が難しいかもしれません。

火注ゆかな火注ゆかな

toolがマクロではない場合のみ実行できれば良いということであれば、tool.IDプロパティが"MacroOperator"かどうかで判別すればマクロの多重構造にも対応できます。
(グループの場合は"GroupOperator"

if tool.ID ~= "MacroOperator" then  -- マクロでない場合のみ実施
    print(tool.Name .. "." .. self.Name)
end

あとはtool.ParentToolプロパティは自身が所属するマクロもしくはグループを返却するのでtool.ParentTool == nilという条件なら多重構造マクロの一番外側(最上位)だけ処理する、あるいはtool.ParentToolで親マクロを辿って自身が上から何層目のノード・マクロか判別するなどもできそうです。
追記における非アクティブマクロのみパススルーONにする処理であれば、ノード本体を直接含むマクロだけを対象にすれば可能かと思います。

if tool.ID ~= "MacroOperator" then  -- マクロでない場合
    parent = tool.ParentTool    -- ノードの一つ上の親マクロ or グループ取得(繰り返せば指定分だけ上位のマクロ取得可能)
    parent:SetAttrs({TOOLB_PassThrough = true})    -- パススルーON
end

また、自動リネームされない仕様への対策案ですが、追跡用コントロールをTextEditControlにして追跡対象ノード.Nameというエクスプレッションを追記すればノード名を直接取得できるようになり、正規表現が不要な分だけ少しシンプルにできそうです。

nodename = tool:GetInput("AddControl")
kcdlrkcdlr

火注ゆかなさん、コメントいただきありがとうございます。
個人的に改めてDaVinci Resolveに挑戦するにあたり、Mugさんとゆかなさんのお二人の発信内容がとても参考になりました。カスタマイズの過程でソフトに慣れていくタイプの人間のため 助かりました。
改めて感謝申し上げます。
実践で使用をしていたため コメント返信が遅れました。
ーーー
追跡対象を追うために読み取り専用のTextEditControlを使うのは最適と言えそうですね!ありがとうございます。

親子関係を辿るのは良い妥協点になりそうですね。特に下から数えれば、状態は安定しそうです。上から数えると、親子関係の深さによって参照が変わってしまいますからね。下から数えれば、うっかり状態によって参照できなくなるケースも起きづらいように思います。

発火したコントロールが大本の記述対象だったかどうかを明示的に得られるのが、その他の状態に依存しなくていいんですが、やはり難しそうでした。
INPS_ExecuteOnChangeがあるかどうかを追うというのもやってみましたがコピーされていますね…

-- 自身の "INPS_ExecuteOnChange" 属性を取得
local my_attribute = self:GetAttrs()

print("--- 属性チェック開始 ---")
print("実行元コントロール: " .. self.Name .. " (" .. tool.Name .. ")")

if my_attribute and my_attribute.INPS_ExecuteOnChange then
  print("結果: INPS_ExecuteOnChange 属性は存在します。")
  print("内容:")
  -- dump() を使うとテーブル構造が分かりやすく表示される
  dump(my_attribute.INPS_ExecuteOnChange)
else
  print("結果: INPS_ExecuteOnChange 属性は存在しません (nil または空です)。")
  dump(attribute)
end

print("--- 属性チェック終了 ---")

出力:

--- 属性チェック開始 ---
実行元コントロール: EnableOut (Switch1)
結果: INPS_ExecuteOnChange 属性は存在します。
内容:
-- 自身の "INPS_ExecuteOnChange" 属性を取得
local my_attribute = self:GetAttrs()


print("--- 属性チェック開始 ---")
print("実行元コントロール: " .. self.Name .. " (" .. tool.Name .. ")")


if my_attribute and my_attribute.INPS_ExecuteOnChange then
  print("結果: INPS_ExecuteOnChange 属性は存在します。")
  print("内容:")
  -- dump() を使うとテーブル構造が分かりやすく表示される
  dump(my_attribute.INPS_ExecuteOnChange)
else
  print("結果: INPS_ExecuteOnChange 属性は存在しません (nil または空です)。")
  dump(attribute)
end


print("--- 属性チェック終了 ---")
--- 属性チェック終了 ---
--- 属性チェック開始 ---
実行元コントロール: EnableOut (ScaleInOut)
結果: INPS_ExecuteOnChange 属性は存在します。
内容:
-- 自身の "INPS_ExecuteOnChange" 属性を取得
local my_attribute = self:GetAttrs()


print("--- 属性チェック開始 ---")
print("実行元コントロール: " .. self.Name .. " (" .. tool.Name .. ")")


if my_attribute and my_attribute.INPS_ExecuteOnChange then
  print("結果: INPS_ExecuteOnChange 属性は存在します。")
  print("内容:")
  -- dump() を使うとテーブル構造が分かりやすく表示される
  dump(my_attribute.INPS_ExecuteOnChange)
else
  print("結果: INPS_ExecuteOnChange 属性は存在しません (nil または空です)。")
  dump(attribute)
end


print("--- 属性チェック終了 ---")
--- 属性チェック終了 ---