Python + MaxScript のツール開発入門(Max2020版)その1

2021年9月3日

仕様の検討

動作の検討

 下の画像のような、選択したオブジェクトをX軸、Y軸、Z軸それぞれ等間隔にコピー配置する、というツールを考えます。そんなものをいつ使うんだという話はありますが、ここでは単純にツールに落とし込む練習としてこれを題材に取り上げる、というだけです。

 画像では元のティーポットを選択し、Shift+移動で個数を指定してコピーしています。一方向にコピーしたらそれを元にして次の方向へコピーする、ということを繰り返し、X、Y、Zそれぞれの方向へグリッド状にオブジェクトを配置しています。

 これを、ツール化します。

入力の検討

 まず、ユーザに入力させる情報をどうするか考えます。必要な情報は6つあります。

  • X方向のコピー数とコピー間隔
  • Y方向のコピー数とコピー間隔
  • Z方向のコピー数とコピー間隔  シーン内のオブジェクトを選択し、6つの情報を入力して実行したら結果が得られる、というのが良さそうです。

設計の検討

 ツールが何を受け取って何を行うのか、ということが決まったら、それをどういう風に実装するかを考えます。

Python かMaxScript か

 この実行ロジックを実装するのに、Pythonを使うべきか、MaxScriptを使うべきか、というのは悩みどころです。

 ポイントは、実行動作が投げっぱなしで良いかどうか、という点です。ツールを実行した結果、シーン内に何らかの変化がもたらされ、その変化そのものが結果であれば良い場合、換言すると、実行関数に戻り値が無い(void)、または成功か失敗か(bool)を返す程度で良い場合は、どちらを選択しても大差ありません。慣れている方を使用すれば良いと思います。

 しかし、実行の結果何らかの情報を取得し、それを他の処理に使用するような場合、PythonとMaxScriptの間でその戻り値を受け渡すプロセスが発生します。戻り値の型によっては簡単に変換して受け渡しできるものもありますが、逆に受け渡しがやっかいなもの、できないものなどもあります。その場合は、ツールの他の部分(戻り値を受け取る部分)と実行部分は同じ環境に統一した方が良いでしょう。

 今回の場合、受け取る情報は7つ(元になるオブジェクト、X個数、X間隔、Y個数、Y間隔、Z個数、Z間隔)もありますが、実行そのものはシンプルです。各軸方向へコピーを行うだけです。実行結果はシーンの状態として得られれば良く、戻り値は必要ありません。そのため、UIはPythonで作ろうと考えていますが、実行部分はMaxScriptでも良い、という判断ができます。(もちろんPythonでも良いです。この連載の後ろの方で、同じ内容をPythonでも実装してみる予定です。)

関数かクラスか

 実装に使用するスクリプト環境を決めたら早速書いていきたいところですが、この実装をどのように行うか、というのもまた検討を要します。

 MaxScriptで実装する場合、クラスという概念はないので、構造体を使用するか、関数で実装するか、ということになります。

 判断のポイントは、関数としての汎用性があるかどうか、という点になるかと思います。(この辺は開発者によってポリシーなどもあるので一概にこれが正しいと言えない部分ですが、私の場合はこのような観点で考えています)

 そのツールでしか使わないような動作であれば、必要なデータともども構造体にまとめてしまい、構造体の中で機能が完結するようにするのが良いです。

 他のツールからでも使用できそうな機能であれば、その部分は汎用関数的に書いておくのが、再利用性という意味でも良いでしょう。

関数設計のポイント

 必要なデータはすべて引数として受け取るようにしましょう。構造体のメンバ関数の場合は、メンバ変数をメンバ関数内で使用するといった実装も行いますが、単体の関数として設計するのであれば、必要なものはすべて引数として受け取り、関数の外にある変数などを使わないような設計にしておくのが良いです。

関数を定義する

 ではさっそく、今回の実行の本体になる関数を実装していきましょう。まずは関数の枠を書きます。MaxScriptでは、関数の宣言と定義は分かれておらず、定義した場所で宣言もされます。

関数の枠を書く

 元のオブジェクト、X個数、Y個数、Z個数、X間隔、Y間隔、Z間隔の7つの引数を受け取る関数です。この関数は結果を返すのではなく、シーン自体を操作するものなので、一旦戻り値はなしで書いていきます。(成否を返す関数にすることもできますが、それは追々考えることにして、一旦は戻り値なしで書いておきます。)

function gridCopy base_node x_count y_count z_count x_interval y_interval z_interval = (
    --------------------------------------
    -- 選択したノードをグリッド状にコピーする
    --  param
    --          node: target_node
    --          int: x個数
    --          int: y個数
    --          int: z個数
    --          int: x間隔距離
    --          int: y間隔距離
    --          int: z間隔距離
    --------------------------------------
)

 この例のような感じで、その関数の概要をコメントとして書いておくと便利です。引数に渡すものがなんなのか、戻り値がある場合は何が戻るのかも書いておくと、後で使いまわす際にわかりやすいでしょう。特にこの関数のように引数が多い場合、何をどういう順番で渡せばいいのかが混乱しやすいため、コメントは書いておいたほうが後々の為になります。

テスト用のコードを書く

 もちろんいきなり中身を実装していっても良いですが、複雑な動作を実装する場合は動作状態を確認するコードを書きながら進めていくのが良いでしょう。

 また、今回のような引数の多い関数はテストするためのコードも書いてしまった方が便利です。

function gridCopy base_node x_count y_count z_count x_interval y_interval z_interval = (
    --------------------------------------
    -- 選択したノードをグリッド状にコピーする
    --  param
    --          node: target_node
    --          int: x個数
    --          int: y個数
    --          int: z個数
    --          int: x間隔距離
    --          int: y間隔距離
    --          int: z間隔距離
    --------------------------------------
    print base_node
    print x_count
    print y_count
    print z_count
    print x_interval
    print y_interval
    print z_interval
)
gridCopy selection[1] 3 3 3 50 50 50

 テストコードとして、受け取った引数をそのままprintする(リスナーに出力する)という内容を書き、関数の外にこの関数(gridCopy)を呼び出すコードを書きました。現在選択されているオブジェクトの先頭のものを渡し、X、Y、Zの個数はすべて3、間隔はすべて50を渡しています。

 実行すると以下のようになります。リスナーにprintの結果が表示されました。

 次回から、このコードをベースに、テストを実行しながら中身を詰めていきましょう。

まとめ

  • どんな結果を得るツールを作るか考える
  • ユーザから何を受け取り、何を実行するか考える
  • 動作をどのように実装するか考える(Python or MaxScript、クラス or 関数など)
  • 実行部分の関数を書く(必要なものは引数で受け取るようにする)
  • テストコードを書く