Autodesk製melコードを読み解く~AnimLayerEditor編~

おはこんばんちわ、ミカンは大体一口で食べます・山本ほっさんです。

Autodesk Maya®のスクリプト言語として知られるmel(Maya Embedded Language)ですが、実はMaya自体の多くの機能やUIが、Autodesk製のmelスクリプトによって構築されています。
それらのソースコードはMayaのインストールされたディレクトリ内のscriptフォルダ
(例:C:\Program Files\Autodesk\Maya2020\scripts など)
に格納されていて、自由に内部を読むことが可能です。

先日、お仕事でAnimationLayerEditorを構築するmelスクリプトを解析したのですが、これがなかなか面白くて、多くの発見がありました。


今回は、このAnimationLayerを構成する「LayerEditor.mel」を通して、Autodesk製のmelを、少しだけ読み解いてみようと思います。


対象のmelを探す

「whatIs」コマンド(注1)とスクリプトログの「Echo All Commands」を使って、AnimationLayerEditorに関するスクリプトを探してみましょう。

Script EditorのHistoryから「Echo All Commands」にチェックを入れ

とリあえずAnimLayerを作成して、選択。

…からのundoを連打すると、ログが出てきます。

「// Undo: animLayerEditorOnSelect “BaseAnimation” 1 // 」
animLayerEditorOnSelect これが怪しそうです。
なんかAnimationLayerEditorで何かを選択したときに実行されてそうな感じ。

このコマンドを「whatIs」コマンドにかけてみます。

whatIs animLayerEditorOnSelect;
// Result: Mel procedure found in: C:/Program Files/Autodesk/Maya2020/scripts/startup/layerEditor.mel

何やらファイルパスが出てきました。
「C:/Program Files/Autodesk/Maya2020/scripts/startup/layerEditor.mel」
このmelファイルの中に、animLayerを選択した際に実行されたスクリプト「animLayerEditorOnSelect」があるとわかりました。

では、このスクリプトファイルに侵入してみましょう…


LayerEditor.melの冒頭

LayerEditor.melを開いてみます。
大体冒頭の部分に説明書きがあるので、 読んでみると…

//  Description:
//      This script initializes the Channel Box and Layer Editor.  
//      Initialization involves determining the initial preferences, 
//      creating the UI and setting the initial visibility.

//説明:
//このスクリプトは、チャンネルボックスとレイヤーエディターを初期化します。
//初期化には初期設定の決定が含まれ、
// UIを作成し、初期可視性を設定します。

(google翻訳)

なるほど。レイヤーエディタを初期化してるのです。
合わせてチャンネルボックスも初期化しているようですね。ふーん。

【面白ポイント1】文字配列を逆転させるプロシージャ

また、冒頭の部分に気になるローカルプロシージャーがありました。

proc string [] reverseArray(string $originalArray[])
{
    string $newArray[] = $originalArray;
    int $newArrayLength = size($newArray);
    int $halfLength = $newArrayLength/2;
    int $i;
    for ($i = 0; $i <$halfLength; $i++)
    {
        string $tmpLayer = $newArray[$i];
        int $j = $newArrayLength - $i -1;
        $newArray[$i] = $newArray[$j];
        $newArray[$j] = $tmpLayer;
    }
    return $newArray;
}

なるほど、これは文字配列を逆転させて返すプロシージャーのようです。
少し中を見てみると、処理が面白いです。
配列サイズの半分の回数だけfor文を回して、オリジナルの配列の前後から要素を取り出して、新しい配列に入れなおしてます。
なるほど。これは試行回数を減らす工夫でしょうか。

こういうmelの書き方も大変参考になります。


ひとまず「animLayerEditorOnSelect」を探してみよう。

ひとまず、最初watIsで検索した「animLayerEditorOnSelect」コマンドが本当に含まれているか、探してみましょう。

コマンドを検索にかけてみると…

treeView -edit -selectCommand "animLayerEditorOnSelect" $animLayerEditor;

