【MaxScript】シーン内の○○を回収する

MaxScriptで何かツールっぽいものを作ろうとしたとき、割と頻繁に必要になるのが「シーン内の○○を回収する」という操作です。例えば「シーン内のカメラを回収する」とか、「シーン内のBipedを回収する」といった操作です。

これをうまい具合にやる方法を考えてみます。

シーン全体を対象にループする

objectsを使う

まず「シーン全体から」という部分ですが、最もシンプルなのはobjectsというコレクションオブジェクトを使用する方法です。

簡単なシーン(適当にオブジェクトをいくつか(いろんな種類のもの)置いて、次のコードをエディタに書いて評価(Ctrl+E)します。

for o in objects do (
    print obj.name
)

実行するとシーン内のすべてのオブジェクトの名前がリスナーに出力されます。オブジェクトの種類を問わず、全オブジェクトを対象にしたい場合はこのobjectsというコレクションを使います。

geometryを使う

回収する対象がジオメトリオブジェクト(ごくザツに言うとレンダリングできるオブジェクト)である場合は、geometryというコレクションが使えます。同様に実行してみてください。今度はシーン内にカメラやライト、ヘルパなども適当に置いてみます。

for o in geometry do (
    print o.name
)

対象をgeometryにした以外は前のコードとまったく同じです。objectsで実行するとカメラやライト、ヘルパなどもリストアップされ、geometryにするとそれらはリストされなくなります。

このように、ジオメトリオブジェクトに限定できる場合にはgeometryを使ったほうが、検索対象の母数が減るので実行速度が若干速くなります。

特定の種類のものを探す

漠然と「種類」と言っていますが、Max上でオブジェクトの種類を識別したい場合、そのオブジェクトのクラスを確認することになります。シーン内の適当なオブジェクトを1つ選択して以下のコードをリスナーで実行すると、選択したオブジェクトがどのクラスのオブジェクトかがわかります。

classof $

種類を識別する最もシンプルなのはこのclassofというビルトイン関数を使用する方法です。

しかし、classofだと細かく分かれすぎていて面倒くさい、というケースがあります。例えばシーン内のライトを全部回収したい場合などです。

ライトはオムニ、スポット、ターゲットスポット、指向性など、ライトの種類によってすべて異なるクラスに属しています。これを、ライトの種類にはよらずすべてのライトを回収しようとすると、classofの結果が○○、または□□、または△△、といった具合にひたすら条件をたくさん書く必要が生じます。

このような場合にはクラスのさらに上のスーパークラスで識別するのが良いです。スーパークラスは以下のコードで取得できます。

superclassof $

これを使うと、ライトはその種類のよらず全部lightというスーパークラスに属していることがわかります。

※厳密には、例えば指向性ライトはlightクラスを継承したDirectionallightというクラスのオブジェクトです。sperclassofというビルトイン関数を使うと、そのオブジェクトのクラスが継承している継承先のクラスを知ることができます。

全てのライトオブジェクトはlightクラスを継承しているため、superclassofでlightが返ってくるものを回収すると、シーン内の全てのライトを回収することができます。

コードは次のようになります。

arr = #()
for o in objects do (
    if superclassof o == light do (
        append arr o
    )
)

print arr

上のコードを実行すると変数arrにシーン内のライトが全部格納されるはずです。

コレクションから特定の条件のものを回収するときの書き方

このような、コレクションオブジェクトから特定の条件を満たすものを回収するという操作はさらにシンプルに書くことができます。

arr = for o in objects where superclassof o == light collect o

print arr

こんな風に一行で書くことができます。for文を回しながら、whereの条件を満たすようなoをcollectする、というような意味です。このように書くとシンプルに書けるだけでなく、検索する母数(この場合はObjectsが持っているオブジェクトの個数)が大きくなるとこの書き方のほうが速いという実利もあります。

※少数を対象にしている場合はほとんど差は体感できません。

Biped Rootを回収する

同様にしてBipedのルートオブジェクトを回収する方法を考えます。Bipedのルートを回収したいケースにはBipedに絡むツールを作ると割りと頻繁に遭遇しますが、これが一筋縄ではいきません。やってみましょう。

Bipedルートを選択してクラス、スーパークラスをそれぞれ確認してみます。

このようにBipedのクラスはBiped_Objectで、これはルートもそれ以外も全部共通です。さらにスーパークラスはGeometryObjectで、これは他のあらゆるジオメトリオブジェクトと共通です。そのため、クラスを使ってもスーパークラスを使ってもルートだけを回収することはできません。

Biped_Objectから辿って見つける

一つ目として、クラスがBiepd_Objectであるものを見つけ、そこからそのオブジェクトが属しているBipedのルートを探す、という方法があります。

arr = #()
for o in geometry do (
    if classof o == Biped_Object do (
        appendIfUnique arr o.controller.rootNode
    )
)

print arr

実行するとこんな感じです。

先に言ってしまうと、この方法はお勧めしません。

ここで使っているappendIfUniqueというビルトイン関数は、配列に対して、今から追加しようとしているものがまだ配列の中に存在しなければ追加し、既にある場合は追加しない、という動作をする関数です。

これは何かと便利なのですが、如何せん処理が遅いという問題があります。かといって、上記のコードではこれを使わないと同じルートオブジェクトが大量に回収されてしまい、実用的ではありません。

コントローラからダイレクトに見つける

Bipedを回収する際に定番なのは、コントローラの種類から見つける、というものです。Bipedのルートとそれ以外のパーツは、持っているコントローラのクラスが違うのです。

このように、Bipedのルートだけが、Vertical_Horizontal_Turnという特殊なコントローラを持っており、これによりシーン内からダイレクトにBipedルートだけを回収することができます。

arr = for o in geometry where classof o.controller == Vertical_Horizontal_Turn collect o

print arr

これだと直接Bipedルートを回収できます。もちろんBiped_Objectから辿る方法よりもはるかに速く動作します。

外部参照にも対応できる

3dsmaxの厄介な点として、外部参照でシーンに持ってきたオブジェクトは、基本的にすべてXRefObjectというクラスになってしまいます。classof を実行するとすべてXRefObjectが返ってきて、superclassofのほうは本来のsuperclassが返ってきます。

ただ、Bipedの場合前述のようにスーパークラスはGeometryObjectなので役に立ちません。

このcontrollerのクラスを見る方法であればたとえ外部参照になっていても問題なく識別できるため、Bipedを識別する場合にはcontrollerのクラスを見るのが一般的です。