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

2021年9月3日

 その10で、更なる改善点を5つほど挙げました。そのうちの1つ目、入力欄を0に戻す「クリア」ボタンはすでにその10で実装済みなので、今回は「間隔の値には少数を入れられるようにする」と「タブストップ(タブキーでの入力欄の移動)を正しくする」を実現したいと思います。

間隔の値に少数を入れられるようにする

 現在、このGridCopy ツールの入力欄は、すべてSpenBox というウィジェットでできています。このSpinBox はもともと整数値しか入れられません。

 なんでそんなもんを使うんだよ、と思ったあなた、正解です。本来なら最初に良く考えて、個数は整数値しか入らないけれど間隔には少数も入るよね、ということを考慮したうえで作り始めるべきです。ここではいったん完成したものを改善していく、という段階を踏むために、あえて最初のバージョンではいろいろな穴を残してありました。(と、わざとですよということを強調してみる)

 ではこれを改善しましょう。

UI のSpin Box を Double Spin Box に変更する

 QtDesigner でgridCopy_ui.ui を開きます。開いたら「間隔」の入力欄として配置されているSpin Box を右クリックし、「他のクラスへ変更」をポイントしてサブメニューから「QDoubleSpinBox」を選択します。変更したらオブジェクト名も変更しておきましょう。

※オブジェクト名は変更しなくても動作上問題はありませんが、SpinBox とDoubleSpinBox が同じspn というプリフィックスだと後で混乱を招きそうなので、私はdspn_というプリフィックスに変更しました。

images/gridcopy08.gif

Double Spin Box の挙動を変更する

 Spin Box を配置した時は特に気にしませんでしたが、今回はDouble Spin Box のプロパティを確認してみましょう。

 修正した方が良いのは次の部分です。

images/ss24.png
  • decimals → 小数点以下の桁数
  • minimum → 最小値
  • maximum → 最大値
  • singleStep → ボックスの▲や▼を押したときに変化する数値  Spin Box から変換したので、Spin Box のプロパティが引き継がれてきます。singleStep は1になっており、これはさすがに不自然な動作に感じるのでここでは0.01 にしました。また、最大値については一考の余地があると思います。(ここではデフォルト値のままにしてあります)

UIを再変換する

 UIに修正を加えたら、必ず.ui を.py に変換する操作を行ってください。.ui ファイルを更新したのにツールで実際に読み込む.py が更新されておらず、「おかしいな」とアタマを捻る、といったことにならないよう、コンバートを忘れないようにしましょう。

pyside2-uic -o gridCopy_ui.py gridCopy_ui.ui

間隔の値を使っている部分のコードを修正する

 GridCopy クラスの doGridCopy でこの値を使用していますので、その部分を確認しましょう。

 オブジェクト名をspn_ からdspn_ に変更したので、それに合わせてコードも修正します。また、渡される値がint から float に変更されたため、それで問題がないかも確認しておきましょう。

    def doGridCopy(self):
        sel_count = MaxPlus.SelectionManager.GetCount()
        if sel_count != 1:
            mb = QtWidgets.QMessageBox()
            mb.setText(u"コピー元オブジェクトを1つ選択してください。")
            mb.exec_()
            return
        pymxs.runtime.doGridCopy(self.spn_x_count.value(), self.spn_y_count.value(), self.spn_z_count.value(), self.dspn_x_interval.value(), self.dspn_y_interval.value(), self.dspn_z_interval.value())
        MaxPlus.ViewportManager.ForceCompleteRedraw()

 ここではMaxScript のdoGridCopy を呼んでいますが、MaxScript ではint とfloat の区別は厳格ではないので、受け取り側はそのままで大丈夫です。

 また、clearInputs メソッドでもこのUIにアクセスしていますのでそこも修正します。

        def clearInputs(self):
                self.spn_x_count.setValue(0)
                self.spn_y_count.setValue(0)
                self.spn_z_count.setValue(0)
                self.dspn_x_interval.setValue(0)
                self.dspn_y_interval.setValue(0)
                self.dspn_z_interval.setValue(0)

UIの修正が反映されない

 さて、ここまでの修正で目的の改善は達成されたのですが、一発でここまでやらず、途中でテストしながらやった人は、もしかしたらUIを更新したのにそれが反映されない、という問題にぶつかったかもしれません。

 追ってPython のモジュールに関するエントリを書こうと思っているので詳細はそちらに譲りますが、ここではとりあえず簡単な説明と修正だけしておきます。

 UIが更新されないのはすでに読み込んだモジュールは再読み込みしない、というPython の仕様のためです。しかしそれを回避するために、reload(UI) という行を書いておいたはずでした。(その6参照)

 gridCopy.py にはreload(UI) というUIをリロードする処理が書かれているのですが、call_gridCopy.py からはgridCopy しか参照しておらず、これをリロードしていないため、gridCopy 内に書かれているreload が動作していない状態になっています。

 そこでこれを少々書き換えます。call_gridCopy.py でGridCopy クラスをインポートしている部分を以下のように書き換えます。

import gridCopy
reload(gridCopy)
from gridCopy import GridCopy

 詳細は項を改めますが、reload() でリロードできるのはモジュールだけです。ここでリロードしたいのはGridCopy というクラスですが、このクラスを直接リロードすることはできません。

 そこで、このクラスの定義が入っているモジュールを一旦インポートしておき、そのモジュールをリロードしたうえで、そこからクラスをインポートする、という手順を踏みました。

※この部分はリリース時には必要なくなるので削除してからリリースしましょう。

タブストップを正しくする

 では数値を入力してテストしてみましょう。ということでキーボードで数値を打つと、やはり次の欄へ移動するのにTAB キーをおしたりしますね。するとどうなるでしょうか。

images/gridcopy_09.gif

 なにやら不自然な動きをしていますね。

 タブストップがどうなるべきか、というのはツールの設計思想にもつながってくると思います。たとえばここで、個数を入れてから間隔を入れる、という動きにするのか、それとも各軸ごとに、Xの個数→Xの間隔→Yの個数→Yの間隔…という動きにするのか。この辺はどちらが正しいということではなく、設計思想の問題です。

 ただ、現状の私のものの動きはダメですね。このようにタブストップの設計には、どう考えてもダメというものは存在しますが、これが最高というものはひとつに決まらないこともあります。どうなっていると使いやすいかよく考えた上で、さらに実際に使う人たちからも意見をもらうと良いでしょう。

 ひとまず、ここでは個数を入れたあと間隔を入れる、という動作にしたいと思います。つまり縦に移動していく、という動きにします。

 QtDesigner でタブ順設定モードに切り替え、タブが移動してほしい順番にクリックしていくだけです。クリックすると1から順に番号が割り振られます。入力欄だけでなく、ボタンも移動対象になるので注意してください。

images/girdcopy_10/gif

 修正できたら.ui ファイルを上書きし、.py に変換し、3dsmax 上でツールを起動して確認しましょう。

まとめ

  • 数値入力で浮動小数点数を入力させる場合はQDoubleSpinBox を使う
  • 開発作業中はモジュールのリロードをコードの中に書いておくと作業がしやすい
  • タブ順の設定はQtDesigner のタブ順指定モードを使うと簡単にできる
  • UIを更新したらpyへの変換を忘れずに