317行目辺りで、上記の行が見つかりました。
なるほど、AnimLayerEditorはtreeViewでできてるんですね。
あと、この行では、treeViewのレイアウトにeditをかけていますね。
treeViewが選択されたら「animLayerEditorOnSelect」が実行される、というわけです。
なるほど。

そしてこの記述、コマンドを埋め込んでいるということは、おそらくAnimLayerEditorを構築している最中の記述っぽいな、と推察できます。

少しだけコードを上に戻って読み進めてみると…

global proc createAnimLayerEditor(string $parentLayout, string $toolName )

上記のプロシージャー内で、先ほどの記述が使われていることがわかります。
プロシージャーの名前「createAnimLayerEditor」から推察するに、やはりAnimLayerEditorを構築する際に実行されている事が予想されます。

では、このcreateAnimLayerEditorの中を読み進めて、AnimLayerEditorがどのように構築されているのかを少し見てみましょう。


AnimLayerEditorのUI構造

すこし読み進めると、

$AnimLayerFormLayout = `formLayout`;

割とレイアウトの冒頭でformLayoutが呼ばれています。
AnimLayerEditorはformLayoutでレイアウトが作られているようですね。
さらに、読み進めると…

$zeroKeyAnimLayerButton = `symbolButton -image "zeroKey.png"  -annotation $zeroKeyAnimLayer`;
$zeroWeightAnimLayerButton = `symbolButton -image "keyZeroWeight.png"  -annotation $zeroWeightAnimLayer`;
$fullWeightAnimLayerButton = `symbolButton -image "keyFullWeight.png"  -annotation $fullWeightAnimLayer`;
$emptyAnimLayerButton     = `symbolButton -image "newLayerEmpty.png"  -annotation $createEmptyLayer`;
$selectedAnimLayerButton   = `symbolButton -image "newLayerSelected.png"  -annotation $createLayer`;
if( size($gLEAddButtons) == 0 ){
    $gLEAddButtons[0] = $emptyAnimLayerButton;
    $gLEAddButtons[1] = $selectedAnimLayerButton;
}
$moveSelectionUpButton   = `symbolButton -image "moveLayerUp.png"  -annotation $moveSelectionUp $lanimLayerMoveUpButtonName`;
$moveSelectionDownButton   = `symbolButton -image "moveLayerDown.png"  -annotation $moveSelectionDown $lanimLayerMoveDownButtonName`;

symbolButtonがられるされています。
アイコンの名前から推察するに、この辺りは…

この辺りを作っている記述ですね。

そして、

$animLayerWeightField     = `floatField -width 40 -precision 3 -maxValue 1.0 -minValue 0.0 -value 1.0 $lanimLayerWeightFieldName`;
$animLayerWeightSlider   = `floatSlider -maxValue 1.0 -minValue 0.0 -step 0.01 -value 1.0 $lanimLayerWeightSliderName`;
$animLayerWeightButton   = `button -label $keyButton $lanimLayerWeightButtonName`;

この辺りは、変数の名前や、floatField、floatSliderを使っているあたりから…

ここを作っているところであると推察できます。

先にすべて作っておいて、あとで
formlayout -e -attachForm…
などからレイアウト調整するのでしょう。 フムフム。

【面白ポイント2】見慣れない記述「melでの三項演算子」

次を読み進めたところで、見慣れない記述を見つけることができました。

$buttonsOnRight = `optionVar -exists animLayerButtonsOnRight` ? `optionVar -query animLayerButtonsOnRight` : 0;
$reverseLayerStack = `optionVar -exists animLayerReverseLayerStack` ? `optionVar -query animLayerReverseLayerStack` : 1;

んー、optionVarからAnimLayerEditorに関する以下のユーザー設定…
- ボタンを右に置くか
- 階層表記を逆転させるか
…を取得しているようですが…途中で「?」が使われていますね。

