【MotionBuilder】PySideを使ってより快適に作業をしよう!
2017/6/12
Tag: MotionBuilder,PySide,UI
こんにちは、TD室の森本@DF大阪です。
なんだかんだ大阪に移ってから丁度1年経ちました。
こちらの環境にも慣れて引き続きTDとしてしっかりサポートしていきます。
さて、今回は最近他のDCCツールとの連携や共通ツール化をすすめるためPySideを使う機会が増えてきたので、
PySideをつかってMotionBuilder(以後MB)をより便利に活用する方法について書いていきます。
なお、今回紹介するコードについてはMB2015、MB2017にてテストしています。
弊社では最近MB2017を使う案件も増えてきていますが、
いままでもウィンドウのDockingが作業中に想定外のところで誤操作して外れたり、
逆にDockingしたりってことはよくあったのですが、Dockingの反応の精度が上がっていて
ちょっと動かすだけでウィンドウがDockingされてかなり面倒になっています。
ウィンドウをDockingせずに動かす操作方法等もあるのですが
うっかりウィンドウをそのままドラッグして想定外のUIレイアウトになることは
MBを触っている方には良くあると思います。
そこで、MBはUIがQTベースでできているため、Pysideからウィンドウの設定を変えてみたのが下記スクリプトになります。
# -*- coding: utf-8 -*- from PySide.QtCore import * from PySide.QtGui import * import re # MotionBuilderのウィンドウを取得 ptn=re.compile('MainBoard') for win in QApplication.topLevelWidgets(): if ptn.search(win.accessibleName()): break else: win=None if win: FlagNoDock=False NoDockWgtList=[] # メインウィンドウ以下のDockWidgetを取得 for cWgt in win.findChildren(QDockWidget): if cWgt.isFloating (): # DockWidgetの中でDocking状態になっていないWidget if cWgt.allowedAreas()==Qt.NoDockWidgetArea: NoDockWgtList.append(cWgt) # Dockingできない設定のWidgetを取得してリストに入れておく else: FlagNoDock=True cWgt.setAllowedAreas( Qt.NoDockWidgetArea ) print "SetNoDocking>>"+cWgt.windowTitle() # Docking許可されているWidgetをDocking不可にする # Docking状態になっていないウィンドウが全てDocking不可設定の場合元に戻す if not FlagNoDock: for oSetDockWgt in NoDockWgtList: oSetDockWgt.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea | Qt.TopDockWidgetArea | Qt.BottomDockWidgetArea) print "SetBackDocking>>"+oSetDockWgt.windowTitle()
7-13行までで、MotionBuilderのウィンドウのaccessibleNameがMainBoardという名前になっているため、
それを利用してMotionBuilderのメインウィンドウを取得しています。
18行でメインウィンドウを親にもつQDockingWidgetのリストを回して、
その中でFloating状態(Dockingされていない)のウィンドウを取得して、
26行でDockingができないように設定しています。
ただし、このままでは逆にDockingできる状態に戻せなくなってしまうので
21,22行でFloating状態のウィンドウのリストを作っておいて
31-34行で全てのFloatingウィンドウがDocking不可設定になっている場合元に戻す処理を入れています。
これでDockingしたくないウィンドウがウィンドウ移動時に想定外にDockingされる事態を防ぐことができます。
また、もう一つよくあることとしてUI操作中にウィンドウ上部を操作して
Dockingが外れてUIレイアウトが崩れることがよくありますが、
こちらもPysideを使って防ぐことが可能です。
# -*- coding: utf-8 -*- from PySide.QtCore import * from PySide.QtGui import * import re # MotionBuilderのウィンドウを取得 ptn=re.compile('MainBoard') for win in QApplication.topLevelWidgets(): if ptn.search(win.accessibleName()): break else: win=None if win: # メインウィンドウにスタイルシートを適用 win.setStyleSheet("QMainWindow::separator{width: 3px;height: 3px;}") for cWgt in win.findChildren(QDockWidget): cWgt.setFeatures(QDockWidget.DockWidgetFloatable|QDockWidget.DockWidgetClosable) # ドラッグしても外れないように設定、右上のボタンでのDocking解除とウィンドウ閉じる操作はそのまま残す
上記スクリプトではメインウィンドウにスタイルシートを適用しています。
マウスドラッグによるDocking解除機能停止とは関係ないのですが
ここではSeparatorの幅を小さくすることで少しでも広めにウィンドウを使えるようにしています。
スタイルシートの設定で色などのUIの見た目を色々変更できるので自分なりの見やすいデザインにするのもいいと思います。
これまでのスクリプトではメインウィンドウ以下のそれぞれのDockingWidgetを取得していますが、
それぞれのDockingWidgetから辿っていくことで各UI上のボタンを取得したりすることもできるので
より細かくMB上のUIを変更することができます。
(ただし一部例外はあります。例:FCurveEditor部分等)
また、既存のMBのウィンドウの中に独自のUIを組み込むことも可能です。
例えば前からViewerの隙間がもったいないと感じていたのですがボタンを仕込んでみましょう。
# -*- coding: utf-8 -*- from PySide.QtCore import * from PySide.QtGui import * import re def getDisplayRect(rootwgt): if rootwgt: #Viewerの中から上部のUIを含むWidgetを取得 oLyt=rootwgt.layout() oMainWgt=oLyt.itemAt(0).widget() for oChild in oMainWgt.children(): for oCChild in oChild.children(): if oCChild.accessibleName()=="ButtonBarWithRightBar": oUpperWgt=oCChild #Viewer上部のWidgetを取得 for oCCChild in oCChild.findChildren(QWidget): if oCCChild.accessibleName()=="Display": #Displayボタンの配置情報を取得 oDisplayRect=oCCChild.geometry() return oUpperWgt,oDisplayRect return None,None #Buttonを押したときの動作 def HelloWorld(): QMessageBox.information(win, button clicked", "Hello World !", QMessageBox.Ok) #MotionBuilderのウィンドウを取得 ptn=re.compile('MainBoard') for win in QApplication.topLevelWidgets(): if ptn.search(win.accessibleName()): break else: win=None #Viewerのウィンドウを取得 if win: RootViewer=None for cWgt in win.findChildren(QDockWidget): if cWgt.objectName()=="ToolWindow_1": RootViewer=cWgt break #Viewerウィンドウ内のDisplayボタンの配置と親ウィジェットの取得 oParentWgt,oDisplayRect=getDisplayRect(RootViewer) if oParentWgt and oDisplayRect: #親ウィジェットの子で新規ボタンをつくる oButton = QPushButton("Hello", oParentWgt) #Displayボタンの配置情報から隣に配置する x=oDisplayRect.right()+5 y=oDisplayRect.top() w=60 h=oDisplayRect.bottom()-oDisplayRect.top() oButton.setGeometry(x,y,w,h) oButton.clicked.connect(HelloWorld) oButton.show()
MBのメインウィンドウからそれぞれのウィジェットに付けられている名前を元に辿っていって、
Displayボタンの隣に新規にボタンを追加しています。
弊社ではViewerの空いている部分にシェルフのような機能を追加して個人単位で登録できるようにしています。
ここまでPysideをつかってUIをカスタマイズしてきましたが、MB起動時に毎回手動で実行するのは面倒なので
スタートアップスクリプトに仕込みたいところです。
ところが上記のようなスクリプトをスタートアップに仕込むと起動時にMBはフリーズを起こして起動できなくなります。
(MBのUIが構築できていない内に実行されてしまうためだと思われます)
そこでスタートアップで実行させるために以下のような処理を入れる必要があります。
# -*- coding: utf-8 -*- from pyfbsdk import * lSystem = FBSystem() lSysOnUIIdle = lSystem.OnUIIdle # UIIdleイベントの初期化(他にも使用しているツールがあった時の回避用) lSysOnUIIdle.RemoveAll() StartUp=-1 def RunStartUp(pOjbect, pEventName): global StartUp if StartUp==-1: # スタートアップスクリプトを実行 print "RunStartUp" StartUp=0 FBApplication().ExecuteScript(r"スタートアップのスクリプトファイルパス") else: # UIIdleイベントを解除 print "EventOff" lSysOnUIIdle.Remove(RunStartUp) # UIIdleイベントにスクリプト実行処理を追加 lSysOnUIIdle.Add(RunStartUp)
OnUIIdleのイベントはUIがIdle状態に入った際に実行されるイベントなので
こちらを使ってUIが落ち着いてからUIをカスタマイズするスクリプトを実行させます。
一通りスクリプトを実行させたらUIIdleイベントから解除することで複数回実行されないようにします。
このように実行用のスクリプトをつくってそちらをスタートアップに登録することで
起動時にフリーズする問題を回避することができます。
以上、今回はPySideをつかってMBのUIをカスタマイズする方法について書いてみました。
PySideは前に記事にしたMBの提供しているPythonSDKのUIより少し難しい部分はありますが、
その分カスタマイズできる範囲は広いため、色々な機能をもったUIを作ることができます。
(QtDesignerを使ってより直感的にGuiベースでUIを組むこともできます)
今回のようなカスタマイズは導入して劇的に作業効率に影響するようなものではありませんが、
普段のちょっとした不便なことが解消されることでより作業に集中できる環境を作り出すことはできます。
是非自分の使い方に合わせたカスタマイズで、より良い環境で作業を進めてください。
今回はこの辺りで、ではまた。
※免責事項※
本記事内で公開している全ての手法の有用性、安全性について、当方は一切の保証を与えるものではありません。
これらの手法を実施したことによって引き起こる直接的、間接的な損害に対し、当方は一切責任を負うものではありません。
自己責任でご使用ください
コメント
コメントフォーム