« 2010年9月 | トップページ | 2010年11月 »

2010年10月

2010年10月31日 (日)

久しぶりの Beerware。

某所の某から、いきなり自宅に届きました。

Img_0017
キリンクラシックラガー 500ml x 24 本!




最近のスクリプティング記事などが某にとっては役立ったようで、ビールよこせと恐喝していたら、ほんとにビール送ってくれました!


いやあ、過去の数少ないビールウェア登録の中で、これは1・2を争うビールの量ですよ。量も大事だけど質も大事で、キリンクラシックラガーというビールは宇宙で一番美味いビールなんです。昔ながらの、いわゆる苦~い味がします。わかりますか。瓶ビールの王冠のウラにコルクが張られていた時代の味ですよ。何かイベントがある時に親に隠れて呑んだあのビールの味ですよ。

呑んでるうちに味も何もどうでも良くなってわけわからなくなるのは当然なのですが、最初のひと口の味がね、「さあ呑むぞ!」とスタートした直後の美味さがね、このビールは格別なんです。口の中にほわ~と広がるあの苦味。これこそがビールです。スーパードライとか言っていてはいけません。ビールは炭酸水とは違うのです。キリンクラシックラガーあるいはキリン明治&大正ラガーあるいは赤い星のサッポロラガーが、本物のビールの味です。


同盟国独逸もビールじゃ有名な国ですが、このビールは独逸にも負けてないんじゃないですかね。共に毛唐どもに負けた国ですが、ビールの味では楽勝です。 亜米利加? バドワイザー? 笑わせるんじゃねえ。 てめぇら日本か独逸に来て本物のビールの味を知って慄然としやがれゴルァ。




ということで、スクリプトを書くと XTC を味わうという変態 XSI 野郎の某君には、俺のスクリプト/プラグインの永久無料ライセンス Serial No.0007 を、謹んで進呈いたします。



毎度ありがとうございます。




.

| | コメント (0) | トラックバック (0)

2010年10月29日 (金)

取得 その5。

Pass や Partition の取得を攻略しましょう。 Pass や Partition がらみのことって、反復操作が多いのでスクリプトの活躍どころです。その端緒をつかむために、簡単なサンプルを書いてみようと思います。


まず、シーンの中にある全ての Pass を取得する書き方です。

  var oAllPasses = ActiveProject.ActiveScene.Passes;

以上。

細かく言えば Application オブジェクトの ActiveProject プロパティを使って XSIProject オブジェクトを取得し、XSIProject オブジェクトの ActiveScene プロパティを使って Scene オブジェクトを取得し、Scene オブジェクトの Passes プロパティを使って PassCollection オブジェクトを取得している、という芋づるになっているのですが、まあこんな理屈は今は無視して、シーンの中にある全 Pass を取得するにはこう書くもんだと覚えましょう。

Return Value は PassCollection です。 Pass オブジェクトが入っている集合体(コレクション)です。ここが重要。 コレクションなので、中に入っている Pass ひとつひとつに対する操作は受け付けてくれません。 なのでループなどの方法で個別の Pass オブジェクトを取り出す必要があります。それさえできれば、Pass オブジェクトに対する色んなプロパティやメソッドが使えるということです。


Pass に対する操作でよく使いそうなのは、

  Pass.CreatePartition メソッド ( Partition を作る )
  Pass.Partitions プロパティ ( その Pass に存在するパーティションをコレクションとして取得 )

とかでしょうかね。

Pass.CreatePartition メソッドの RV( Return Value ) は Partition オブジェクトです。CreatePartition のヘルプページを見ると書いてあります。実行すると Partition が新規で作られるうえに、新規で作られたパーティションは Partition オブジェクトとして取得できちゃうということです。 なので以降この Partition オブジェクトに対する操作は全部自由です。

Pass.Partitions プロパティの RV は PartitionCollection オブジェクトです。Partitions のヘルプページを見ると書いてあります。その Pass に所属する全ての Partition をコレクションとして取得できるということです。取得できたコレクションから Partition オブジェクトをひとつひとつ取り出せば、それはもう Partition オブジェクトそのものを取得できたということです。なので同じく Partition オブジェクトに対する操作は全部自由です。



ってことでサンプルを書いてみます。
超基本から。

var oAllPasses = ActiveProject.ActiveScene.Passes;
for ( var i=0; i<oAllPasses.count; i++ );
{
    var oPass = oAllPasses(i);   
    var oPartitions = oPass.Partitions;

    {
        var oPartition = oPartitions(j);
        Logmessage( oPartition );
    }
}



これを実行すると、シーンに存在する全ての Partiton がスクリプトエディタ上に表示されます。

処理の順番としては、


1.全Pass取得
2.1で取得した全Pass をループし、ひとつの Pass を取り出す
3.その Pass に所属する全 Partitoin を取得
4.3で取得した全 Partition をループし、ひとつの Partition を取り出す
5.取り出したひとつの Partition をスクリプトエディタに表示する
6.3に戻って全部の Partition が終わるまで繰り返し
7.2に戻って全部の Pass が終わるまで繰り返し


こういう感じです。 もうこれで十分な説明になっている気もするのですが、同じスクリプトにいちおう冗長なコメントを入れてみます。


var oAllPasses = ActiveProject.ActiveScene.Passes;    //    全 Pass 取得(コレクションとして)
for ( var i=0; i<oAllPasses.count; i++ ) //    全Pass が入ったコレクションの中身をループ
{
    //    「コレクションの中の i 番目の人」を取得。 oPass の中身はもはやコレクションではなく、特定の Pass オブジェクト。
    var oPass = oAllPasses(i);

    //    特定の Pass オブジェクトにぶら下がる Partition を、コレクションとして取得
    var oPartitions = oPass.Partitions;

    for ( j=0; j<oPartitions.count; j++ )    //    取得できた全Partition が入っているコレクションをループ
    {
        //    「コレクションの中の j 番目の人」を取得。 oPartition の中身はもはやコレクションではなく、特定の Partition オブジェクト。
        var oPartition = oPartitions(j);

        //    スクリプトエディタ上に表示
        Logmessage( oPartition );
    }
}

こういうことになります。
取得したものを、その後どこで使っているのかわかり易いように、色分けもしてみました。 oAllPasses に全Pass をコレクションとして取得したのを皮切りに、次々と取得の連鎖です。芋づるです。



一歩進めて、全 Pass のパーティションに HagePartition というパーティションを作るサンプル。


var oAllPasses = ActiveProject.ActiveScene.Passes;

//    新規で作ったパーティションを入れておく箱=空っぽのコレクションをあらかじめ用意
var oNewPartitions = XSIFactory.CreateObject( "XSI.Collection" );


for ( var i=0; i<oAllPasses.count; i++ )
{
    var oPass = oAllPasses(i);
   
    //    oPass に対し、新規のパーティションを作る。 戻り値は Partition オブジェクトで、oPartition に格納。
    var oPartition = oPass.CreatePartition( "HagePartition", siObjectPartition );
   
    //    oPartition を oNewPartitions という箱に Add (追加) している
    oNewPartitions.Add( oPartition );
}
//    最後に箱ごと選択しておしまい
SelectObj( oNewPartitions );



このようにシーンにいくつか Pass がある状態で、
S51

上のスクリプトを実行すると、
S52
このように、全 Pass で HagePartition が作成され、選択状態になっているのがわかると思います。



では新たに出てきたものを説明します。


  var oNewPartitions = XSIFactory.CreateObject( "XSI.Collection" );

これは、空っぽの箱を作るということです。まだ何も格納されていないコレクションです。呪文のように覚えてしまいましょう。


    var oPartition = oPass.CreatePartition( "HagePartition", siObjectPartition );

これは、oPass に格納されているもの=つまり Pass オブジェクトに対して、CreatePartition メソッドを使って新しいパーティションを作成しています。 カッコの中で指定しているのはパーティションの名前とタイプです。 タイプというのは、オブジェクトパーティションなのかライトパーティションなのかということですね。 ま、CreatePartition のヘルプページを見れば載っています。

そしてこのメソッドの RV は Partition オブジェクトです。つまり新規で作られたパーティションをオブジェクトとして取得できたということです。 以降、Partition オブジェクトに対して何するにも自由です。この例では箱に入れる以外は特に何もやってないですが。


    oNewPartitions.Add( oPartition );

oNewPartitions は XSICollection オブジェクトであり、それに対して Add メソッドを使っているということです。最初に用意した空っぽの箱の中に、新規で作られたパーティションを格納しているということです。
 
なんでこんなことをするのか説明します。

このスクリプトは Pass を1つ1つループしています。 最初の Pass で HagePartition を作成して oPartition に格納し、次の Pass へループが進みます。 すると次の Pass でまた HagePartition を作って oPartition に格納してしまうので、最初の Pass で作られた方の HagePartition は2回目のループの中で上書きされてしまいます。別の言い方をすると、破棄されてしまい、スクリプトの中では永遠に失われます(もちろんシーンの中にできたパーティションが消えるという意味ではありません。スクリプトの中で、そのパーティションを指し示す変数の中身が上書きされるため、最初に格納されたものへの関連は失われるという意味です)。 

で、新規で作ったパーティションは最後に全部、選択状態にしたいじゃないですか。 でも選択したい Partition はループのたびに上書きされるため、ループが終了した時点では oPartition にはループの最後の回で格納された1個しか残っていません。 これはよろしくない。

だから1回ループするごとに、oPartition に格納された新規パーティションを oNewPartitions という箱の中にも格納しておくのです。待避させていると言ってもいいですね。 Add メソッドで 「追加」 しているので、前回のループで格納されたパーティションは上書きされません。どんどんケツに追記されていくことになります。 なのでループが全部終了した時点で、新規で作られた全ての Partition がスクリプト内で失われることなく、箱に入っている状態になります。 だから最後に SelectObj で箱ごと選択することができます。

これ、すんごくよく使うパターンなので、覚えておいて損は無いと思います。



あ、ここで勘違いしやすいので注意した方が良いと思われるのは、オブジェクトモデルのメソッドとしての CreatePartitoin と、コマンドの CreatePartition の混同です。

上の例で使ったのは、Pass オブジェクトに対するメソッドとしての CreatePartition です。 でも XSI にはコマンドの CreatePartition もあるんですよ。 わかりにくいですね。 区別できる名前を付けりゃいいのねえ。

オブジェクトモデルのメソッドは、 オブジェクト ドット メソッド という書き方になります。まずオブジェクトの部分が取得できていることが前提になります。 今回の場合は Pass.CreatePartition( うんたらかんたら ) であり、Pass オブジェクトが取得できていることが前提です。 これに対しコマンドの方は、Pass を必ずしもオブジェクトとして取得していなくても、名前さえわかれば実行できてしまうという性質のものです。 オブジェクトモデルとコマンドの違いを解説するのが主旨ではないので詳説はしませんが、今回この記事で扱っているのはオブジェクトモデルのメソッドである CreatePartition であり、「CreatePartition コマンド」とは全く別物だということだけ留意しておいて下さい。 特にヘルプページを見るときに注意です。 間違ってコマンドの CreatePartition の方を読んでいたりしたら、この記事に書いてあることが意味不明になり、デタラメを書くななどと junki にクレームを出し、俺は怒ってモニタをあなたの頭上めがけて遠投するような事態に容易になり得るからです。




さ、戻りましょう。
さらに一歩進めて、 「ユーザが選択しているオブジェクトを、全 Pass の中の特定のパーティションに入れる。 もしそのパーティションが無かったら、新規で作る」 というのを書きましょう。



// ぶち込みたいパーティションの名前。 ここを書き換えて下さい。
PartitionName = "HagePartition";

