株式会社デジタル・フロンティア-Digital Frontier

Header

Main

  • TOP
  • DF TALK
  • 【3dsMAX】MAXScriptでPythonつかってみた part02【PySide:友愛編】

【3dsMAX】MAXScriptでPythonつかってみた part02【PySide:友愛編】

2016/10/12

Tag: ,,,,,

みなさん初めましての方ははじめまして。part01を読んでくださった方はお久しぶりです。TD市川です。

前回は3dsMAX(以下MAX)でPythonを扱うための基礎をメインに、ひたすらボックスを並べていましたね。
それから約10ヶ月も経ってしまいましたが、ようやくpart02です!

part01はこちら↓
thumbnail

リニューアル!

いつも記事をご覧になってくださってる方ならお気づきかと思いますが、
DF TALKのページがリニューアル致しました! (わーわーぱふぱふー)
スタイリッシュな仕上がりで、いい感じではないかと!

スマートフォンやiPhoneからも見やすくなったみたいです。
記事の表示は3列まで表示できるようになったので情報が探しやすいですね。

サムネイルも表示できるようになったので、視覚的にも優しいです。
私はYoutubeなどでよく見かけるタイプのものを用意してみました。

これからも皆さんのお役に立てる情報を発信していきますので、DF TALKをよろしくお願い致します。
(ぁ、まだ終わりませんよ)

はじめに

唐突ですが、MAXScriptでUIを作ろうとすると結構たいへんですよね。
ボタンなどの位置を手打ちしなければならなかったり、ウィンドウの大きさが変えられないので配置に気を使ったり…
Maya人な私には、なかなか手強い相手だった記憶があります。
そこで今回は3dsMax 2015になって実装された「PySide」を使ってみたいと思います。

そのまえに

前回の記事でソースの上部に記述していたこの箇所

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

この部分が原因で動作しなかったという方がいらっしゃいました。
この記述はPythonファイルの文字コードをasciiからutf8に変更して日本語を取り扱えるようにする。
といったものなのですが…

この文字列をPythonファイルの1-2行目に記述して、
前回紹介した方法でpythonファイルを実行すると以下のエラーが出てしまいます。(test.pyを実行した場合)

-- Runtime error: <type 'exceptions.SyntaxError'> encoding declaration in Unicode string (test.py, line 0)

