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

2021年9月3日

 前回までに、MaxScriptを使用した実行部分と、PySide2を使用したUI部分ができました。これらを使ってPySideのUIからMaxScriptの実行関数に情報を渡して動作させるツールを作成しましょう。

ツール本体のクラスを作成する 前編

 uiファイルをコンバートしてできたpyファイルはPythonのファイルなので、この中に実行ロジックを書き足して動作させることも一応可能です。が、このpyファイルはuiを更新するたびにコンバートを実行し、その都度上書きされてしまうものです。そのためここに実行ロジックを書くのは現実的ではありません。

 そこで、UIのクラスを継承した新たなクラスを作成し、そこに実行ロジックを書くという方法を使います。

UIのクラス定義を確認する

 まずは、uiをコンバートしてできたpyファイルの中身を見てみましょう。3dsmaxのスクリプトエディタで開いてみます。

 細かいところに違いはあると思いますが、だいたいこんな感じになっていると思います。重要なのはこのクラスの実装です。

 ここでUi_Dialogというクラスが、objectオブジェクトを継承して定義されています。このクラス名(大文字、小文字の区別も重要)と、継承しているのがobjectだという点を確認しておきます。

ツールのクラスを実装する

 スクリプトエディタで新規のファイルを作成し、gridCopy.pyというファイル名で、実行関数のmsファイルやUIのファイルと同じディレクトリに保存します。

 3dsmaxのスクリプトエディタはMaxScript用の設定になっているため、新規ファイルを作成するとMaxScriptとしてコードハイライトや評価などが行われます。そのため作業を始めるときにpyファイルとして保存し、Pythonであることを明示する必要があります。(pyファイルとして保存するとPythonとしてのコードハイライトや評価が行われるようになります。)

 これからツールのクラスを実装していくわけですが、やはり実行して状態を確認しながら作業を進めたいところです。そのため、クラス本体と合わせて実行用の関数も実装します。そのあたりまで踏まえて書いていきます。

エンコーディング指示を書く

# -*- coding: utf-8 -*-

インポートを書く

 まずはファイルの先頭に必要なライブラリのインポートを書きます。まずおすすめは、Python2系で3系のようなコーディングを可能にするための__future__モジュールからのインポートです。

※__future__モジュールについては詳述しませんが、Python2系→3系のアップグレードで大幅な更新がされ、コードの互換が著しく損なわれました。その差異を減らすためのモジュールが__future__で、これを使用するとPython2系でも3系のような書き方ができるようになります。

 私が必ずインポートするのは次の三つです。

from __future__ import absolute_import, print_function, division

このように、モジュールの一部だけをインポートする場合は、from モジュール名 import 名前空間 という書き方をします。

次に、3dsmaxの操作に必要な物をインポートします。

import MaxPlus
import pymxs

 MaxPlusは3dsmaxの操作全般を行うPythonAPIの核、pymxsはPythonからMaxScriptの機能を使用するためのものです。今回はMaxScriptで作った実行関数をPythonから実行する必要があるのでpymxsが必要になります。(使用しない場合はインポートする必要はありません。)

 そしてPySide2 からも必要なものをインポートします。

from PySide2 import QtWidgets

 さらに、前回作ったUIのPythonファイルをインポートします。が、ここで問題が生じます。Pythonのインポートは検索パスのルールがあり、任意の場所に置いたファイルはそのままではインポートできません。

 そこで、一時的に環境変数に現在の(このPythonファイルの)場所を追加するという操作を行います。この操作のためにsysとosという二つのモジュールが必要になります。

 インポートの順番はどうでも良いのですが、なるべくカテゴリを分けてインポートした方が見やすいと思います。ここまでの部分を私は以下のように書きました。

from __future__ import absolute_import, print_function, division
import os
import sys
import MaxPlus
import pymxs
from PySide2 import QtWidgets

 最初にコードの書き方そのものを左右する__future__モジュールからのインポートを行い、次にPythonのビルトインライブラリをインポートしています。そして3dsmax関係のモジュールをインポートし、最後にUI周りをインポートします。

 この後、UIクラスをインポートしますが、その前にパスを通す必要があります。

sys.path.append(os.path.dirname(os.path.abspath(__file__)))

 QtWidget のインポートの後に、この一行を実行します。

 環境変数のPathに、現在のPythonファイルがあるディレクトリを追加 しています。この追加はこのファイルが実行されている間のみ有効で、実行が終了すると追加されたパスは破棄されます。

※この__file__という特殊な変数はこのコードが書かれている実行中のPythonファイルのフルパスを格納していますが、ファイルを実行しない限り読み取ることはできません。そのため、ファイルをスクリプトエディタで編集しながら、エディタ上で「評価」した場合にはこの行はエラーになります。

 パス追加の後にUIファイルのインポートを行います。

import gridCopy_ui as UI

 インポートするファイル名(モジュール名)を書き、asを使用して別名を与えています。asを使用すると長いモジュール名、クラス名などに別名を与えて短くすることができます。

 そして、開発中に必要になるUIのリロードを書いておきましょう。

reload(UI)

 Pythonでは、同じモジュール、クラスなどを何度もインポートしてメモリを浪費することを回避するため、既に存在する名前空間のインポートは実行しません。つまり、インポートしているUIを更新して再実行した場合に、新しいものはインポートされず、既に読み込まれているものが使用されてしまいます。

 既にあるものを再読み込みしたい場合にはこのreloadを使用します。注意する点は、importは式なのでimport 〇〇という表記にはかっこ()がありませんが、reloadは関数なので引数をかっこ()で囲む必要がある、という点です。

 ここまでで、ファイルの先頭部分は以下のようになりました。

from __future__ import absolute_import, print_function, division
import os
import sys
import MaxPlus
import pymxs
from PySide2 import QtWidgets
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
import gridCopy_ui as UI
reload(UI)

 長くなってきたのでいったんこの辺で休憩しましょう。

 次回はクラス本体の実装と、その呼び出しについて説明します。

まとめ

  • Pythonファイルの先頭で必要なモジュール類のインポートを行う
  • 実行中のPythonファイルのフルパスは__file__という特殊な変数が保持している
  • 一度importしたものを再読み込みしたい場合はreload()を使う