var oNewPartitions = XSIFactory.CreateObject( "XSI.Collection" );
var oAllPasses = ActiveProject.ActiveScene.Passes;

for ( var i=0; i<oAllPasses.count; i++ )
{
    var oPass = oAllPasses(i);
    var oPartitions = oPass.Partitions;

    //    oPartitions というコレクションに入っているパーティションのうち、
    //    PartitionName に入っている文字列と名前が一致するパーティションを、
    //    oTargetPartition に取得している

    var oTargetPartition = oPartitions( PartitionName );

    //    取得したものが null かどうかを調べている。 null だったら=空っぽ=存在しない=じゃあ新規で作りましょう
    if ( oTargetPartition == null )
    {
        var oTargetPartition = oPass.CreatePartition( PartitionName, siObjectPartition );
        Logmessage( "そんな Partiton はないから作ってやりますた。 --> " + oTargetPartition );
    }
   
    //    この時点で、上の if を通っているので、必ず PartitionName の名前の Partition が存在し、
    //    oTargetPartition に格納されている。 安心して Partiton にメンバーを追加できる。
    //    Partition オブジェクトのメソッドのひとつ、AddMember を使って選択中のオブジェクト(Selection)をぶち込んで終了。

    oTargetPartition.AddMember( Selection );
   
    oNewPartitions.Add( oTargetPartition );
}
SelectObj( oNewPartitions );



コード中のコメントで説明し切っちゃってるので冗長になりますが、いちおう説明します。

冒頭の PartitionName の中に入れる文字列が、パーティションの名前になります。ここを自由に書き換えてください。 ただの文字列でありオブジェクトではないので、変数名に o は付けていません。

その後しばらくは前のサンプルで出てきたので説明不要ですよね。 カラのコレクションを用意し、全 Pass をループしています。


次のここはポイントです。

    var oTargetPartition = oPartitions( PartitionName );

オレンジの oPartitions は、「その Pass に所属する全 Partition 」が入っています。コレクションです。 そして、そのコレクションの中から特定の名前を持つものだけを抽出しようとしています。 特定の名前とは、最初にユーザが自由に書き換えた文字列 PartitionName です。  このように、コレクション( 特定の文字列 ) という書きかたをすると、コレクションの中でその名前を持つものだけが取得できます。 今まで何度も コレクション( 数字 ) という書き方は出てきましたよね。 Selection(0) とか、oModels(1) とかです。これって、コレクションの中の「何番目」という言い方で、つまり格納順の番号(インデックス)で指定していたことになります。  今回は番号の代わりに、「番号は何番目でもいいから、この名前のやつを出せゴルァ」 と言っているわけです。 インデックスを使っても名前を使っても、いずれにせよ特定のひとりを指名したんだから、取得できたものはもはやコレクションではありません。 こうして特定の1つの Partition が oTargetPartition に取得できました。 しかし・・・・

ちょっと待った。 本当に取得できたのでしょうか?

ユーザが指定した名前のパーティションがすでにシーンに存在していれば、当然取得できます。しかし存在してなかったら、取得できないですよね? できないんです。当たり前です。ありもしないものは取得できません。 なので、その名前のパーティションが存在するかどうかを調べているのが、次の if ブロックです。

    if ( oTargetPartition == null )
    {
        var oTargetPartition = oPass.CreatePartition( PartitionName, siObjectPartition );
        Logmessage( "そんな Partiton はないから作ってやりますた。 --> " + oTargetPartition );
    }



前の行では、その名前のパーティションがすでにあるかどうかに関係なく、強引に oTargetPartition に格納していました。 もしその名前のパーティションが無かった場合は、oTargetPartition には何も入りません。 何も入ってないという状態は、 「無」 が入っていると同じ意味になります。 なので JScript の場合は null が入っているかどうかで判断することができます。 この null とは、ビューポート上に現れるヌルのことではなく、プログラミング的に「無」とか「無効」とかを表す null です。 なので上のように、oTargetPartition の中身が null かどうかを if で判断させて、null だった場合はそんな名前のパーティションは存在しなかったということになるので、ならば新規で作りましょうということで、先ほど出てきた CreatePartition メソッドを使ってユーザが指定した名前のパーティションを作り、あらためて oTargetPartition に代入しています。

ちなみに、あるオブジェクトが存在するのかどうかを判定するには、今回使った if ( xxx == null ) という書き方以外にもいっぱい方法はあります。 今回はたまたまこれを使ったというだけです。 また、VBScript や Python ではこの辺の書き方が違ってくると思います。 この辺はいずれまた別記事で書くかもしれません。 書かないかもしれません。


ってことで、 既に存在していた or 無かったので作った というブロックを越えたので、この時点で確実にその名前のパーティションが存在していることになります。 そしてそのパーティションは oTargetPartition に格納されています。 なので、Partition オブジェクトに対するメソッドのひとつ、AddMember を使って、Selection を Partition にぶち込んでいるのが次の行です。

    oTargetPartition.AddMember( Selection );

Partition.AddMember は、ユーザが Partition に入ることの出来ない変なもの(例えば Curve )を選択していても特にエラーは出しません。無視してくれます。 なのでいちいちエラー処理を書いていません。 でも、ライトなのかそれ以外のオブジェクトなのかという問題がありますよね。 そこら辺の判定を入れたり、パーティションの名前はスクリプトの冒頭を書き換えるのではなく PPG を表示して入力を促すなどのブラッシュアップを行うと、より実用的なツールになると思います。  っていうかそういうスクリプト、もう何年も前に書いたものがあります。 そのうち整理して公開するかもしれません。しないかも知れません。

えーと、後は同じく箱に待避させて最後に箱ごと選択、で終了ですね。 はい終了。 一応実行してみます。


Pass が複数あり、cone と cube を選んでいる状態で実行します。 HagePartition はどの Pass にも存在しません。
S55

.スクリプトを実行すると、
S56
各 Pass に HagePartition が作られ、今まで Background_Partition に入っていた cube と cone は HagePartition に入れられ、そして全ての HagePartition が選択状態になっているのがわかると思います。 わかるでしょ。 わかれよ。




ついでにもう一個書いちまおう。先ほどのスクリプトをちょっと改造しただけです。

if ( Selection.count != 0 )    //    何か選んでないと名前をゲットできないからね。
{
    //    ユーザが最初に選んでいるものから名前をゲット
    PartitionName = Selection(0).name;

    var oAllPasses = ActiveProject.ActiveScene.Passes;
    var oTargetPartitions = XSIFactory.CreateObject( "XSI.Collection" );

    for ( var i=0; i<oAllPasses.count; i++ )
    {
        var oPass = oAllPasses(i);
        var oPartitions = oPass.Partitions;
       
        //    ユーザが選んでいたオブジェクトと同じ名前を持つパーティションをゲット
        var oTargetPartition = oPartitions( PartitionName );
       
        //    null じゃなかったら、存在しているということになるので、oTargetPartitionsコレクションに追加
        if ( oTargetPartition != null )
        {
            oTargetPartitions.Add( oTargetPartition );
        }
    }
    //    最後にコレクションをまるごと選択し、ついでに PPG を表示させておしまい。
    SelectObj( oTargetPartitions );
    InspectObj( oTargetPartitions );
}
else
{
    Logmessage( "何か選んどけやドルァ", siError );
}



何かパーティションをひとつ選んで実行すると、全 Pass の中で同じ名前を持つパーティションを探し、選択状態にして、かつ PPG を表示するというスクリプトです。 すでに Pass がいっぱい存在していて、同じ名前のパーティションがいっぱいあり、そいつらをイッキにハイドにしたいだとかなんとか、そういう時に使えるスクリプトじゃないですかね。


ポイントになる部分だけ説明すると、

  if ( Selection.count != 0 ) 

まずユーザが何も選んでいない状態で実行すると、探すべき名前がゲットできないので、エラーを出して終了にしています。 何か選ばれているかどうかは Selection の Count の数字を調べればわかります。 Selection というコレクションに入っているアイテムの数、つまり count が 0 以外だったら何かが選ばれているということだから次のブロックを実行しろ(正常実行)、  さもなくば( 0 だったら)何も選ばれていないということなので、else 以下を実行しろ(異常終了) という書き方です。 A != B という書き方は、JScript では 「 A と B が一致していない 」 という意味です。 びっくりマークは「否定」を表すんです。 びっくりイコールと書いているので、イコールを否定している、つまりイコールじゃない、という意味になります。



  if ( oTargetPartition != null )

ユーザが選んでいたオブジェクトの名前と一致するパーティションを oTargetPartition に格納したので、その中身が null かどうか、つまりそんな名前のパーティションが存在するのかしないのかを調べています。 ここでもびっくりイコールです。 null じゃなかったら、つまり存在していたということになるので、次の行以降でコレクションに格納しておきます。



    InspectObj( oTargetPartitions );

最後に InspectObj コマンドで PPG を表示させています。 1つのオブジェクトを InspectObj コマンドに食わせた場合は当然そのオブジェクトの PPG が開きますが、今回はコレクションを食わせているので、コレクションの中身全員に対するマルチモードの PPG が自動で立ち上がります。便利です。

一応やってみましょう。
以下のように、複数の Pass がある状態で、Background_Partition をひとつ選択しておきます。
S510

この状態でスクリプトを実行すると、
S511
はい、全ての Pass 以下にある Background_Partition が選択され、かつマルチな PPG も立ち上がっています。 なかなか使えるじゃないか。



ちなみにですが、ユーザが選んでいたものがパーティションかどうかのチェックはしていません。 つまり、Group だろうが Kinematics だろうが、何か1つ以上選んでさえいれば、このスクリプトは走ります。 選んでいたオブジェクトの種類は問わずに名前をゲットして、それに一致する名前のパーティションを探しているということです。 もしユーザが最初に選んでいるオブジェクトをパーティションのみに制限したいのであれば、Selection(0) の type をチェックして Partition じゃなければエラー出して終了するような書き方にすれば良いでしょう。





ふーー。 ひとまずネタが尽きた。今回の攻略は終了します。全然攻略してないけど。 表面だけの気がするけど。 知りません。





あ、もうひとつ注意しなければいけないのは、この記事で書いた Partition がらみのことって、XSI 7 の新機能なんですよね。 XSI 6 の頃は Partition という名前のオブジェクトは存在しておらず、したがって Pass.CreatePartition メソッドも、Pass.Partitions プロパティも、Partition.AddMember メソッドも存在していませんでした。 なので上記のスクリプトは XSI の七ちゃん以降でしか動かないはずです。 昔はしかたなく、コマンドの CreatePartition や MoveToPartition を使っていました。 今はすげえラクになっています。






うむ、しんどい。
ごきげんよう。




.

| | コメント (0) | トラックバック (0)

2010年10月28日 (木)

取得 その4。

久しぶりに取得です。 FindChildren 攻略です。



「現在開いているシーン内に存在する、全ての Model を取得する」 というスクリプトを書いてみましょう。

    var oModels = ActiveSceneRoot.FindChildren( "", siModelType );
    Logmessage( oModels.GetAsText() );
    SelectObj( oModels );

以上。

S41
シーンの中に存在する全ての Model がセレクトされました。Logmessage によってスクリプトエディタ内に表示もしています。


ActiveSceneRoot
は何度も出てきていますが、現在開いているシーンのシーンルートを表すものです。シーンルートの名前を変える人もあまりいないとは思いますが、名前を変えようと何だろうと、シーンルートが取得できます。

