🌀

キャラクターセットを仲介したノードの接続/再接続について考える

6 min read

そんなひとはいない、はやく目を覚ませ!

※この場合のお強い方というのは、下図の様子を見て「なるほどね」と神妙に頷きながら一緒に酒を飲める地球人のことを差します。


例えば、あるコントローラーに挿さっているアニメーションノードを、
別のコントローラへ挿したい(移植したい)ことがあります。

コントローラーノードとアニメーションカーブノードの対応関係がシンプルなら
把握もしやすくスクリプト化もちょっと頑張ればできそうです。

でもアトリビュートがキャラクターセットに登録されていると、
アニメーションノードは直にアトリビュートに挿さらず、キャラクターセットを仲介してコントローラーのアトリビュートを駆動させます。
冒頭の図でいうところの、左側のノード群のことですね。
中央の間抜けづらした細長いやつがキャラクターセット、それを挟んで右にくっついてるのがコントローラ、左のエイヒレの出来損ないみたいなのがアニメーションノードです(口が悪い)。

この左ノード群のエイヒレ部分を、右ノード群の適切なところに挿したい。

つまり、
キャラクターセットを介している場合にアニメーションノードをつなぎかえようと思ったら、

  1. 元コントローラーのキャラクターセットとのコネクションを確認
  2. キャラクターセットとアニメーションノードのコネクションを確認
  3. 差し替え先コントローラーのキャラクターセットのどこに新たに挿せばいいか確認

みたいな手順を踏む必要があります。
めんどくせ。

アトリビュートの接続の確認

アトリビュートの接続状況を確認したい時には、
基本的には listConnections を使います。

ここでは横着をして PyMEL の inputs/outputs を使うことにします。

inputs

https://help.autodesk.com/cloudhelp/2020/JPN/Maya-Tech-Docs/PyMel/generated/classes/pymel.core.nodetypes/pymel.core.nodetypes.DependNode.html?highlight=inputs#pymel.core.nodetypes.DependNode.inputs

outputs

https://help.autodesk.com/cloudhelp/2020/JPN/Maya-Tech-Docs/PyMel/generated/classes/pymel.core.nodetypes/pymel.core.nodetypes.DependNode.html?highlight=outputs#pymel.core.nodetypes.DependNode.outputs

あるノードを対象に inputs / outputs を実行すると、そのノードとコネクト関係にあるノードや、挿さっているアトリビュートを調べられます。
中で実際仕事しているのはlistConnectionsなので、
listConnectionsに備わっているオプションがそのまま使えます。
主に p c を使いますが、どっちを付けたらどうなるんだっけ?
というのをよく忘れるので、ちょっとまとめてみます。

inputs/outpus (オプションなし)

「緑」ノードを対象にしてinputs/outputsを オプションなしで 実行すると、
実行結果として「赤」のノードが返ってきます。

.inputs() で左側のノード、
.outputs() で右側のノード

それぞれ複数接続されている可能性があり、リストで返ってきます。

p=True

p(plugs) を有効にして実行してみます。
同じく赤で示してある要素が返ってきます。

https://help.autodesk.com/cloudhelp/2020/JPN/Maya-Tech-Docs/CommandsPython/listConnections.html#flagplugs

対象ノード(緑)から出ているコネクションが挿さっているノード(オプションなしで得られたノード)の
挿さっているアトリビュートが返ってきます。

.inputs(p=True) で、対象ノードに入力しているコネクションの、出し側ノードのアトリビュート
.outputs(p=True) で、対象ノードから出力しているコネクションの、受け側ノードのアトリビュート
が得られます。

c=True

c(connections) を有効にすると、

https://help.autodesk.com/cloudhelp/2020/JPN/Maya-Tech-Docs/CommandsPython/listConnections.html#flagconnections

微妙にわかりづらいんですが、
オプションなしで返ってきたノードと、そのノードが挿さっている(対象ノード側の)アトリビュート
が二重配列になって返ってきます。
(二重配列に整形してくれるのはたしかPyMELの設計で、cmdsだと単純にリストが返ってきた気がする。)

.inputs(c=True) で、コネクションを挿しているノードと挿さっているアトリビュート
.outputs(c=True) で、コネクションを挿しているアトリビュートと挿さっているノード

……この組み合わせで返ってくるの、いつ嬉しいんですかね??

p=True, c=True