「?」の前後を見ると、optionVarの値が存在しているかどうかを調べているようです。
つまり、この表記は

$変数 = `テスト` ? `テストが正の時の値を返す式` : テストの値が偽の時の値

と読めます。
要はこれは、mel版の三項演算子ですね。
実は「?:」という表記は、C言語などのプログラミング言語の中ではポピュラーで、三項演算子として広く知られている記述です。
https://ja.wikipedia.org/wiki/条件演算子
ただ、非プログラマーの人間には、なかなかなじみのない記述ですよね。
アーティストにとっては、初めて見る人も少なくないと思います。

しかし、このように既成のmelを読み解くことで、こういったプログラムの知見も吸収しやすいのではないでしょうか。

もう少しコードを読み進めると…

$animLayerEditor   = `treeView -parent $AnimLayerFormLayout -numberOfButtons 4 -abr $buttonsOnRight -rto $reverseLayerStack ($toolName+"animLayerEditor")`;

ここで、optionVarから取得した値を基に、treeViewでAnimLayerEditorのメイン部分を構築していますね。

ようやく、ここです。

設定を見ると、
- ボタンを4つ
- -abr = -attachButtonRight ボタンレイアウトを右に置くかどうか
 →optionVarの設定を参照
- -rto = -reverseTreeOrder ツリー描画の順序を逆転させるか
 →optionVarの設定を参照

…という指定がされています。

それ以降の記述では

treeView -edit -selectCommand "animLayerEditorOnSelect" $animLayerEditor;
treeView -edit -pressCommand 4 ("animLayerGhostCallBack \""+$animLayerEditor+"\"") -pressCommand 3 "animLayerMuteCallBack" -pressCommand 2 "animLayerSoloCallBack" -pressCommand 1 "animLayerLockCallBack" $animLayerEditor;
treeView -edit -rightPressCommand 4 "animLayerGhostRightCallBack" $animLayerEditor;
treeView -edit -expandCollapseCommand "animLayerExpandCollapseCallback" $animLayerEditor;
treeView -edit -dragAndDropCommand "animLayerDragAndDropCallback" $animLayerEditor;
treeView -edit -editLabelCommand "animLayerEditLabelCallback" $animLayerEditor;

いくつかのコマンドを埋め込んでいますね。 上から順に…
- treeViewを選択した時、「animLayerEditorOnSelect」が実行される
- treeVewの4番目のボタンを押した時、「animLayerGhostCallBack」が実行される
- treeViewの親子階層が展開された時、「animLayerExpandCollapseCallback」が実行される
- treeViewにドラッグ&ドロップが行われた時、「animLayerDragAndDropCallback」が実行される
- treeViewのラベルを編集した時、「animLayerEditLabelCallback」が実行される

…といった具合。
各々の挙動を確認したい際、それぞれのコマンド名で再検索すれば、その処理の中身が見れて面白いと思います。

【面白ポイント3】popUpMenuは表示される直前に再構築している

その次の記述では、

$animLayerPopupMenu  = `popupMenu -button 3 -allowOptionBoxes true -parent $animLayerEditor`;
treeView -edit
    -contextMenuCommand ("layerEditorBuildPopupMenu \""+$toolName+ "\" \""+$animLayerPopupMenu+"\" ")
    $animLayerEditor;

ここでは、popUpMenuを仕込んでいますね。
ここで着目すべきは、2行目の「treeView -edit -contextMenuCommand」です
popUpMenuが表示される直前に「layerEditorBuildPopupMenu」が実行されるよう仕込んであるようです。
なるほど、表示される直前にpopUpMenuの状態を再構築しているんですね。

確かに、

BaseLayerを右クリックしたときと、


通常のレイヤーを右クリックしたときでは、だいぶ様子が違いますね。

表示する直前に、条件を参照してpopUpMenuをリビルドした方が効率的と言えるかもしれません。

こういった記述やフラグの実例は、実際に自分でToolを自作するときに大変参考になると思います。