で、その後にドットでつないで FindChildren メソッドを使っています。 この呪文はその名の通り、子供たちを発見する呪文です。 hogehoge.FindChildren( hagehage )  と書くと、「hogehoge の子供のうち、hagehage という条件を満たす奴らを取得しやがれ」と言っていることになります。 スクリプトエディタ上で FindChildren を選んで F1 を押してヘルプを見てみましょう。
S42 全てはここに書いてあります。読みましょう。


余談ですが、スクリプトエディタ上で FindChildren を選んで F1 を押した時は、いきなり FindChildren のページに飛ぶので、左側の目次項目とは一致していない状態になります。ここで、ヘルプページの上の方にある「同期」ボタンを押すと、目次項目が表示中のページに同期されます。すげえ便利です。必ず同期ボタンを押す癖を付けると良いと思います。 なぜならば、自分が今見ているヘルプページは SDKガイド上のどの項目以下にあるのかがわかると、スクリプティングの構造の理解が早まるからです。 「ああ、プロパティかと思っていたら、これはメソッドだったのか」 とか、 「オブジェクトモデルのメソッドのつもりでヘルプ開いたんだけど、これってコマンドだったのか。 じゃあメソッドとしては用意されてないのかな?」 とかなんとか考えるので、確実に理解の手助けになります。俺は必ずやってます。


本題に戻って、FindChildren の説明をします。
FindChildrenメソッドのカッコの中身、つまり取得すべき子供の条件ですが、最初のは名前です。 こういう名前の付いたやつを探せ、と言っています。 今回は "" としています。ダブルクォーテーション2連発。つまりカラの文字列。カラの文字列の場合、「名前は何でもいいぜ」と言っていることになります。

次は siType です。  siModelType としているので、これはいわゆる Model です。 Model と呼ばれる type のものを見つけろと言っていることになります。 

これも余談になりますが、siModelType の代わりに "Model" という書き方でもOKです。こちらはダブルクォーテーションで囲っているので「ユーザが指定した文字列」になるわけですが、その文字列で表される type のものを見つけろと言っていることになり、結果は同じです。 siModelType には XSI 側で予約された固定の文字列が入っており、 この場合 "Model" が入っていると考えていいと思います。よって結果は同じであり、どっちを使ってもかまいません。 俺の場合は、ダブルクォーテーションが多いとソースが見づらいと感じるので、siXXXXX という言い方がわかっている場合にはなるべくそちらを使うことにしてますが、まあ気分です。


ってことでもう一度見てみると、

       var oModels = ActiveSceneRoot.FindChildren( "", siModelType );

    現在開いているシーンのシーンルートの、
    子供を探せ。
    名前は何でもいい。
    ただし Model という Type の子供だけ探せ。
         そして見つかった子供たちは、 oModels という変数にブチ込んでおけやドルァ

と言っていることになります。色分けを見ると、どの部分で何を指令しているのかわかると思います。

ちなみに3つめの条件として、Family という条件を追加することもできます。 ヘルプを見ればわかると思います。 ここでは3つ目を何も書かず省略しているのでデフォルト設定 = Family はなんぜもいいぜ、が使われます。 また、4つめの条件として 「階層の一番下まで探すかどうか」 も指定できます。これは後ほど説明します。




以下に、FindChildren を使う上で留意しておいた方が良いと思われることを、思いついただけ列挙します。



●X3DObject にしか効かないぜ

上の FindChildren のヘルプページ画像を見てみましょう。 冒頭のタイトルの部分に FindChildren ( X3DObject ) と書かれています。また左側の目次のところにも同様に  FindChildren ( X3DObject ) と書かれています。 Scripting Syntax つまり文法の説明のところにも、 X3DObject.FindChildren ( うんたらかんたら ) と書かれています。 これらは皆、

「 FindChildren というメソッドは X3DObject に対して実行できるものなんだぜ、だからまずは X3DObject を取得して、それに対して実行しなければいけないんだぜ」 

ということを表しています。 X3DObject とは以前書いたとおり、ビューポート上に実体がある( SRT を持つ )オブジェクトのことです。

繰り返します。 まずは X3DObject を取得し、そこを基点にして FindChildren しなければいけないのです。

じゃあどうやって X3DObject を取得するのか? 上のスクリプトの場合は、ActiveSceneRoot を使って、シーンルートという Model を取得しました。Model はビューポート上に実体のあるオブジェクトなので( 見た目は Null と同じ )、X3DObject です。 他にも取得の方法はいくらでもあるので、その時々によって最適な方法で取得すれば良いのです。 後半、「オブジェクトとして取得する」 のところでもう少し解説します。



●取得できるのも X3DObject だぜ ただしコレクションだけどな

FindChildren のヘルプページの下の方に、Return Value(RV) が書かれています。 Return Value とは戻り値、つまりこのメソッドによって返ってくる値のことです。別の言い方をすれば、このメソッドを使った時に取得できるものは何なのか? が Return Value です。
S43
FindChildren の RV は X3DObjectCollection です。 これはその名の通り、X3DObject のコレクション(集合体)です。 X3DObject が入っている箱と考えてもいいでしょう。 ってことで、X3DObject に対して FindChildren を使うと、X3DObject が入った箱が取得できるということがわかりました。

で、ここで重要なのは、コレクションはただの箱(入れ物)であり、中にある個々の X3DObject に対する操作を引き受けてくれるわけではないということです。 ミスタードーナツでドーナツをいっぱい買いました。 金を払って渡してもらえるのはドーナツが入った箱です。 中のドーナツを食いたいのですが、箱ごと食うことはできません。つまり DonutBox.Eat というメソッドは使えません。 なのでドーナツを食おうと思ったら、つまり Eat メソッドを使おうと思ったら、ドーナツを1個1個取り出して、Donut(0).Eat などとやらなければいけないということです(この場合、コレクションの中からゼロ番目=最初のドーナツを取り出し、この特定の1個を食うという書き方になってます)。

前回は Children プロパティを題材にしてこれと全く同じ内容の説明を書きましたが、大事なポイントなのでまた書いてます。

上の Model 取得スクリプトで言えば、例えば取得できた各 Model に新規で Null をぶら下げたいという時、oModels.AddNull( ) ではダメです。 oModels に入ったのはコレクションなんだから中にある個々の Model に対する操作は受け付けてくれません。なので、oModels(0).AddNull( ) とか oModels(1).AddNull( ) などと書くことによって、コレクションの中の特定の一人を指定しなければいけないのです。普通はループの中に入れて oModels(i).AddNull( ) などとやることになるでしょう。

ちなみに、X3DObjectCollection はその中のひとつひとつを取り出さないと何も出来ない、という意味ではありません。コレクションにはコレクションに有効なメソッドがありますし、コマンドと組み合わせても良いわけです。 今回の例では Model を全部取得してそれを Logmessage したり SelectObj したりしているので、コレクションの中のひとつひとつを取り出してはおらず、コレクションのまま扱っています。これはもう、スクリプトの目的によりけりです。



●コレクションを格納する変数の名前は、複数形を推奨するぜ

上でコレクションに関する話題を書いたので、FindChildren 限定ではないのですが、コレクションを格納する変数の名前に関する話をします。

コレクションは集合体なので、格納する変数の名前は s を付けるなど、複数形にしておくことを推奨します。

例えばこんなスクリプトを考えてみます。
  var oModels = ActiveSceneRoot.FindChildren( "", siModelType );
  var oModel = oModels(1);
  var oGroups = oModel.Groups;
  var oHageGroup = oGroups( "HageGroup" );
  var oMembers = oHageGroup.Members;
  MemberCount = oMembers.count;
  Logmessage( MemberCount );

まさに取得の連鎖=芋づるですね。 ま、それはここではどうでも良くて、名前に関する説明を以下でします。

1行目、oModels には FindChildren の結果=つまりコレクションを格納しています。oModels という複数形の名前を使うことによって、これはコレクションなんだぞ、と自分で意識できます。

2行目、oModel という変数の中に、oModel(1) つまり oModels の中に入っている2番目のアイテムを格納しろと言っています。2番目という特定のひとりを指しているので、結果はもうコレクションじゃありません。 なので変数の名前は複数形の s を取っ払って単数形の oModel にしています。 こいつはもうコレクションじゃねえぞ、だからコレクションに対するメソッドじゃなくて個々のオブジェクトに対するメソッドしか効かないぞ、と意識できます。

3行目、oModel という特定の1つの Model 以下にあるグループを全部取得しています。Groups というプロパティを使っているからです。戻り値は GroupCollection です。 グループがいくつあるのかはわからないので、実際に存在するグループの数に関わらず、Groups プロパティの戻り値は常にコレクションです。 なので変数名は複数形で oGroups としています。コレクションだぞ、と意識できます。

4行目、oGroups の中にある "HageGroup" という名前の特定のグループを指定しています。同じ名前のグループはひとつしか存在できず、結果として特定の1つのグループを指定していることになります。つまり、もはやコレクションではありません。なので変数名は複数形ではない oHageGroup にしています。 コレクションじゃなくて個々だから、個々に対するメソッドしか効かないぞと意識できます。

5行目、oHageGroup に取得した特定の1つのグループに対し Members プロパティを使っています。グループのメンバーを全部取得するプロパティです。戻り値は SceneItemCollection です。メンバーが何人いるかはわからないので、実際のメンバー数に関係なく Members プロパティの戻り値は常にコレクションです。なので変数名は oMembers という複数形にしています。 コレクションだぞと意識。

6行目、oMembers というコレクションに対し、count というプロパティを使っています。コレクションの中に格納されたブツの数を教えてくれるプロパティです。1 とか 99 など、「数」を戻すだけなので、戻り値は数字であり、もはやオブジェクトですらありません。なので変数名に o を付けていません。
var を付けるのかどうかは、まあこの場合はどうでもいいでしょう。この変数を複数回リサイクルするのであれば、初期化のために付けるべきなのでしょうが、まあ今回の説明の主旨とは関係ないので、ほっときましょう。

7行目はまあ、どうでもいいです。得られた結果を Logmessage で表示しているだけです。



・・・・ってな感じで、自分が取得したものはコレクションなのかそうでないのか、ということを明確に意識しながらコードを書ければ、変な間違いを起こしにくくなります。また、後から自分でソースを見たときにわかりやすいです。 なので変数名は単数形と複数形を厳密に使い分けるのがおすすめであります。 FindChildren に限った話ではないですが、結構重要なことだと思っているのでここで書きました。 俺の場合、昔はこれを全く意識してなかったため、そりゃもうわかりにくい、ひどいコードばかり書いてました。最近はなるべく気にしているので、昔よりはロジカルで後から見てもわかりやすいコードを書いていると思います。少しですけどね。

また、単数形複数形の話ではないですけど、上の Count プロパティの例のように、自分が取得したものが「オブジェクト」なのか、単なる「数値」なのか、はたまた単なる「文字列なのか」 などを意識して、変数名の頭に o を付けるかどうかなどを決めています。 この辺は好みもあるでしょうが、取得しているものは何なのだ? を常に把握するという意味では、結構重要なのではないかと思っています。



●階層の一番下まで探すぜ

デフォルトだと FindChildren は直近の子供だけでなく、孫や曾孫など階層の一番下までくまなく探してくれます。 4つめの引数、recursive のデフォルトが true なので、「再帰的に」 探すわけです。つまり、階層の中を全部探して、見つかったものは全部取得するということです。 ここを false にすると、直近の子供しか探しません。

今回の場合はシーンルートを基点にしているので、結果的にシーン全体から探すことになります。なぜならば、シーンに存在するあらゆるオブジェクトはシーンルート以下のどこかにあるからです。シーンルート以下じゃない場所に存在するオブジェクトはありません。



●自分自身も含むんだぜ

