« XSI男Tシャツ発送完了。 | トップページ | さわやかな朝に Group Layer Partition。 »

2011年5月14日 (土)

Partition.AddMember の罠。

長らく続いたモデリング作業も終盤を迎え、その後はレンダリングの必要があるわけでして、溜まったモデリング系のスクリプトも整理しないうちにレンダリングに関わるスクリプトも書かざるを得ないという日々であります。 いや、そんなに大したものは書いてませんよ。 パーティション分けのツールをほんのちょっと書いているだけです。


で、パーティションのスクリプトで気づいたことを書きます。


XSI 七ちゃんの時からだと思うけど、Partition オブジェクトが独立し、色々楽になりました。 Partition ってもともと Group や Layer と同じ種類のモノ( siGroupType )なのですが(アイコンも Group や Layer と色が違うだけで形は同じ)、独立した Type を持つようになったため取得するのも超楽になったし、Group に有効なメソッドやプロパティが Partition にも使えるようになったりしました。


がしかし。
落とし穴が。

いや、そんな大した落とし穴でもないんですが。
でもこれ気づくまでにだいぶ時間かかった。
故にスクリプトが上手く動かなくてかなり時間をロスしました。


こんな状態です。
1
Partition_A には Hage1~3 が入っています。
Partition_B の方には Hage4~6 が入っています。

Partition_B に入っている Hage 4, 5, 6 を Partition_A に移したいとします。
そこでこういうスクリプトを書きます。

 var oPartition = Dictionary.GetObject( "Passes.Default_Pass.Partition_A" );
 oPartition.AddMember( Selection );


1行目は、安直な方法でパーティションオブジェクトを取得しているだけです。名前がフルパスでわかっているなら GetObject で取得しちまうのが簡単です。 今回この行は重要ではない。

大事なのは2行目なんですが、1行目で取得したパーティションオブジェクト( oPartition にバインドされている)に対して、AddMember の呪文を唱えています。 そのまんまですね、パーティションにメンバを追加する、つまりこのパーティションにこのオブジェクトを入れろと指令しているわけですね。 カッコの中は Selection = 現在選択中のブツ です。 つまり、oPartition の中に現在選択中のオブジェクトを入れろと言っているわけです。

Hage4, 5, 6 を選んだ状態でこのスクリプトを実行すると、
2
こうなります。 問題ありません。


次にたった今移動させた Hage4, 5, 6 を Partition_B に戻して、元の状態にします。

次に、Hage1, 4, 5, 6 を選択した状態で同じスクリプトを走らせてみます。
Hage1 のみは、既に Partition_A に入ってしまっていますね。この状態で実行すると、
3
あれっΣ(゚Д゚)

この画像は、スクリプト実行後です。
4, 5, 6 は Partition_B に入ったままになってますね。移動してくれないんですよ。


どうやら、移動先のパーティションに既に入ってしまってるものが1つでも含まれていた場合、Partition.AddMember を使うと移動が行われないようなんですね。 これに気づくまでにすんげえ時間がかかりました。 


選択中のオブジェクトを全 Pass で特定のパーティションに移動させるみたいなスクリプトを書いて、わあ便利だ俺は天才などと思っていたら、ちゃんと移動していないことが多々あることを発見し、コードを見直してもどこも間違ってないように見え、あーだこーだといじくったりモニタを2個くらい窓から遠投しているうちに選択しているブツによって結果が変わることを発見し、観察した結果、どうやら上に書いたことが原因らしいと思われる、という状況です。

観察した結果そうであるらしい、というだけですよ。 マニュアルに書かれてませんもの。 AddMember のヘルプページ見ても 「すでにメンバであるオブジェクトが含まれていると実行されません」とかなんとか、書いてないですから。 ちゃんと書いとけドルァ


そして Partition ではなく、Group に対して AddMember を実行した時はちゃんと動くんです。既にその Group に入ってしまっているものが移動対象に含まれていても正しく AddMember が実行され、既に入っているそのオブジェクトも含めた全てがちゃんと Group のメンバになってくれます。 Group に対してはちゃんと動くのに、なぜ Partition に対しては動かないのか。 ノルァ

思うにですね、Group と Partition の決定的な違いは、ブツは複数の Group に入ることができるけど、Partition は1個しか入れない、という部分ですよね。 上記の不審な挙動はこの違いから来ているんじゃないかと思っているんですが、どうですかね。 そして AddMember メソッドはもともと Group 用に作られたものであり、Group とは違ってブツが所属できるのは1個だけという Partition の特徴は考慮に入れられてない設計なんだろうと思うんですよ。 でも XSI が七ちゃんになって Partition オブジェクトが独立した時に、Partition オブジェクトにも安直に Group と同じスクリプトインターフェースをサポートさせた結果このような挙動になっているのではないか、と疑っているのです。 どうなんですか嘔吐デスクさん。 いや、これは嘔吐デスクに買って頂く前の話なので、犯人は Avid 傘下の Softimage さんですね。

ヘルプのページを見ても、Partition.AddMember に行くと Group.AddMember に飛ばされますからね。明らかに Group 用の AddMember を流用しているとでも言うか。 だから Partition 用に独自の AddMember があればいいんだと思うんですよ。 後述しますがコマンドでやると問題なく動くわけで、オブジェクトモデルで AddMember した時と挙動が違うのはやはり不具合と言うべきでしょう。 直してくれ。 こんなちょっとのことで、自作のツールが動かなくて原因解析に何時間も使っちまうんだよ嘔吐デスクさん。