ん、あの「緑色の丸」の記述はどこなの?

ここまで読んで、AnimLayerに含まれるノードを選択していると、AnimLayerEditor上に表示される「緑色の丸」に関する表記が何も出てきませんでした。

これですね。

これの正体は何なのでしょうか。
いったんmelのドキュメントに戻って、フラグを確認してみましょう。
http://help.autodesk.com/cloudhelp/2020/JPN/Maya-Tech-Docs/Commands/treeView.html

それらしいフラグがありました。

「項目がオーナメント(小さい色付きの円)を持つようにし…」とあります。 きっとこれでしょう。
試しに、コードを「-ornament」で検索してみると…

int $affectedLayer = isAffectedLayer($layer, $affectedLayers, $recursive);
if( $affectedLayer == 1) // This layer is an affected layer
{   
    treeView -edit -ornamentColor $layer 0.8 0.4 0.4 ($toolName+"animLayerEditor");
            treeView -edit -ornament $layer 1 0 3 ($toolName+"animLayerEditor");
}
else if( $affectedLayer == 2) // A child is an affected layer
{
            treeView -edit -ornamentColor $layer 0.8 0.4 0.4 ($toolName+"animLayerEditor");
            treeView -edit -ornament $layer 1 1 3 ($toolName+"animLayerEditor");
}
else    //layer does not affect selected objects 
{
    treeView -edit -ornament $layer 0 0 5 ($toolName+"animLayerEditor");
}

…ありました。

対象のレイヤが影響するかどうかを「isAffectedLayer」プロシージャで確認しているようです。
その後、条件分岐で-ornamentの色や表示を切り替えているようです。

ちなみに、上記の「ornament」を埋め込んでいる記述はどこのプロシージャに属しているのか、少し上をたどってみると…

proc updateEditorFeedbackAnimLayer(string $toolName, string $indentLayer, string $bestLayers[], string $affectedLayers[])
{

なるほど。updateEditorFeedbackAnimLayerプロシージャの中で実行されていますね。
ちなみに「updateEditorFeedbackAnimLayer」をコード内検索をかけてみたところ、scriptJobやそのほかAnimLayerEditorを更新する際にちょこちょこ呼ばれていることがわかりました。


まとめ

今回はAnimLayerEditorのUI部分にフィーチャーして、Autodesk製melの読み解きを紹介してみました。
本当は各処理の中まで掘り下げて紹介したかったところですが、UI部分だけ軽く紹介するだけでも結構な文章量になってしまいました。
ですので、今回はこの辺で区切りたいと思います。

こうして普段何気なく使っているMayaの機能でも、whatIsコマンドからAutodesk製melを探り当て、中身を掘り下げて追及してみると、割と新しく発見することも多いと思います。
このことから、Autodesk製melを読み解くことは、自身がmelを作成する際のいいお手本になるだけでなく、Mayaやmelについて、より深く理解することにも繋がって、大変おすすめです。

また読んでいる最中、様々な見慣れない記述や処理が登場して、
「ん?これはなんだ??」
…と疑問に思った端から再検索をかけ、更に読み進めることで、
「なんと、そういうことだったのか!ハラショー!!」
…などと、更なる新しい発見と理解に次々遭遇する事でしょう。

これはもうちょっとした読み物としてもなかなか面白いのではないでしょうか?
え、そうでもない?
ああ、そう。 そうですか。。

でも酒の肴にはもってこいですよ!
え、そうでもない?
ああ…そう…ですか。。


注釈1)
whatIsコマンドは、スクリプトコマンドが書かれているソースのmelファイルを探し出すためのコマンドです。
以前の記事でも紹介しておりますので、こちらの記事をご覧ください。

表示されないMelコマンドを知る方法:中級編


公式ドキュメントはこちら↓
https://help.autodesk.com/cloudhelp/2020/JPN/Maya-Tech-Docs/Commands/whatIs.html

Author: tomohito.yamamoto