FindChildren で取得されるものは、条件が一致すれば基点となるオブジェクトも含みます。上の例ではシーンルートが基点になっており、siModelType を取得しろと言っていて、そして基点であるシーンルートも Model なわけです。 条件が一致するので、結果シーンルートも oModels に入ります。

もしこんな状態で、
S45_2

以下のように書けば、

    var oLights = ActiveSceneRoot.FindChildren( "", siLightPrimType );
    Logmessage( oLights.GetAsText( ) );
    SelectObj( oLights );

以下のような結果になります。
S46

「シーンルート以下で、名前は何でもいいから、siLightPrimType つまりライトを取得しろ」 と言っているので、ライトが検索され、取得されました。 シーンルートはライトではないから、oLights に格納されません。 なのでシーンルートはその後の Logmessage による表示にも現れないし、SelectObj で選択された結果にも入っていません。

それに対し、以下の画像のように
S47
ライトが1つ選択されている状態で、以下のスクリプトを走らせます。

  var oLights = Selection(0).FindChildren( "", siLightPrimType );
  Logmessage( oLights.GetAsText( ) );
  SelectObj( oLights );

1行目、ActiveSceneRoot の代わりに Selection(0) を使いました。「ユーザが選んでいる最初のもの」という意味です。そして今回はライトを選択した状態でこのスクリプトを走らせたんだから、
S48
こうなります。
基点となっていたライトも含まれているのがわかります。

以上、FindChildren は、条件が合えば基点のオブジェクトも含みますよ、という補足説明でした。



●しかし FindChildren2
もあるぜ

FindChildren は自分自身を含むと書きましたが、SDK ガイドを読んでたら 2011 で新たに FindChildren2 というメソッドが新設されていまして、これを読むと、「違いは自分自身を含むかどうかだけ。FindChildren2 では含まないよ。でも前に書いたスクリプトが 2011 で動かなくなっちゃ困るから従来の FindChildren も残しておいてやるぜドルァ」 だそうです。 2011SP1 と 2011AP のマニュアルにはそう書いてあります。 最初の 2011はインストールすらしていないので未確認。 2010 では確認してませんが、2011 の新機能の所に書いてあったので 2010 には無いはずです。

ということなので、2011 以上の人は上のスクリプトの FindChildren の部分を FindChildren2 に書き換えて走らせてみましょう。自分自身が含まれないのがわかります。



●シーンルートではなく、特定のオブジェクト以下で探そうぜ

上のライトの例では Selection(0) を基点に、それ以下の階層を探しました。 このように特定のオブジェクトの階層以下だけを捜索したいときは、

    hoge.FindChidren( "", siModelType );

です。 この hoge の部分を、特定のオブジェクトに書き換えればいいのです。 上の例では hoge の部分が Selection(0) だったので、特定の「名前」のオブジェクトではなく、「ユーザが選んでいるオブジェクト」という特定のオブジェクト以下で探すということになります。

ま、最初の例でも同じです。 hoge の部分がシーンルートだったわけで、シーンルートも「特定のオブジェクト」だから、なんら差はないです。 hoge 部分をどのようにでも書き換えられまっせ、ということが言いたいのですよ。

しかし・・・・(以下に続く)



●まずは基点になるオブジェクトを、オブジェクトとして取得しないとダメだぜ

ここで重要な問題があります。
hoge の部分は、単に XSI シーン内での「オブジェクト名」を書けばいいのでしょうか? 

例えば、"hogeNull" という名前の付いた Null があって、その子供のうち、名前に "Hage" という文字を含むオブジェクトを取得したいとします。

S410
上の画像のように、確かにシーンには hogeNull という名前のオブジェクトが存在しています。 そしてその子供として Hage で始まる4つのオブジェクトが存在します。

なので、

  var oHages = hogeNull.FindChildren( "Hage*" );
  SelectObj( oHages );

こう書きました。

"Hage*" の "*" は、ワイルドカードですね。Hage に続く文字は何でもいいぜ、と言っていることになるので、結果的に、名前が Hage で始まるオブジェクトを全部取得しろと言っていることになります。

で、走らせたらエラーです。
S411_2
なぜでしょう?

答えは、hogeNull がいかんのです。 hogeNull という名前のオブジェクトを、まずはプログラミング的な意味でのオブジェクトとして取得しないことには、XSI 様は何もできないのです。 hogeNull という名前のオブジェクトはシーンの中に厳然として存在しますが、その名前だけをスクリプトに書いても「取得」にならないのです。
エラーメッセージを見てもわかりますが、上の書き方だと、あたかも hogeNull という名前の変数に何かが取得できていて、それに対して FindChildren を実行しているように見えます。 でも実際には、hogeNull なんていう変数はそれ以前にどこにも出てきていません。

なので、まずは hogeNull という名前のついたシーン内のオブジェクトを、プログラミング的な意味でのオブジェクトとして取得して変数に代入しておくことが前提になります。これをやって初めて、XSI 様は FindChildren の基点になるオブジェクトが誰なのかを特定できるのです。

ということで、このように書きます。

  var oObj = Dictionary.GetObject( "hogeNull" );
  var oHages = oObj.FindChildren( "hage*" );
  SelectObj( oHages );

赤字の1行目が重要なわけです。 Dictionary.GetObject は、Dictionary オブジェクトに対する GetObject メソッドなわけですが、今は詳しく解説しません。 ともかく、こう書くことによって hogenull という名前のオブジェクトが、プログラミング的な意味でのオブジェクトとして、oObj の中に取得できます。

取得できちまえばこっちのもんです。FindChildren メソッドが使えます。やっとエラー無く走りました。


今回は FindChildren を例に取りましたが、オブジェクトモデルによるスクリプティングでは何でもこれと同じルールになります。つまり、まずは基点にしたいオブジェクトをプログラミング的な意味でのオブジェクトとして取得する。これが大前提です。 基点にしたいオブジェクトの名前がわかっていても、その名前をスクリプトの中にただ書くだけでは、それは取得ではありません。 よって名前に直接ドットでつないでメソッドやプロパティを書くのはダメなんです。 名前がわかっているのであれば、その名前を Dictionary.GetObject などに食わせることによってオブジェクトとして取得する。これです。 取得取得と何度も言っていますが、取得の本当の意味はまさにこれです。

Dictionary.GetObject は、基点にしたいオブジェクトの名前がわかっている時には便利な方法ですが、取得の方法は他にいくらでもあります。ActiveSceneRoot と書けば一発でシーンルートを取得できますし、Selection(0) と書けば選択している最初のものを一発で取得できます。 このように、基点になるものを何らかの方法で取得できれば、それ以降は取得したオブジェクトに応じたメソッドやプロパティが使えるので、芋づるになります。



●カンニング例だぜ

FindChildren の例を、思いついたままにいくつか書きます。


//  シーン全体で、ライトを全部取得して、選択する。名前は問わず。

var oLights = ActiveSceneRoot.FindChildren( "", siLightPrimType );
SelectObj( oLights );



//  シーン全体で、名前もタイプもファミリーも問わず、すべての3Dオブジェクト( X3DObject )を取得し、選択する。
var oAllX3DObjects = ActiveSceneRoot.FindChildren( );
SelectObj( oAllX3DObjects )

※カッコの中の条件を何も書かないと、シーン内にある3Dオブジェクト全部が取得されます。



//  シーン全体で、ジオメトリがあるオブジェクトを全部取得し、選択する。名前やタイプは問わず。

var oAllGeoObjects = ActiveSceneRoot.FindChildren( "", "", siGeometryFamily );
SelectObj( oAllGeoObjects );






//  現在選択している全てのオブジェクト(複数可)の子供のうち、名前が HageHage で始まり、
//  かつタイプが Null のものを取得し、その数をスクリプトエディタ上に表示する。

for ( var i=0; i<Selection.count; i++ )
{
    var oHageHageNulls = Selection(i).FindChildren( "HageHage*", siNullPrimType );
    Logmessage( oHageHageNulls.count );
}


// 最初に選択されたオブジェクトの子供のうち、タイプが Model のものだけ取得し、
// 取得できた Model の数を表示する。
// 選択していたもの(自分自身)を数に含めたくないので、
// ゼロだったらそのまんま、ゼロじゃなかったら自分自身を差し引いて表示する

var oModels = Selection(0).FindChildren( "", siModeltype );
if ( oModels.count == 0 )
{
    ModelCount = oModels.count;
}
else
{
    ModelCount = oModels.count - 1;
}
Logmessage( Selection(0) + " 以下にある Model の数は " + ModelCount );
"




うーむ、あんまりいいカンニング例ではないなあ。サンプルを作るって難しい。
まあいいや。





ふうーーー  なかなかしんどい・・・!
まだ続くのか? 知りません。





.

| | コメント (0) | トラックバック (0)

2010年10月27日 (水)

群集じかけのオレンジ。

あら、またエリックさんが素敵なビデオを。

emFlock 1.10 - Flockwork Orange from Eric Mootz on Vimeo.

エロい。実にエロい。

emFlock が 1.0 から 1.1 にバージョンアップしたそうで、でもこのビデオはバージョンアップのデモというわけではないのかな、まあ emFlock そのもののデモなんでしょうね。


バージョンアップ内容は、スピードアップ&精度アップ、メインのコンパウンドの整理、ちょっとしたバグ修正 とありますね。 あと、下位互換性がある=つまり以前のバージョンで作ったシーンも、この新バージョンでちゃんと動くそうです。


同じく Vimeo には emFlock の親切なチュートリアルビデオもアップされてますね。さっき初めて見ましたが、分かり易いです。 XSI ではありがちですよね、「最初はまずパッと基本的な状態で出てきて欲しいのに、ちゃんと分かってパラメータいじってあげないと、様子見のレンダリングすらできない。ビューポートにすら出てこない」 みたいなツールやシェーダ。 BA とかまさにそれ。 でもこの emFlock は、とりあえずパパッとフロックな状態に持っていくことができそうなので、見ていて安心します。いじる気にさせてくれるってのは重要ですよね。

emFlock のダウンロードページにも、エロいビデオがいくつかありますね。 エリックさん、いつもデモビデオの作り方が上手いなあと思います。フェロモンが出ているんですよね。技術的なデモではなく、フェロモンが漂うアートなデモにしたいのだと思います。  ま、もうちょっと実用的と言うか具体的というか、抽象的ではないモノを使ったデモも入れて欲しいですけどね。


レンダリングは大幅にアーノルド君を使っているようですね。なにやら綺麗だなあ。1枚5~6分のレンダリングか。 俺は GI なレンダリングをあまりやったことないのでよく分からないんですが、たぶんかなり速いですよね。 シミュレーションは秒に4~5フレームくらいのようですね。これは速いんだかどうかよくわからん。




ってことで em シリーズをまだひとつも買っていない俺ですが、うーむ、いじりてえ。 年内いっぱいは無理かなあ・・・。 うーむ。。。。




.

| | コメント (0) | トラックバック (0)

2010年10月17日 (日)

取得 その3。

今日も取得します。


前回シーンルートを取得したので、それに続く形で何か書けないかなーと思っていた矢先、何やら前の書き込みに質問があったので、しかたなくそれに呼応する形で取得してみましょう。

しかし、どうやら質問者の野郎は根本的に何かわかっていないようで、そして何がわかっていないのか、どこを勘違いしてるのかをこっちが理解できないくらい理解してないようなので、必ずしも質問にズバリ答える形ではありません。無理です。なので関連があると思われることを勝手に書くだけです。贅沢言うんじゃねえぞドルァ





では、ユーザが現在選択しているシーンオブジェクトの直近の子供を選択するスクリプトを書きます。

   var oSel = Selection(0);
   var oChildren = oSel.Children;
   SelectObj( oChildren );



はい、完成。 こんだけ。 以上です。