ってことでですね、回避策のひとつとして、オブジェクトモデルを使うことをあきらめ、つまり Partition オブジェクトに対して AddMember の呪文を唱えることをやめて、昔ながらのコマンドでやっちまうことにしてみます。
4


var oPartition = Dictionary.GetObject( "Passes.Default_Pass.Partition_A" );
MoveToPartition( oPartition, Selection  );


1行目は全く同じ。
2行目で、MoveToPartition コマンドを使っています。

これを実行すると、Hage1 という既に Partition_A に入っちまってるオブジェクトが含まれているにも関わらず、正しく全員 Partition_A に入ってくれます。 ふう。 一応解決。


<余談>
コマンドを使っているということは、フルパスで名前(文字列)を直接指定して事を進行させるということなので、パーティションをオブジェクトとして取得する必要はありません。なので、パーティションをオブジェクトとして取得している1行目は今回、本質的には必要ではありません。2行目の oPartition の所に、1行目の "Passes.Default_Pass.Partition_A" を書けばいいだけです。  じゃあなぜ必要のないコードを書いているのかと言うと、今回の場合は、最初のスクリプトと同じ構造(まず取得し、取得したものを使って何かをやる)に見える方が説明上わかり易かろうと思ってこうした、というだけのことです。

さらに言えば、コマンドではオブジェクトではなく「名前」を使うわけなので、2行目の oPartition は、1行目で取得したパーティションオブジェクトを  MoveToPartition コマンドに渡しているわけではなく、oPartition の名前(文字列)を渡しているだけということになります。 oPartition は厳然たるオブジェクトですが(1行目で取得しているので)、コマンドに渡す際にはオブジェクトとして渡されるわけではなく、名前(文字列)として渡されているだけだということです。 oPartition.fullname という表記が省略されて oPartition と書かれていると考えた方が良いでしょう。

だからと言ってコマンドに対して取得したオブジェクトを食わせるのは間違いだとか無駄だとか言っているのではありません。こんなん、いくらでもやりますからね。なんら間違った手法ではありません。 ただ、渡されているものはオブジェクトではなく文字列なんだ、という意識があった方が、スクリプティングそのものの理解には役に立つと思います。

さらに言うと、こんなこと、マニュアルに書かれていたわけではありません。たぶん。 今までの経験と、ネット上で色々読んだ話などから、「ああ、コマンドというものは文字列を食っているんだなあ」とだんだん理解してきた、というのが本当のところです。 なのでもしかしてとんでもなく間違ったことを言っているかもしれません。 その場合、どうか指摘して下さい。
</余談>




さて、コマンドで問題なく実行できたのでよしとしますが、しかも七ちゃん以前はこの方法しかなかったわけで、これ以上突っ込む必要は全くないと言えばないのですが、可能な限りコマンドを廃してオブジェクトモデルを使おうとしている身としては、MoveToPartition コマンドを撲滅したいと思うわけです。 コマンドはログが残ってうざったかったり(それが良かったりもするんですが)、実行も遅いですからね。

ということで、こうしてみました。
5

var oPartition = Dictionary.GetObject( "Passes.Default_Pass.Partition_A" );
for ( var i=0; i<Selection.count; i++ )
{
    if ( ! oPartition.IsMember( Selection(i) ) )
    {
        oPartition.AddMember( Selection(i) );
    }
}


1行目は同じ。 目的のパーティションをオブジェクトとして取得しています。

その後、現在選択しているものをループして、既に目的のパーティションのメンバになっているかどうかひとつずつ調べる。 メンバでなければ、メンバにする。

IsMember は Partition や Group に効く呪文ですね。まあこれも上の書いたのと同じく元々は Group 用の呪文だと思います。 でも Partition に使っても今のところ不具合は見当たらない。   oPartition.IsMember( Selection(i) ) というのは、パーティション(oPartition)にとって、ループ中のブツ( Selection(i) )が、メンバであるのかどうかを調べるという意味になります。 メンバであれば true を返し、メンバでなければ false を返します。 

AddMemberを使ったとき、すでにメンバである人が含まれているとちゃんと実行されないのであれば、しかたない、ひとりひとりメンバかどうかを調べて、選別してから AddMember に渡してあげようということです。 めんどくさいですね。なんとかしなさい嘔吐デスク様。


ま、こんな余計なコードを書いてスクリプトは複雑になったように見えますが、実行は速いです。オブジェクトモデルですから。 数個のオブジェクトに対して実行してもあまり速さはわからないかも知れませんが、50個や100個に対して実行すると、MoveToPartition コマンドを使うよりもあからさまに速いのがわかります。 そして、そもそも手でやるのでははなくスクリプトでやろうとしていることなんだから、基本的には数が多いわけです。50個や100個を扱いたいからスクリプトを書いているわけです。 だから実行の速さは正義です。


ということで、コマンドを廃してオブジェクトモデルだけで望みの挙動になるスクリプトが書けました。
でもめんどくさい。 
メンバが既に含まれているかどうかに関係なく AddMember が使えるようになればそれで済むこと。
Group に対する AddMember の挙動が、Partition に対しても同じになればそれで済むこと。
嘔吐デスク様、なんとかして下さい。



ごきげんよう。



.

|

« XSI男Tシャツ発送完了。 | トップページ | さわやかな朝に Group Layer Partition。 »

コメント

コメントを書く



(ウェブ上には掲載しません)




トラックバック

この記事のトラックバックURL:
http://app.cocolog-nifty.com/t/trackback/217974/51666265

この記事へのトラックバック一覧です: Partition.AddMember の罠。:

« XSI男Tシャツ発送完了。 | トップページ | さわやかな朝に Group Layer Partition。 »