melで多重配列っぽいことをしてみる!

おはこんばんちわ。体が硬くて正座ができません・山本ほっさんです。

今回はAutodesk Maya®のスクリプト言語であるmelにて「多重配列」的な実装の例をご紹介したいと思います。

そもそも多重配列とは…

本題の前に、そもそも多重配列に関する解説と、その導入意義について。

wikiを見てみると、
『1次元だけではなく2次元・3次元などの多次元配列 (multidimensional array) を備える言語もある。 マトリックスやグリッドのような矩形構造を持ったデータ構造であることから、矩形配列 (rectangular array) と呼ばれることもある[3]。』
https://ja.wikipedia.org/wiki/配列#多次元配列
…とありました。なんのこっちゃ。(´・ω・`)ポカーン

要は入れ子の配列構造のことです。
複数の配列要素をセットで扱う場合などに大変便利です。 

例えば、
・translate
・rotate
・scale
・jointOrient …など。

これらは、それぞれ3つずつの要素を持っていますね。
これらの値を一挙に操作したい場合、毎回x,y,z…と各要素ごとに処理を書くのは大変面倒です。

多重配列が使えるpythonでは、以下のように記述することができます。

import maya.cmds as mc
#アトリビュートがセットになった多重配列を作成
trns = [0.0,0.0,0.0]
rot = [0.0,0.0,0.0]
scl = [1.0,1.0,1.0]
jo = [0.0,0.0,0.0]
valSetList = [trns,rot,scl,jo]

node = mc.joint()
for cnt in range(len(valSetList)):
    #ここで全ての要素に対して一挙にsetattr
    mc.setAttr('{}.{}'.format(node,['t','r','s','jo'][cnt]),
    valSetList[cnt][0],valSetList[cnt][1],valSetList[cnt][2])

※ちょっと特殊な書き方をしているのは、後述のmel構文と合わせるためなので、ご了承ください。

このように使うと、いちいちアトリビュートごとに分けてsetAttrを書かずに済みます。

それ以外にも…

・自動処理をかけたいファイルをディレクトリごとにセットで扱いたい場合
・テクスチャ・UV・マテリアルをセットで編集したい場合
・スムースバインドされたメッシュの頂点ごとにウエイト値を処理する場合

…など、複数のデータをセットで扱いたい、さまざまなケースに多く使えると思います。

しかしmelでは多重配列が使えない

多くの言語で多重配列はサポートされていますが、しかしmelでは多重配列が使えません。
pythonが使えれば問題ありませんが、LT版などmelしか使えない環境はどうしたらいいのでしょうか。

あきらめて各要素を個別に処理してもいいのですが、ここでは工夫して多重配列っぽい実装をした例をご紹介します。


結果を先に
vector型配列を使う

vector型変数を配列として使って、それっぽい実装を試みてみました。(かなり使用条件が限定されますが)
「ベクトル」というワードを聞くと、数学のなんか難しい印象があるかわかりませんが、ここではシンプルに…
「3つの数値がセットになったもの」
…という所がポイントです。

例えば

vector $vec;
$vec = <<1.0,2.0,3.0>>;

として、vector型の変数 $vecを定義してみます。
ベクトル型は「3つの数値がセットになったもの」なので、これだけで…

print ($vec.x + "\n");
print ($vec.y + "\n");
print ($vec.z + "\n");
//結果
1
2
3

こんな感じで、要素数3のfloat配列っぽく使うことが可能です。

なので、これを配列定義してやれば…

vector $vecList[];
$vecList[0] = <<1.0,2.0,3.0>>;
$vecList[1] = <<1.0,2.0,3.0>>;
$vecList[2] = <<1.0,2.0,3.0>>;

こんな感じで、まるで要素数3のfloat配列の2重配列のように使用することができます。


vector配列を使った例

たとえば、冒頭で紹介したpythonを上記のvector変数を使わずにmelで行うと…。

float $trns[] = {0.0,0.0,0.0};
float $rot[] = {0.0,0.0,0.0};
float $scl[] = {1.0,1.0,1.0};
float $jo[] = {0.0,0.0,0.0};

string $node = joint();

setAttr ($node + ".t") $trns[0] $trns[1] $trns[2];
setAttr ($node + ".r") $rot[0] $rot[1] $rot[2];
setAttr ($node + ".s") $scl[0] $scl[1] $scl[2];
setAttr ($node + ".jo") $jo[0] $jo[1] $jo[2];

こんな感じに、各要素ごとでsetAttrを書かないといけません。

ここでvectorを応用してやると…。

vector $trns = <<0.0,0.0,0.0>>;
vector $rot = <<0.0,0.0,0.0>>;
vector $scl = <<1.0,1.0,1.0>>;
vector $jo = <<0.0,0.0,0.0>>;
vector $valSetList[] = {$trns,$rot,$scl,$jo};

string $node = joint();
string $attrList[] = {".t",".r",".s",".jo"};

for ($cnt = 0;$cnt < size($valSetList);$cnt++){
    vector $val = $valSetList[$cnt];
    setAttr ($node + $attrList[$cnt]) ($val.x) ($val.y) ($val.z);
}

こんな感じ。
setAttrを一度しか書かずに済みました。

…いや。
でもちょっと。
むしろコードが長くなっちゃったじゃん。

vector配列のままだとsetAttr処理のところがうまく実行できないので、上の例ではvector要素をいちいち取り出して処理しています。
vector型の各要素を取り扱う場合、ちょいちょい手続きが必要だったりするので、場合によってはむしろコードが長くなってしまう欠点もあるというわけです。。(大汗)

いやでも。
しかしですよ。
vector配列を使うことによって、本来melでは不可能だった多重配列的な処理が組め、setAttrのところを一回にまとめることができたじゃないですかー!ハ、ハラショー!

こういうところなんですよぉ(何

まとめ

今回は、melの抱える問題をなんとか工夫で乗りきる!…ということで(汗
melは多重配列が使えないスクリプト言語ですが、vectorを3要素のfloat配列と見立てれば、多重配列っぽいコードを書くことが可能、というご紹介でした。
ただ、手続も少し面倒なところがあるので、使いどころを選ぶと思います。

また、「ベクトル」というワードに難しい印象を持つ人も少なくないと思います。
しかし、このように「3つの値をセットで扱うもの」という最小限のポイントだけフィーチャーすると、導入のハードルも下がって、melのスクリプティングの可能性が広がると思います。

ほかにもvector型すごく便利!

更にvector型は、他にもいろいろな便利な機能を備えていて、
・3つの値を同時に足し引きできる
・座標までの長さを求める
・二つのベクトルの織り成す角度を求める
・三角関数を使った、高度な計算ができる

など、様々な演算が可能です。
三角関数を使えば、ノードの向きや、位置関係などを判定することが可能になってきます。
しかもmelだと、これらの計算がスクリプトコマンドでサポートされているので、難しい計算式をいちいち書く必要もありません。

実用例として、
・複数頂点が同一座標かどうか調べる
・複数座標の中から、ある座標に最も近い座標点を求める
・ある座標から、指定された三軸座標または距離を移動させた、あたらしい座標点を求める
など。

また、melサポートされているということは、エクスプレッションにも使えるので、複雑なリギングのシステムにも導入する事だって可能です。

これらの、より踏み込んだvector型の導入例については、また別の機会にご紹介したいと思います。

なお、pythonでvectorを使う場合は、numpyをimpoprtして使うことが多いと思います。
既定のMayaではnumpyはimportで来ませんが、OpenMayaでMVectorクラスが用意されているので、こちらをimportして使うのが便利です。

ぜひこの機会に、一度vector型を使ってみてはいかがでしょうか。
この記事がその一助となれば幸いです。

Author: tomohito.yamamoto