こんな状態から実行してみましょう。
Shutoku301

cone を選択してこれを実行してみると、 cone の直近の子供である cube1, cube2, cube3 が選択されます。
Shutoku301b
cube1 と cube2 にもそれぞれ子供がいますが、cone から見ると直近の子供ではないので、選択されません。 以上。完成。終わりです。


もしこれをもっと短く書こうと思ったら、

  SelectObj( Selection(0).Children );


でもいいです。 この1行だけで、上の3行と全く同じ働きをします。 でもまあそれは後の話。



3行バージョンについて解説します。

まず1行目。 現在ユーザが選んでいるものを取得する。 それが Selection です。 もう、そういうもんだと覚えちゃいましょう。

厳密には XSIApplication の Selection プロパティを使って Selection オブジェクトを取得する、書き方は Application.Selection である、とかいうことになるみたいなんですが、まあどうでもいいでしょう。今は忘れましょう。 Selection とだけ書きゃいいんです。 でももしかして Python だと Application を省略出来ないかな? どうなんでしょう。 JScript の場合は Selection とだけ書けばOKです。


で、ユーザは複数のものを選んでいるかも知れません。Selection と言った場合には1つかも知れないし、100個かも知れないわけです。なので、ユーザがいくつ選んでいようと関係なく、「最初に選んだもの」 を表すために、 Selection(0) と書きます。 Selection の中身は Collection つまり「複数あるかもしれない集合体」なわけですが、その集合体の中の何番目のものを取得するのかでカッコの中の数値を変えます。Collection のスタート番号はゼロです。1ではありません。 なので最初に選んだものは Selection(0) になり、2番目に選んだものは Selection(1) になり、10番目に選んだものは Selection(9) になります。 今回の場合、「ユーザが最初に選んだものの子供をゲットする」という仕様ですので、 Selection(0) にしています。 ちなみに、ユーザが1つしかモノを選んでいない場合はカッコを省略して Selection という言い方をすれば最初に選んだものを取得できるかというと、そうは行きません。Selection は、たとえユーザがひとつのモノしか選んでいなくても常に集合体として返すので、たとえ集合体の中に1つしか入っていないことがわかっていても、やはりカッコで番号を特定してやる必要があります。


はい、ということで oSel という変数の中にユーザが最初に選んでいるものが取得できました。 そして、取得できた cone とはどういうものなのか? を考えます。

今回取得した cone は、 X3DObject です。 SDK Explorer で確認してもわかります。 では、X3DObject とは何か? これは、「ビューポート上に目に見える実体があるオブジェクト」です。

例えば Polygon Mesh の cone。 例えば Light。 例えば Camera。 例えば Null 。例えば Curve。 例えば Point Cloud。

これらみんな、目に見える実体として存在しますよね。 実体があるということは、どの位置に存在しているのかという座標の情報、そして向いている方向や大きさの情報、つまり SRT の値を持ちますね。 なので、SRT の値を持つことができるものを X3DObject と考えても差し支えないと思います。 ちなみに SRT は Kinematics オブジェクト以下にあるんですがまあ細かい話は今はどうでもよい。 ジオメトリがあるかどうかは問いません。 例えば Null は1点の SRT を持つだけであり、レンダリングできるジオメトリを持ちませんが、でもビューポート上には厳然として実体があります。 SRT も持てます。だから X3DObject です。 カメラもライトもジオメトリを持ちませんが、実体があり SRT があるので、X3DObject です。

これに対し、例えば Group というものは実体を持ちません。 Group には SRT もありません。 だから Group は X3DObject ではないです。

同様に、Material も、Partition も、Visibility という名のプロパティも、 Geometry Approximation という名のプロパティも、Image Clip も、Action Source も、Mixer も、目に見える実体はありません。従って SRT 情報も持っていません。 結果、これらは全て X3DObject ではありません。

とまあ、X3DObject の解説になってしまいましたが、大事なのはそこではなく、X3DObject という種類のものを取得できたということです。つまり以降は X3DObject に対する操作は何でもできるようになった、ということです。ここが重要です。もうあなたは自由です。さあ身を震わせて大河を遡上して下さい。


あー、余談ですが、前回の説明で、SDK Explorer で ClassName をチェックして、X3DObject と出ているから X3DObject に対するメソッドもプロパティも全部効くぜ、という言い方をしましたが、ClassName よりもどちらかというと Supported Object Model Interfaces を見た方がいいかもしれません。
Shutoku302
ここに出ているように、cone は X3DObject であると同時に、SIOject でもあり、ProjectItem でもあり、SceneItem でもあるわけです。 なので、X3DObject だけでなく、これらに対するメソッドやプロパティも基本的には効く、と考えてもいいかと思います。たぶん。 構造をもうちっと厳密に言うと、SIObject と呼ばれる範囲のものの中に ProjectItem があり、ProjectItem の中のひとつとして SceneItem があり、そのうちのひとつが X3DObject だ、ということになると思います。階層構造です。 でもまあいいや、忘れましょう。 俺もそんなにわかっていません。大事なのは自由だということです。X3DObject どころか、SIObject や ProjectIem に使える各種の呪文も効くということです。


すっかり余談になりました。さあ大河を遡上しましょう。

X3DObject で使えるプロパティのひとつ、Children を使います。 まずX3DObject のヘルプページを見ましょう。 Children プロパティがありますね。 

ではそこをクリックして飛びます。すると、
Shutoku303
Children のページには、「このオブジェクトの子である、すべてのX3DObjectを含むX3DObjectCollectionを戻します。再帰的検索は行われません。言い換えれば、直接の子のみ戻され、子の子は戻されません。」 とあります。 これを読むと、直近の子供が X3DObjectCollection として返されるということがわかります。 イエイ。子供が取得できそうだ。

でも X3DObjectCollection とはなんだ? X3DObjectCollection の文字の部分がリンクになっているのでクリックして飛びます。 すると、
Shutoku304
X3DObjectCollection のページには、「X3DObjectオブジェクトのコレクションです。このコレクションは0から開始されます」とあります。 そしてまた、X3DObject の文字がリンクになっているので飛びます。 すると元の X3DObject のページに戻ります。戻っちゃった。戻っちゃったよ?

・・・・もともとは X3DObject の Children から始めて、最終的に取得できたものは再び X3DObject。 戻っちまった。 いいの? いいんです。

子供を集合体として取得して(X3DObjectCollection)、集合体の中からひとつを取得すると、それはまた X3DObject になるってことは、取得できた子供に対してまたもや Children とかその他のプロパティ、メソッドを使えるということです。 ここでまた自由になります。 取得した子供に対してもさらに、 X3DObject に有効な全ての自由を享受できるということです。 イエイ。


そして上記のヘルプページのリンクのたどり方が、SDK ガイド(ヘルプ)の使い方そのものです。 これを芋づると呼んでいます。

X3DObject から始めて、Children に行き、またもや X3DObject に戻ってきたり、戻ってきた後に今度は Children 以外のプロパティにアクセスしたりして別のものを取得する。 この取得の連鎖芋づるです。 スクリプティングは、芋づるです。


で、Childrenプロパティで取得できた芋たち= cone の子供は、oChildren という変数に格納されます。 さっき見たように Children プロパティは X3DObject を Collection として返すのだから、oChildren の中身は複数の芋です。 そして最後の行、SelectObj コマンドでその複数の芋を選択して終わりです。 イエイ。 できました。 終了。



とりあえずできちゃったのでここで解説を止めてもいいんですが、上でSDKガイドの芋づるを例示するために X3DObjectCollection の話を出しちゃったのでこれをネタにして、取得しているものは何なのかを常に把握していることが大事だという話をちょっと追記します。

上で、「子供を取得した。取得した子供は再び X3DObject なので、また X3DObject に対するメソッドやプロパティが使える」 という話をしました。 これを例にとって、よくやる間違いをやってみます。 こんな風に書いてみます。

   var oSel = Selection(0);    //    最初に選んでいるものを取得
   var oNull = oSel.AddNull( "hoge" ); //    oSel に Null をゲット

   var oChildren = oSel.Children;    // oSel の子供たちを取得
   var oNullNull = oChildren.AddNull( "hage" );    //    子供たちに Null をゲット(エラー出る)



各行にコメントした通りですが、まず1行目で「ユーザが最初に選んだもの」を取得します。oSel に格納されるのは X3DObject でした。 なので2行目で、X3DObject に使えるメソッドである AddNull メソッドを使って、1個 Null を出してやります。名前は hoge です。
次に3行目、oSel の子供たちを取得します。 結果は oChildren に入りました。 さあ、今度は、その子供たちに対して Null を出してやりましょう。名前は hage です。

実行すると、
Shutoku320
エラーで止まってしまいます。
よく見ると、最初の Null はちゃんと出ています。 Explorer を見ると、cone の子供に hoge ができているのがわかります。
しかし2回目の Null は出ていません。その前にエラーで止まったからです。ではなぜエラーで止まってしまうのでしょう?

答えは、「 Add Null メソッドは X3DObect には効く。 oSel の中身は X3DObject だったから、ちゃんと効いた。 しかし、oChildren は X3DObject ではない。 X3DObect ではないものに対して、AddNull メソッドは効かない」 ということになります。

1行目の oSel に入ったものは、Selection(0) と指定しているので、「最初に選んだ一人」と言っています。つまり特定の一人のことを表します。そしてその一人は Polygon Mesh の cone だったので、こいつは X3DObject なわけです。  それに対し3行目の oChildren に入ったものは「子供たち」なので、一人かも知れないし、100人かも知れません。なのでたとえ一人だろうが1000人だろうが、常に Collection として返します。上の例の場合はX3DObjectCollection でしたよね。これは X3DObject の集合体であり、X3DObject そのものではありません。 X3DObjectCollection に所属するひとりひとりは X3DObject ですが、Collection 自体は集合体を入れるためのただの器であり、所属している個々のメンバーに対する操作を引き受けてくれるわけではないのです。

それを見るために、上記のスクリプトに追記してみます。


   var oSel = Selection(0);    //    最初に選んでいるものを取得
   Logmessage( "oSel は " + ClassName( oSel ) + " だぜ"); 
   var oNull = oSel.AddNull( "hoge" ); //    oSel に Null をゲット
   
   var oChildren = oSel.Children;    // oSel の子供たちを取得
   Logmessage( "oChildren は " + ClassName( oChildren ) + " だぜ");
   var oNullNull = oChildren.AddNull( "hage" );    //    子供たちに Null をゲット(エラー出る)



赤字が追加した部分です。 Logmessage を使って、取得したモノの ClassName を表示させています。 

結果は、
Shutoku305
こうです。 ログされたところを見てください。 「 oSel は X3DObject だぜ 」 「 oChildren は XSI)bjectCollection だぜ 」 と言っています。 こうして、oChildren に格納されたものは X3DObject ではなく、X3DObjectCollection だということがわかります。 前述のX3DObjectCollection のヘルプページ画像を見てみましょう。 AddNull メソッドなんてどこにも載っていません。 X3DObjectCollection には AddNull メソッドが効かないからです。

ということで、取得したものが Collection だった場合、、そこに所属するひとりひとりに対して何かをしたいのであれば Collection の中身をひとつひとつ取り出して何かをやるという書き方をせねばなりません。ま、ただのループ処理なので簡単です。これについてはまたそのうち書きます。 今回はループの書き方を説明したいんじゃなくて、「取得できたものが○○ならコレコレができる、逆にアレソレはできない」 という話をしたいのです俺は。