このエラーについては以下の事がわかっています。
・この記述をしてエラーが出てしまうのは、以下のようなケースです。
  python.ExecuteFileで実行したPythonファイルに記述されている場合
  python.Execute(“# -*- coding: utf-8 -*-“)のように直接実行した場合

・それ以外の場合は通常の扱いと変わりませんので、以下のようなケースではこの記述が無い場合エラーになってしまいます。
  python.Executeを使用して文字列でimportした
    【# -*- coding: utf-8 -*-】が無いPythonファイルに日本語が含まれていた場合

  python.ExecuteFileで実行したPythonファイル内でimportした
    【# -*- coding: utf-8 -*-】が無いPythonファイルに日本語が含まれていた場合

・python.ExecuteFileで実行したPythonファイルとpython.Executeで実行するpython処理に、
 この記述がなくても日本語を取り扱えていることから、内部的に変更を行っている可能性が高いです。

結構扱いが面倒なので日本語を使わないのが一番いいのかもしれません。

混乱させてしまう情報を掲示してしまい、大変申し訳ありませんでした。
今回の記事からは不要な場合は表記しないように致します。

では、気を取り直して…

ウィンドウを表示してみよう!

早速ですが、PySideのウィンドウを表示してみましょう。

from PySide import QtGui

window = QtGui.QWidget()
window.show()

こちらが実行結果です。
ui_01
これでPySideデビュー出来ました!

…が、しかし、
実際に実行された方はわかると思いますが、
このウィンドウ、MAXでなにか操作をしようとすると後ろに隠れてしまいます。

私もPySideを勉強し始めた時はこの問題がなかなか解決できずに苦労しました…
Mayaでツール開発をされている方ならご存知かと思いますが、
このウィンドウはMAXのメインウィンドウと親子関係が作られていないためこのような現象が起こります。

次は親子関係を作ってみましょう。

ウィンドウを後ろに行かせない!

親子関係を作ってみよう!と題を打ちたいところなのですが、
調査を試みた所、MAXでは親のウィンドウが取得できないようでした。

代案として、以下の方法で前面にUIを固定できます。

from PySide import QtGui
from PySide import QtCore

window = QtGui.QWidget()
window.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
window.show()

どうでしょう、後ろに隠れてしまう事はなくなったと思います。
しかしこのウィンドウ、常に全面に出てしまうのです。別のアプリケーションでも関係なく…
ui_03
自己主張が強めですね。

と、とりあえず目的は達成(?)できました。

ガベージコレクション

気を取り直して…(2回目)
以下のスクリプトをベースにしてUIをどんどんカスタマイズして行きましょう!

from PySide import QtGui
from PySide import QtCore

# ウィンドウのクラス
class Gui(QtGui.QWidget):
    def __init__(self, parent=None):
        super(Gui, self).__init__(parent)
        # 後ろに行かせない!設定 
        self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
        # ウィンドウのタイトル設定
        self.setWindowTitle('MyWindow')
        # ウィンドウのサイズ設定
        self.resize(250, 100)

# メインの処理
def main():
    # ウィンドウの作成
    w = Gui()
    # ウィンドウの表示
    w.show()

if __name__ == '__main__':
    main()

ではさっそく実行してみて下さい。
ui_04

…ぇ、あれ?
さっきまで普通にでてたのに一瞬でウィンドウ消えちゃうんだけど? また混乱させてしまう情報?

と、少し待って下さい。ちょっとした説明がしたくて一部の処理を抜いていました。
この処理を追加してみて下さい。

from PySide import QtGui
from PySide import QtCore

# GCプロテクター
class _GCProtector(object):
    widgets = []

# ウィンドウのクラス
class Gui(QtGui.QWidget):
    def __init__(self, parent=None):
        super(Gui, self).__init__(parent)
        # 後ろに行かせない!設定 
        self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
        # ウィンドウのタイトル設定
        self.setWindowTitle('MyWindow')
        # ウィンドウのサイズ設定
        self.resize(250, 100)

# メインの処理
def main():
    # ウィンドウの作成
    w = Gui()
    # ウィンドウの表示
    w.show()
    # プロテクターに追加
    _GCProtector.widgets.append(w)

if __name__ == '__main__':
    main()

問題なくウィンドウが開いたままになっていると思います。

この手法は公式のサンプルで紹介されていたものなのですが、
Pythonのガベージコレクションという機能がメモリを開放してしまう対策として使われています。

一般的には公式サンプルのようにQApplicationを作成するのですが、
MAXで使用するとメインウィンドウに問題が出てくるので今回は使用しません。

今回のように簡単なUIでしたら、main内の処理をグローバルに記述すればそんな面倒な事をしなくても良いのですが、
importして使いたい時などに不便ですので記憶の片隅に置いておいて下さい。

UIをカスタマイズしてみよう!

ここまで来たら後は自由です。
自分好みにどんどんカスタマイズしていきましょう!

from PySide import QtGui
from PySide import QtCore
import MaxPlus

# GCプロテクター
class _GCProtector(object):
    widgets = []

# ウィンドウのクラス
class Gui(QtGui.QWidget):
    def __init__(self, parent=None):
        super(Gui, self).__init__(parent)
        # 後ろに行かせない!設定 
        self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
        # ウィンドウのタイトル設定
        self.setWindowTitle('MyWindow')
        # ウィンドウのサイズ設定
        self.resize(200, 50)
        # ウィンドウの中身を作る
        self._initUI()

    def _initUI(self):
        # MyWindowのレイアウト作成
        myWindowLayout = QtGui.QVBoxLayout()

        # MyWindowの中身作成
        createBoxLayout = QtGui.QHBoxLayout()
        createBoxLabel  = QtGui.QLabel('Box :')
        createBoxButton = QtGui.QPushButton('Create')

        # 右寄せに
        createBoxLabel.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter)

        # Buttonを押した時の処理
        createBoxButton.clicked.connect(self.createBox)

        # createBoxLayoutに追加
        createBoxLayout.addWidget(createBoxLabel)
        createBoxLayout.addWidget(createBoxButton)

        # レイアウトに追加
        myWindowLayout.addLayout(createBoxLayout)

        # レイアウトをMyWindowにセット
        self.setLayout(myWindowLayout)

    def createBox(self):
        # part01で紹介したボックスを作る処理
        boxObject = MaxPlus.Factory.CreateGeomObject(MaxPlus.ClassIds.Box)
        boxNode   = MaxPlus.Factory.CreateNode(boxObject, 'testObject')

