« 群集じかけのオレンジ。 | トップページ | 取得 その5。 »

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 );
"




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





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





.

|

« 群集じかけのオレンジ。 | トップページ | 取得 その5。 »

コメント

コメントを書く



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




トラックバック


この記事へのトラックバック一覧です: 取得 その4。:

« 群集じかけのオレンジ。 | トップページ | 取得 その5。 »