このように、oSel に取得できたものは X3DObject だぜ、 oChildren に取得できたのは X3DObjectCollection だぜ、という風に、「何を取得できたのか」をちゃんと把握さえできていれば、その後の処理がすんげー楽です。エラーが出た時に、「 あ、俺は○○をしたいんだから××を取得しなければいけなかったのに、△△を取得しちまってたんだ。そりゃ動くわけねーよな 」 とすぐにわかるからです。 あるいは××を取得すればいいとすぐにはわからなかったとしても、少なくとも取得したモノの種類が違うんではないかと疑うことができます。 これは問題解決への近道です。

よくあるんですよね。 ちゃんと書いてるのに、文法的には何も間違ってないのに、なんで動かないんだ? バグか? とかね。 間違ったものを取得してるくせに、そこに気づかず、ソフトウェアのせいにするんです。 俺も毎日これです。 ま、 XSI 様の場合はそれももっともなんですが。 
また、オブジェクトとして取得したと思い込んでいて、実はただの文字列だけを取得していたということもよくあります。 これについてもまた後日書きますたぶん。

ともかくですね、エラーで止まってしまった時は、文法などをチェックするのと同時に、「俺は正しいものを取得できているのか? コレをしたいんなら、アレを取得するべきなのに、もしかしてナニを取得しちまっているんじゃないのか?」 と疑うことを激しくおすすめします。


上手く言えたかどうかはわからないけど、もともと「取得」云々と言い出したのは、要するにこれを言いたかったのですよ。 正しく取得できていれば正しく動く。 正しく動かないときは正しく取得できていなかったのではないかと疑え。 これです。 ここさえおさえていれば、スクリプティングなんてチョロいもんです。ウソです。チョロくありません。俺は表層しかわかっていません。すいjません。




もうひとつ、本題からは外れますが、今回の例では最後に SelectObj コマンドを使っています。 オブジェクトモデルで書くんじゃなかったのかよゴルァ と怒られそうですが、SelectObj のような単純なものは、むしろコマンドで書くべきではないのか、と思っています。 なんとなくですが。 オブジェクトモデルで書くと長くなるんだもん。解説する上でも本題とは関係のないところで煩雑な話をしなければいけなくなるし。 ということで SelectObj はコマンドです。コマンドで行きましょう。



とまあ、またしくみの説明をいっぱいしてしまった気がする。
もうちっとカンニングのサンプルを書かないとなあ。
しくみの前にカンニングだよやっぱり。
カンニング繰り返しているうちにしくみなんて勝手にわかってくるからなあ。

前にも書いたように、俺はしくみをわかっているからスクリプトが書けたわけではありません。 カンニングしながらわけわからんまま書いているうちに、結果からしくみが想像できてきて、その時点でマニュアル読んだらどうやらその解釈は正しいらしいということがわかってきた、という感じです。 スクリプティングなんてそんなもんです。 たぶん。



酔っているのにずいぶん書いたなあ。疲れた。
たぶん続く。 たぶん。




.

| | コメント (9) | トラックバック (0)

2010年10月11日 (月)

取得 その2。

では取得行きましょう。

    var oRoot = ActiveSceneRoot;

はい、これでシーンルートが取得できました。 細かいことはともかく、こう書けば、現在開いている XSI シーンの、シーンルートというものを取得できた、と覚えてしまえばいいです。

で、ActiveSceneRoot のヘルプのページを見てみましょう。 スクリプトエディタ上で ActiveSceneRoot という文字を選択し、F1 を押します。

すると「アクティブなプロジェクトのアクティブなシーンのルートModelを戻します」という説明があります。  「戻します」という言い方は Return Value つまり戻り値というものを意識しての記述だと思いますが、「取得します」と同じ意味と思って差し支えないと思います。 「シーンルート Model を取得します」 と同義だということです。


で、ここで大事なのが、取得できたものはいったい何オブジェクトなのか? ということです。 ActiveSceneRoot で得られるオブジェクトは、Model オブジェクトです。 まあそりゃそうですよね。XSI をそれなりに使っている人ならわかると思いますが、シーンルートって、XSI にとってはただの Model ですからね。 ただし削除できないとか他の Model の子供なれないとか、シーンルートならではの特別な性質もありますけどね。でも基本的にはただの Model です。エクスプローラやスケマティック上のアイコンも Model の人型アイコンですよね。
Shutoku21

SDK Explorer というものを使っても、それを確認できます。エクスプローラでシーンルートを選択し、Ctrl + Shift + 4 あるいは GUI 上のトップメニューから View > Scripting > SDK Explorer で開くことができます。
Shutoku22
ClassName のところに Model とあるじゃないですか。 同様に、Create > Model > New Model で普通に Model を1個出して、SDK Explorer で見てみましょう。やはり ClassNameModel となってますよね。 これは Model というオブジェクトである、という意味だと思って良いと思います。例外があるかも知れませんが、んなこた知りません。どーでもいいです。厳密にならないことが、スクリプティングの初期においては最も重要です。

ってことで、 シーンルート= Model オブジェクトを取得できました。  Model オブジェクトを取得できたってことは、Model オブジェクトに対する操作は何でもできるようになったということです。ここが重要です。取得さえしちまえばなんでもできるんです。もうあなたは自由です。大空に羽ばたいてください。




ちなみに var oRoot = という記述によって、ActiveSceneRoot で取得できた Model オブジェクトは oRoot という名前の変数にアサインされました。 なので、以降 oRoot と書けばそれは シーンルートを表します。

ためしに、こう書いて実行してみましょう。

    var oRoot = ActiveSceneRoot;
    Logmessage ( oRoot );

スクリプトエディタ上に Scene_Root と表示されましたよね。 2行目の Logmessage は、スクリプトエディタ上に何かを表示させるための呪文です。死ぬほど使うので覚えましょう。 で、oRoot を表示しろ、と指令したところ、Scene_Root と表示されているんだから、ちゃんと oRoot の中身はシーンルートが入っているということがわかります。 ためしにシーンルートの名前をテキトーに変えて、再びこの2行のスクリプトを実行してみましょう。 するとリネーム後の名前が表示されるはずです。 このことから、ActiveSceneRoot によって、シーンルートの名前がなんであろうと、ちゃんと現在開いているシーンのトップ階層のルートが取得され、そして取得されたオブジェクトは oRoot に格納されたということがわかると思います。

oRoot の部分は自分で好きな名前を付ければいいと思います。 でも aaa とか bbb みたいな意味不明な名前にしてしまうと後で自分で意味がわからなくなり、スクリプトが大きくなるにつれ収集が付かなくなり、ついにはブチ切れて、まるで内田裕也のようにマンションの10階からモニタやPCを遠投することになるので不経済です。なるべくそれらしい名前にするといいと思います。今回はシーンルートを取得したので Root という言葉を使い、かつシーンルートをオブジェクトとして取得しているので object の o を頭に付けて oRoot としています。

余談ですが、実はこの ActiveSceneRoot というのは省略形であり、省略しないと Application.ActiveSceneRoot という書き方になります。 でも今はどうでもいいです。余談です。忘れてください。


で、シーンルートつまり Model オブジェクトを取得できたんだから、Model オブジェクトのヘルプを見てみましょう。色んなことができるのがわかります。
Shutoku23

では取得した Model オブジェクトを使ってなんかやってみましょう。

    var oRoot = ActiveSceneRoot;
    var oNull = oRoot.AddNull( "hoge" );


2行目を追加しました。 oRoot の中に格納された Model オブジェクトに対し、AddNull メソッドを使って null を追加しています。 カッコの中に何を書いてあるのかは、AddNull のヘルプを見ればわかりますが、null の名前です。AddNull のヘルプページにたどり着くには、キーワード検索してもいいですが、Model オブジェクトのヘルプページのメソッド一覧にも載っているので、そこからリンクをたどってもよいです。

こうして、「シーンルートに null をひとつ追加する」 というスクリプトが書けました。こんなもん何の役に立つのか、と言われるともちろんそのまんまでは役に立たないのですが、何事も基本からであります。基本をわかってしまえば、このスクリプトにほんのちょっと手を入れるだけで、例えばぬるぬるボンデージのようなツールができます。 ぬるぬるやボンデージは、そこそこ役に立つツールだと思います(俺は日常的に使っています)。 ちょっとのことで、普段役に立つツールができるんです。 ほんとだってば。

そして、AddNull メソッドで得られた null は、Null オブジェクトです。SDK Explorer で null を見てみましょう。 ClassName に Null とあります。では SDK ガイドで Null オブジェクトのページを探してみましょう。
Shutoku24
ありました。 説明には「 Nullオブジェクトは特化されたX3DObject オブジェクトであり、空間内にある大きさを持たないポイントです」だとか何とか書いてます。そして Null オブジェクトに使えるプロパティやメソッドがずらりです。 もう oNull という変数の中に Null オブジェクトを取得済みなのですから、Null オブジェクトに対する操作は何でもできるようになったということです。ここが重要です。もうあなたは自由です。大海原をこぎ出してください。

  var oRoot = ActiveSceneRoot;
  var oNull = oRoot.AddNull( "hoge" );

  var oChildNull = oNull.AddNull( "hage" );
  oNull.size.value = 0.5;
  oNull.primary_icon.value = 2;
  oChildNull.size.value = 0.1;
  oChildNull.primary_icon.value = 3;

こんな風にしてみました。

3行目の

    var oChildNull = oNull.AddNull( "hage" );

ですが、oNull に対して、つまり最初にシーンルートに AddNull メソッドでゲットした1つ目の null に対して、さらに AddNull メソッドしてます。つまり、1つ目の null の子供として新たな2つ目の null が作られます。名前は hage です。 hage は、oChildNull という変数に格納されます。
Shutoku25

そして、oNull や oChildNull に対して、null のサイズや null のアイコンを設定しています。これで、新たな null を既存の null にぶら下げたり、そのサイズやアイコンを変えるというスクリプトになりました。


ここで疑問に思うのが、primary_icon.value という言い方や size.value という言い方がマニュアルに載っているのか? ということです。 Null オブジェクトに対してできる全ての操作は Null オブジェクトのヘルプページに載っているというようなことを言ってきましたが、Null オブジェクトのページを見ても primary_icon.value とか size.value とかは載っていません。

 実は上記のスクリプトは若干省略した書き方をしてあり、省略せずに書くと、

    var oRoot = ActiveSceneRoot;
    var oNull = oRoot.AddNull( "hoge" );
    var oChildNull = oNull.AddNull( "hage" );
    oNull.ActivePrimitive.Parameters( "size" ).value = 0.5;
    oNull.ActivePrimitive.Parameters( "primary_icon" ).value = 2;
    oChildNull.ActivePrimitive.Parameters( "size" ).value = 0.1;
    oChildNull.ActivePrimitive.Parameters( "primary_icon" ).value = 3;


こうなります。あ、ややこしくなったからってここで読むのやめないで下さい。こんなややこしいこと考える必要はないとう話を以下でするので。

上記の省略してない書き方を一応解説すると、Null オブジェクトが持つ Primitive オブジェクトにアクセスし、Primitive オブジェクトが持つ Parameter オブジェクトにアクセスし、その中で size という名前でより分け、値を 0.5 などとセットしてやる・・・・という書き方です。SDK ガイドで Null オブジェクトのページを起点にし、リンクをこの順でたどっていけば、ちゃんと全部書かれています。つまり「ちゃんとマニュアルには載っている」ということになります。

でもちゃんとマニュアルに載っていれば何でも理解できるのか、記載さえあればスクリプト書けるようになるのかというと、これはまた別の話です。 はっきり言ってこんなややこしいこと、さっぱりわかりません。