pcも有効にしたら?
両方の結果を合わせて、つながっているノードとつながっている出し/受け側アトリビュート
全部が返ってくる気がします。

でも直感に反して、返ってくるのは下図の赤の要素です↓

コネクションの、出し/受け側アトリビュートが二重配列で返ってきます。
これにノードも返ってきたとして、配列が複雑になっても使いづらいですし、
アトリビュートオブジェクト自体に node メソッドがあってそこからノードを取得できるので、
必要に応じてそれを使えば良さそうです。


キャラクターセットを介してアニメーションノードを探ってみる

まず対象ノードに挿さっているキャラクターセットのアトリビュートを調べて、
そこからアニメーションノードのアトリビュートを調べます。

対象ノードを target_ctl という変数に入れたとして、

target_ctl.inputs(p=True,c=True)

これで、受け側(target_ctl)/出し側(キャラクターセット)双方のアトリビュートが得られます。
ひとまず前者を target_input、後者を src_outputに入れることにします。

for target_input,src_output in target_ctl.inputs(p=True,c=True):
    # target_input : 受け側(対象ノード側のアトリビュート)
    # src_output : 出し側(キャラクターセット側のアトリビュート)

src_output にはAttributeオブジェクトが入っていますが、
こいつも inputs / outputs メソッドを持っていて、
そのアトリビュートへの入出力を調べられます。
ここに入力しているアニメーションノードを調べるには .inputs() オプションなしで実行すればいいですが、
今回はアニメーションノードそのものには用事はなく、アトリビュートのつなぎ換えが目的なので
.inputs(p=True) とpオプションを有効にして実行します。

これもリストで返ってきますが、
キャラクターセットのひとつのアトリビュートに複数のノードが入力しているとは考えづらいため、 [0] でパパッと取り出します。

for target_input,src_output in target_ctl.inputs(p=True,c=True):
    anim_node_output = src_output.inputs(p=True)[0]

つなぎ変えられる側のキャラクターセットも調べる

同じようにして、先ほど調べたアニメーションノードのアトリビュートを
別のキャラクターセットのどこに挿せばいいか確認します。

for new_target_input,chr_attr in new_target_ctl.inputs(p=True,c=True):
    # new_target_input : 受け側(対象ノード側のアトリビュート)
    # chr_attr : 出し側(キャラクターセット側のアトリビュート)

やってることは一緒ですが、なにやらそろそろ
スクリプトの難しさ云々より変数名考えたり、どれになにが入ってるかイメージする(覚えておく)のが微妙に厄介な感じになってきました。

アニメーションノードをつなぎ変える

あとは、両方の for をぐるぐる回しながら、
対象ノードとnew対象ノードのアトリビュート名が一致する時に、
アニメーションノードのアトリビュートをキャラクターセットのアトリビュートへ接続する、
という内容で書けばいけそうです。

# 前略
attr_longName = target_input.longName()

# 中略
new_attr_longName = new_target_input.longName()

if new_attr_longName == attr_longName:
    anim_node_output >> chr_attr

↑実際には、for を二重に回すのでインデントがずれます。

まとめると、こんな感じ↓

from pymel import core as pm

target_ctl,new_target_ctl = pm.selected()
for target_input,src_output in target_ctl.inputs(p=True,c=True):
    anim_node_output = src_output.inputs(p=True)[0]
    attr_longName = target_input.longName()
    for new_target_input,chr_attr in new_target_ctl.inputs(p=True,c=True):
        new_attr_longName = new_target_input.longName()
        if new_attr_longName == attr_longName:
            anim_node_output >> chr_attr

キャラクターセットに登録されているノードAとBがあったとして、
A→Bの順で選択してこのスクリプトを実行すると、
A側のキャラクターセットにささっていたアニメーションノードが、B側のキャラクターセットのおなじアトリビュートに挿さります。

説明変数に入れたりしているので思ったより行数がありますが、
そういうことをしなければ半分くらいの行数になります。

このつなぎ替え、手でやってもいいですが、
キャラクターセットは登録したアトリビュート分タテに伸び、数百行分におよぶこともあるので、
ノードエディタ上でも非常にめんどくさいことになります。冒頭画像のように。
スクリプトで処理するのが人道的です。

あとコントローラにコンストレインもかかっているとpairblendも考慮しないといけなくなるので、もう一歩めんどくなります。