# メインの処理
def main():
    # ウィンドウの作成
    w = Gui()
    # ウィンドウの表示
    w.show()
    # プロテクターに追加
    _GCProtector.widgets.append(w)

if __name__ == '__main__':
    main()

こちらが実行結果です。
ui_02

ひとまず最低限UIとしての機能が盛り込めたと思います。
これでまたBoxをひたすら作れるようになりました。

おまけ

MAXScriptsのファイルダイアログが使いづらいと感じたので、
そこだけPySideを使用する為に作ったスクリプトです。

ポイント
①実行にpython.Executeを使用しているのは、ちゃっかりMAXScriptとPythonで文字列のやり取りをするためです。
②import文を使用する際は環境変数に登録されている場所に配置して使うか、sys.pathに追加してからimportして下さい。
③冒頭で話題にしていた【# -*- coding: utf-8 -*-】の記述は今回必要になります。

■MAXScriot

-- 実行準備
selectPath  = undefined
defaultPath = @"初期表示したいパス"
-- Python実行
python.Execute("import fileDialog as fd;reload(fd);fd.selectPath(r'"+defaultPath+"');")
-- 結果を表示
print selectPath

■Python[fileDialog.py]

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

from PySide import QtGui
import os
import MaxPlus

def selectPath(defaultPath):
    dPath = defaultPath

    # ディレクトリがなかった時の処理
    if not os.path.exists(dPath):
        dPath = r'C:\\'

    # ファイルダイアログを表示
    selectPath = QtGui.QFileDialog.getExistingDirectory(
                    caption="Select Output Postion",
                    dir=dPath,
                    options=QtGui.QFileDialog.ShowDirsOnly)

    # 指定されたかどうか
    if selectPath :
        # 文字列をEvalで実行してPythonからMAXに
        MaxPlus.Core.EvalMAXScript('selectPath = @"%s"'%selectPath)
    else:
        print 'Cancel'

こんな感じのダイアログが出ます。
ui_05

あとがき

ここまでお付き合い頂き、ありがとうございました。
Part01の冒頭でも書いていましたが、DF_TalkではPySideについて取り上げている記事が他にもあります。
こんなしょっぱいUIじゃ満足出来ないぜ!って方は、参考にしながら自分だけのUIを作ってみて下さい。

雑談になってしまうのですが、今年発表された3dsMax 2017では以下の機能が追加されたようです。
 ・python用のエディターが実装
 ・Qtdesignerで作成したuiファイルが読めるように
 ・pymxsモジュール登場

嬉しいような寂しいような…
他のDCCとの連携がもっと取りやすくなったのは良いことだと思います。
パイプラインで苦労された方もこれで少しは楽になるのでしょうかね?
弊社ではごく一部の部署でしかMAXを使用していないため、導入はいつになるのかわかりませんが…

次回はMAX以外の記事も書いてみようかと思っています。

※免責事項※
本記事内で公開している全ての手法・コードの有用性、安全性について、当方は一切の保証を与えるものではありません。
これらのコードを使用したことによって引き起こる直接的、間接的な損害に対し、当方は一切責任を負うものではありません。
自己責任でご使用ください。
Pocket

コメント

コメントはありません

コメントフォーム

コメントは承認制ですので、即時に反映されません。ご了承ください。

*