じゃあどうするかと言うと、スクリプトでやりたいことを、まずは GUI 上でやってみるのです。 この例で言えば、null の PPG を開き、手でサイズやアイコンを変えてみればいいのです。
Shutoku26
すると、スクリプトエディタにログが残ります。 

      SetValue("hoge.null.primary_icon", 2, null);
      SetValue("hoge.null.size", 0.5, null);


とか出ています。 ここに出ているログはコマンドで値を変える場合の書き方です。でも今は Null オブジェクトを取得した上で、オブジェクトモデル的に操作しようとしているので、ここをヒントにオブジェクトモデルで書き直します。

書き直すって言っても大したことではありません。まずはログを見て、「ああ、アイコンを変えるには primary_icon というやつに何かをセットしてやればいいんだな、今 Ring にセットしたら 2 という数字が出ているから、2 を primary_icon に入れてやればアイコンが Ring になるんだな」 と想像をつけます。

で、取得済みの Null オブジェクトに対し、

  oNull.primary_icon.value = 2;

と書いて実行してやれば、予想した結果が得られたことがわかります。 値をセットする時は、何でもかんでも .value = 値 という書き方になると覚えてしまいましょう。

このようにして、何かをやろうとする時、いちいち真面目にマニュアルのリンクをたどってやりたいことが載っているかどうかを調べるのではなく、まず○○オブジェクトを取得するとこころまでをやり、その後は GUI で適当に何か操作をしてみて、ログされる結果から予想し、書いてみて、予想通りになったらそれでOK、ならなければ俺はどっかで勘違いしてるぞということで更に調べる。 これを繰り返して覚えていきます。 初心者はそうやって覚えろと言っているわけではなく、初心者じゃない人も普段こうやってスクリプト書いてると思います。俺はそうしてます。日々それの繰り返しです。


ちなみにですが、オブジェクトモデルとコマンドが混じってはいけないなんてことはありません。 なので、○○オブジェクトを取得する、というオブジェクトモデルな操作をした後、値をセットするのはコマンドで書く、というのは全然アリです。 というかモノによってはコマンドでセットする以外に無い場合もあるので、両者混在したスクリプトはいたって普通です。

この例の場合は、

    var oRoot = ActiveSceneRoot;
    var oNull = oRoot.AddNull( "hoge" );
    var oChildNull = oNull.AddNull( "hage" );

    SetValue( oNull + ".null.size", 0.5, null);
    SetValue( oNull + ".null.primary_icon", 2, null);
    SetValue( oChildNull + ".null.size", 0.1, null);
    SetValue( oChildNull + ".null.primary_icon", 3, null);

こう書いても良いと言うことです。前半で Null オブジェクトを取得し、後半はコマンドを使ってその null のパラメータを書き換えています。 GUI で値をいじった結果ログされるのが

      SetValue("hoge.null.primary_icon", 2, null);
      SetValue("hoge.null.size", 0.5, null);


なんだから、この null の名前である hoge の部分のみ、取得済みの oNull に置き換えてやるだけです。 hoge という文字を取っ払い、 「 oNull + 」 を頭につなげます。 oNull には hoge が入っているんだから、結果的に hoge.null.primary_icon と書いたのと同じことになるわけですね。 なんでもかんでもオブジェクトモデルでやるのがつらいと感じた時は、取得までできたら、以降コマンドで書いちまうというのも手ですね。





ダメだ、まとまらない。
ともかく、「俺は今何を取得しているのか」 というのを常に意識しておくのが大事だと思うんですよね。  スクリプトなんて、ちゃんと理解してなくても動いていればOKというもんではありますが、今俺の手中にあるもの=取得できているものは何なんだ? ということがわかっていれば、色んなことがすんげーたやすくなるんです。 だから、取得という切り口でスクリプトをおベンキョする、っていう考え方を提示したかったのですが、どうなのか。  まあ、また時間とやる気のある時に、「取得して何かやる」というサンプルを書いてみようと思います。




うーむ、なんかやっぱり、スッキリわかり易く書けてない気がしますね。
わかりますかねこんなんで?
概念的な話、しくみの説明をし過ぎている気がしますが、どうなんだろう。

ま、しつこいですがサンプルのスクリプトを溺れるように見ていくしかないですね。この記事も、前回「カンニング」と言ったように、サンプルの提供、つまりカンニング元にさえできれば多少の価値はあるのではないかと思っています。 取得の仕方のサンプル、取得した後にやる操作のサンプル。 そういったものを溺れるように見ていくうちにわかっていくものです。

その道のりは、超簡単とまでは言いません。やはり「わかんねえよ、どうすりゃいいんだよ」というストレスを感じる時期はどうしても通過せねばなりません。 しかし、超難しいわけでは全然ないです。参考書の1冊も持っていない俺が何とか理解できてるくらいなんですから。 わかんねぇよというストレスが体に馴染んでくると、こっちのもんです。わかんねぇことはわかんねぇまま、持っておけばいいんです。 わかんねぇからやめるとか、わかんねぇから更に調べてもっとわかんなくなってイライラするとかではなく、わかんねぇけどとりあえずここだけはどうにかなったように見える、というような実に半端で曖昧でストレスもある状態を受け入れてそこに身を浸しておくのです。 あるいはわかんなくてもいいからサンプルをダラダラと見ては、挫折しない程度に少しのトライを続けるのです。すると、いつのまにかわかっていることもあれば、わかってはいないんだけど結果的にやりたいことはできてるように見えるからわかんねぇこと自体を問題視しなくなる、という状態になって行きます。 ←俺は今ココです。

俺がここに書いていることは、全部マニュアルで調べて理解して来たのではない、というところが重要だと思います。 むしろ逆。 わかんねぇからひらすらカンニングしながらごちゃごちゃやってきて、ある程度わかった気になったくらいの時点でマニュアルを調べたらこう書いてあった、って感じですね。 よく「マニュアルに全部書いてあるから読め」とか言いますが、わかんねぇうちはマニュアル見てもわかんねぇんですよね。 わかってから初めてマニュアルに書いてあることの意味がわかる、って感じですよね。 だからマニュアルが意味不明でもダイジョーブ、逆の順番でどうにかなるもんです。

また、カンニングの最も手っ取り早い方法は「人に聞く」です。知っている人が近くにいるとすげえ助かります。 俺は近くにはいなかったので、SDK ガイドのサンプルスクリプト、およびインターネット上に転がっているものをカンニングすることが主体になりました。多少つらいと感じた時期はあるかな。 あとは、有償サポートに質問するのと、WEBフォーラム上で質問するというのはありましたね。 質問できる人間と仲良くしておくというのが最も良いです。

俺は気まぐれでこんな記事を書いているだけなので人様の助けを積極的にしようなんてつもりは毛頭無いのですが、基本的なことなら、質問してくれれば答えられるかもしれません。○○オブジェクトってどう取得するの? ○○のコンストレインだけ削除したいんだけど、何をどう取得すればできるの? みたいな話なら、このブログ上でできるんではないのかな、という気もする。自分の研究にもなるというメリットがあれば、そういうことをしてみたいという気分はある。

まあ、気前のいいようなこと書いちゃって、勘違い野郎が変な質問してきたりするのが怖いんですがね。前にもあったからなあ・・・。 



たぶんつづく。

.

| | コメント (8) | トラックバック (0)

2010年10月 9日 (土)

取得 その1。

最近俺の身の周りで、スクリプトの話がすごくよく出てくるんです。 スクリプト書けるようになりたいとか、そういう本が出たら買うぞとか、どこから手を付けていいかわからないとか。


俺自身は、7年くらい前に、全部のビューポート上でシェーディングの上にワイヤフレームをイッキに表示させたくて、そんなスクリプトを書いたのが最初だった気がします。 まず GUI 上で操作をして、スクリプトエディタにログされた履歴をコピペして、一部だけ書き換えて。 最初はやはりこれから始めなければいけません。 記憶が曖昧だけど、そんな簡単なスクリプトですら、いつもお世話になっているエアコン屋さんに質問して助けてもらった気がします。

そんな感じで、ログをコピペして書き換える、わからなければサポートに質問する(有償のサポート契約が必要ですが)ということを繰り返していたと思います。 1行か2行のスクリプトばっかりだったと思います。 スクリプトのことをちゃんと理解しているわけではなく、なんとなく出来ちゃってるからまあいいか、くらいの状態です。実は今でもそんな感じです。

2005年だったと思うんだけど、仕事をしていくうちにあまりにも面倒な繰り返し作業とかがいっぱい出てきて、それを簡略化するために、やはりサポートに「こんな機能はないか?」というような質問をした気がします。 「スクリプト書くしかないですねえ」という答えだったと思います。 なのでその頃からスクリプトをもう少し深く研究し始めました。 VBScript で書いてました。

最近 studioNEST で活躍中の Helge Mathee さん著作による、Getting Start with Scripting というチュートリアルビデオとの出会いが大きかったですね。 たしか 60ドルかそこらだったと思うんですが、得るものはとてつもなく大きかったです。 for loop とか、if else とか、基本的なスクリプトの制御の話も出てきていたし、スクリプトでよくある「ユーザがこのタイプのものを選んでいたらこうしろ。 そうじゃなければエラーを出せ」とか、「シーンにこのプロパティがあればPPGを表示し、なければ新たに作る」みたいな王道パターンをここで覚えた感じがします。 また、SDK ガイド(スクリプトのマニュアル)の読み方もここで覚えたと思います。

そう言えばこのビデオは JScript でした。ちょうどVBS のロジックファンクションの書きにくさに辟易していたので、思い切って JScript に切り替えたのでした。このビデオに出会ってなかったら、JScript はいじってなかったはずです。 いやあ、ほんとにいいビデオだった。今はたぶん郵便受けさんからダウンロードできるんじゃないかな? どうかな。




ま、ともかく、最初にスクリプトエディタのログをコピペして、一部を書き換えていくという手順は基本ですので、これからスクリプティングをやろうと思っている人はやっぱりそこからやればいいんじゃないですかね。 基本的には、オブジェクトの名前の部分や、セットする数字を書き換えればいいだけですからね。

そんなことをダラダラと解説したビデオを昔作りました。
http://vimeo.com/2518061
しかし、Vimeo が高解像度版を削除しやがったので非常に低解像度で画質の悪いものしか残っておらず、マトモに字が読めず、役に立たないビデオになっています。 元データを見つけられたら、再度アップロードしてみます。


そんでもって、この「ログをコピペして一部書き換えて実行」というのは、いわゆる「コマンドモデル」というものを使った作業になりますね。 XSI で GUI 上の操作をするというのは、XSI にコマンドを送っているということです。例えば Get > Primitive > Polygon Mesh > Cube という操作をした場合、CreatePrim("Cube", "MeshSurface", null, null); というコマンドを XSI に送っていることになります。 このコマンドを使ったスクリプトの書き方がコマンドモデルです。 もしかして「コマンドモデル」とは言わないかもしれません。ただの「コマンド」かな。よくわからんな。まあいいや。 で、コマンドだけでもかなりのことはできますが、それよりは「オブジェクトモデル」というものに親しんだ方がいいと思います。少し書き方が違いますが、別に面倒ではありません。っていうかむしろ簡単だと思っています。 オブジェクトモデルとコマンドモデルの違いの解説は別にしません。俺もよくわかってないし。 ともかくオブジェクトモデルでやった方がいいことがいっぱいあるんです。 より細かく制御できるし、実行速度が速いし、他のスクリプトへの流用も簡単になるとかそんな感じ。

で、オブジェクトモデルで書くスクリプティングって、一度端緒をつかんでしまえば、あとは芋づる式なんですね。 全般に渡って深い知識が必要なんてことはなく、はじっこの方だけつかめれば、あとは SDK ガイド上で連鎖的につながっていく感じ。

例えば var oGroup = ActiveSceneRoot.Groups( "hogeGroup" ); とかいう書き方を、あまり深く考えずに丸暗記してしまいます。 こう書けば、「シーンルート直下にある hogeGroup という名の Group が、oGroup という名前の入れ物の中に入るんだ」 と覚えてしまいます。これを 「hogeGroup を Group オブジェクトとして取得できた」 という言い方をします。

次に SDK ガイドで Group オブジェクトのページを見てみます。キーワード検索で簡単にそのページにたどり着けます。すると、Group オブジェクトに対する MethodsProperties というものがずらりと並んでいます。
Shutoku11
Group オブジェクトが取得できていれば、これだけの色んなことができるというのがわかります。 ま、ここに載っている全てが実行可能ではないんですが、あまり深く考えないことにしましょう。

例えば Methods の中に AddMember というものがあるじゃないですか。 名前から想像できますよね。 Group に対して AddMember なんだから、Group に所属するメンバーを追加する、つまりあるオブジェクトを Group に入れるということができそうだぞ、という予想がつきます。

そこで AddMember をクリックします。AddMember の解説ページに飛びます。
Shutoku12
AddMember に続いて何をどう書けば Group にオブジェクトを入れられるのかが、そこに載っています。 

こうやって、なんでもかんでもリンクをたどって調べます。これが芋づるです。

ま、もっと連鎖する例を挙げられればいいんだけど、パッと上手い説明が思いつかないので終了。 ともかくですね、この例では 「Group オブジェクトを取得できた」という端緒がつかめたので、以後、Group に対して実行できる操作は全部できるようになりました、ということなのです。ここが重要なんです。Groupさえ取得できれば、以降は何でもできるぜ、になるんです。あるいは、Group に対してはこういう操作はできないのかー ならば Group を取得することはやめて、別のものを取得してそこから芋づるをたどろう、という判断もできます。 なので、まずは「○○オブジェクトを取得する」というのが全ての始まりです。そこができれば、スクリプトの半分は書けたようなもんです。


ただし、最初のうちは何をやるにもこの「取得」の方法がわからなくて困るんですよね。 上の例では、ActiveSceneRoot.Groups( "hogeGroup" ); という書き方を知っていることが前提になっちゃってます。 お前は知ってるからいいけど、知らない俺はどうするんだよ どこ調べれば載っているんだよゴルァ ということになります。 まあ SDK ガイドのサンプルスクリプトを見るのが一番なんだけど、スクリプティングそのものに慣れていない状態でサンプルを見ても、まずわかりません。 だんだん慣れてくると、そのタイプのオブジェクトは初めて取得するという場合でもだいたいどう書けばいいのか想像が付くんでさほど苦労しないのですが、慣れないうちは手がかりすらつかめずに途方に暮れてしまいます。 俺はだいぶ慣れたつもりですが、しょせんテクニカル野郎ではない素人スクリプターなので、未だに取得したいものが取得できなくて困ることが多々あります。っていうか毎日です。


なので、よくある取得のパターンを少し紹介してみようかな、などという気になったのです。取得さえできれば以降は芋づる方式なんだから、最初の取得のところだけカンニングして丸暗記してしまおうという感じですかね。 俺も基本的には SDK ガイドに乗っているサンプルスクリプトをカンニングして覚えてきました。


ってことで、次回以降、まず何かを取得して、取得したものに対して何かをする、という簡単な例を挙げていこうと思います。 そういうものを積み上げていくと、いつの間にか普通にスクリプトが書けるようになると、俺は思います。




ちなみにですが、「さあスクリプト覚えるぞ」 「スクリプト覚えたら何をしようかなあ」 という人は、まず無駄だと思うのでやめた方がいいと正直思います。 そういう人は、根本的にスクリプトを必要としてない人です。こんな呪文の羅列を勉強するのに時間を使うくらいなら、モデリングを鍛えるとかアニメーションに熱中するとかビールで泥酔するとか中国に出張するとかに時間を使ったほうがよっぽど有意義です。要りもしないものに労力をさく意味はありません。  そうではなく、普通のCG作業中で、「 うっ。 これめんどくせえ。 手でやりたくねえ。 イッキにできねえのかよゴルァ 」 とか思った瞬間に、スクリプティングの扉は開きます。この瞬間こそが、スクリプトを必要とし始める瞬間です。

俺の場合、時間のあるときに「さあスクリプト書くぞー 何書こうかなー」 などということはまずありません。普通に仕事していて、急にこの瞬間が来るので、大抵の場合はすぐにスクリプト書き始めます。時間がなければメモっておいて、なるべく早くごく簡単なスクリプトを書いてとっておきます。ブラッシュアップは後ですればよい。ブラッシュアップすらする必要がなく、その場で使い捨てられるスクリプトもたくさんあります。

これからスクリプティングを始めるという人も、その瞬間が来たら、何をどうしたいのかメモっておけばいいと思います。 この瞬間がないのにスクリプティングをいじる意味は全くありません。 いやほんとだってば。





あれっ 友愛の話も未完だった気がするっ
まあいいや
今回も未完になるだろうなあ。
っていうかそもそも完結しようにないしな。
第1回のこの記事で完結する可能性もあるな。
まあいいや。俺のブログだし。
けけけけけけ。


あ、最近スクリプティングに目覚めたという、あるヤローから来たメールを無断でコピペしておきます。

 今日初めてスクリプトを自分で書きました。
 GUIで一度やってスクリプトをコピペする感じですけど…。

 パス作り、パス分けをするやつなんですが
 出来てみてビックリです。
 スクリプトが走った瞬間、
 30歳にして初SEXの絶頂を迎えたような
 大好きな女の子に告白してOKをもらえたような
 なんとも言い難い快感が体を走りましたよ!
 あの快感には本当にビックリです。
 一人でPCの前でニンマリしてました。
 スクリプトを書いた先にあんな快感が待っているなんて思ってもいませんでした。





だそうですよ。

けけけけけけけけけ。


.

| | コメント (6) | トラックバック (0)

2010年10月 7日 (木)

スプライトな野郎どもを選択。

いや、とある知り合いの野郎が、スプライトシェーダを持っているオブジェクトを全部選択するスクリプトが欲しいとかなんとかほざいていたんでね、ちょっとやってみたというだけなんですがね。 思ったより手間がかかってしまった。マテリアル周りのスクリプティングはあんまりいじってないからなあ。。。。



var oAllItemsWithSpriteShaders = XSIFactory.CreateObject( "XSI.Collection" );//結果を入れる箱ヨーイ

var oMatLibs = ActiveProject.ActiveScene.MaterialLibraries; // シーンの中の全 MatLib 取得
for ( var i=0; i<oMatLibs.count; i++ )    //    全 MatLib をひとつずつループ
{

    var oMatLib = oMatLibs(i);
    var oMaterials = oMatLib.Items; //    MaterialLibrary.Items で返って来るのは Material オブジェクト(らしい)
    for ( var j=0; j<oMaterials.count; j++ ) // 全 Material をひとつずつループ
    {
        var oMat = oMaterials(j);
        oAllItemsWithSpriteShaders.AddItems( GetItemsWithSpriteShaders( oMat ) );  //    Function に飛ばしてブツ取得
    }
}

//    返ってきた野郎どもを選択しておしまい
SelectObj( oAllItemsWithSpriteShaders );



//  以下、Material オブジェクトを受け取ってそこから Sprite を探して見つけたらその Material を使用している野郎どもを返すファンクション。

function GetItemsWithSpriteShaders( oMat )
{
    var oShaders = oMat.GetAllShaders( ); //    その Material にぶら下がる全ての Shader を取得(接続されてないのも含む)

    var oItemsWithSpriteShders = XSIFactory.CreateObject( "XSI.Collection" ); //    結果を入れる箱ヨーイ
    oItemsWithSpriteShders.unique = true;
   
    for ( var i=0; i<oShaders.count; i++ ) // 全シェーダをひとつずつループ
    {
        var oShader = oShaders(i);
        var FuckinClassID = XSIUtils.DataRepository.GetIdentifier( oShader, siObjectCLSID ); // Shader の ClassID を取得
        if ( FuckinClassID == "{6BA13137-7FF5-4B27-AEF6-D280DB0B5043}" )    //    ClassID がこの象形文字だった場合、それは Sprite。
        {
            oItemsWithSpriteShders.AddItems( oMat.UsedBy  ); //    Sprite だったら、それを使ってる野郎( UsedBy )をぶち込む
        }
    }
   
    return oItemsWithSpriteShders; //    ぶち込まれた野郎どもを返す
}



JScript です、念のため。
一応、動いているように見えますがね。


実行すると、シーン中で Sprite Shader が含まれるマテリアルを持つオブジェクトが全部選択されます。 Cluster のマテリアルが該当した場合、Cluster も選択されます。 Group や Partition が該当した場合、Group や Partition 自身が選択されるのではなく、そこに所属するオブジェクトたちが選択されるようです。
もし Partition に左右されたくないなら、新規の Partition でも作って全部のオブジェクトが何のマテリアルもアサインされていない Partition に所属している状態で実行した方がいいかな。 Group や Layer はどうしようもないか。


改良あるいは仕様変更するとすれば、Group や Partition そのものも選択の対象に入れるとか、Group や Partition でマテリアルを上書きされている場合でもそのマテリアルは無視して所属するメンバがローカルに持っているマテリアルだけを対象にするだとか、Cluster の場合は親オブジェクトを選ぶようにするとか、そんなとこでしょうかね。 そのうち気が向いたらやってみようかな。たぶんやらないな。


アプローチはいくつでもあるとは思うのですが、今回はひとまず、

 1.シーン中の Material Library を全部取得

 2.それぞれの Material Library に所属する Material を全部取得(これでシーン中の全マテリアルが対象になる)

 3.それぞれの Material に所属する全 Shader を取得(これでシーン中の全シェーダが対象になる)

 4.全シェーダをひとつずつ、ClassID を使って Sprite かどうかを判別

 5.Sprite だったら、その Shader が所属する Material に対して UsedBy の呪文を唱えて、その Material を使用中のアイテムを取得

 6.そのアイテムを選択して終了


という流れにしてます。 ユーザが選んでいるオブジェクトの中から探すようなアプローチの方が簡単だったかも。


ちと発見だったのが、GetAllShaders メソッドですね。 Material オブジェクトを取得して、その Material オブジェクトに対して Material.GetAllShaders( ) と実行すると、そのマテリアルにぶら下がるシェーダ達、つまり RenderTree のノード達が全部イッキに取得できるんですね。 これは便利だ。 この GetAllShaders メソッド、7.0 以降で搭載されたようです。 よって上記のスクリプトは XSI 七ちゃん以降でしか動かないはずです。前からあった Material.Shaders プロパティで返って来るのは、直近で接続されているシェーダだけでしたからね。ツリー全体をたどって取得してくれるわけじゃなかったですからね。  なんだ、こんな便利な機能、もっと早く搭載しろよ&搭載したら大々的にアナウンスしろよ嘔吐デスクっていうかアビッドゴルァ。

Sprite シェーダかどうかの判別は、やはり ClassID に頼らねばならないか。シェーダの名前で検索しちゃうと、ユーザが名前変えちゃったらヒットしなくなりますからね。 にしてもあのヒエログリフのような ClassID はなんとかならんのか。他に方法ないかなあ。
ちなみにシェーダごとの(というか全てのオブジェクトごとの) ClassID は、SDK Explorer ( Ctrl + Shift + 4 )から調べられます。



ってことで、動いたかね?
これでいいかね?
ビールよこせゴルァ



.

| | コメント (3) | トラックバック (0)

« 2010年9月 | トップページ | 2010年11月 »