MayaでGUIでノードを指定してもらいたい時、OutlinerやHyperGraphなどからノードをD&Dしてリストに登録できたら便利ですよね!
ちょっと強引な部分もございますが、Maya内でのD&Dでノード名を受け取る方法をご紹介したいと思います!(`・ω・´)ゞ
完成品は、このようなことができます!!
クラスの骨格
今回は、「QListView」から派生させて作りたいと思いますが、基底クラスはお好みで変更してください。また、Dropが受け付けられるように各オプションを設定します。(*´ω`*)b
from PySide import QtCore, QtGui from maya import cmds class MayaDragDropListView(QtGui.QListView): def __init__(self, *args, **kwargs): super(MayaDragDropListView, self).__init__(*args, **kwargs) self.setDragEnabled(True) self.setAcceptDrops(True) self.setDragDropMode(QtGui.QAbstractItemView.InternalMove)
MayaのD&Dの仕様を理解する
ここで、一度Maya内でD&Dした時の挙動を確認してみます。検証コードは以下の通りです。
from PySide import QtCore, QtGui from maya import cmds class MayaDragDropListView(QtGui.QListView): def __init__(self, *args, **kwargs): super(MayaDragDropListView, self).__init__(*args, **kwargs) self.setDragEnabled(True) self.setAcceptDrops(True) self.setDragDropMode(QtGui.QAbstractItemView.InternalMove) # Drag and Dropしたアイテムが、このWidgetに重なった時の処理 def dragEnterEvent(self, event): # Drag and Dropした時に、どのような情報を持っているか表示する print event.mimeData().formats() # 検証用に、表示する window = MayaDragDropListView() window.setWindowTitle('Drag and Drop View') window.setWindowFlags(QtCore.Qt.Window | QtCore.Qt.WindowStaysOnTopHint) window.resize(640, 480) window.setModel(QtGui.QStandardItemModel()) window.show()
表示したウィジェットに、あちらこちらからノードをD&Dすると以下のようになりました!
Hyper Graph | [u’application/x-maya-data’, u’text/plain’] |
Hyper Shade | [u’application/x-maya-data’, u’text/plain’] |
Node Editor | [u’application/x-maya-data’, u’text/plain’] |
Outliner | [u’application/x-maya-data’] |
「text/plain」はシンプルなテキスト情報で「QMimeData.text()」で簡単に取り出すことができます。「application/x-maya-data」については一旦おいておくことにします!
dragEnterEventの実装
続いては、D&Dできたアイテムを受け入れるかどうか処理する「dragEnterEvent」を実装していきます!
「text/plain」は「QMimeData.hasText()」で、「application/x-maya-data」は「QMimeData.hasFormat(<format>)」で調べることができます。これらの情報を持っている時は受け入れて、それ以外は受け入れないようにします。
from PySide import QtCore, QtGui from maya import cmds class MayaDragDropListView(QtGui.QListView): def __init__(self, *args, **kwargs): super(MayaDragDropListView, self).__init__(*args, **kwargs) self.setDragEnabled(True) self.setAcceptDrops(True) self.setDragDropMode(QtGui.QAbstractItemView.InternalMove) def dragEnterEvent(self, event): mimeData = event.mimeData() # text/plainがあって、application/x-maya-dataがあれば受け入れる if mimeData.hasText() and mimeData.hasFormat('application/x-maya-data'): event.accept() # application/x-maya-dataがあれば受け入れる elif mimeData.hasFormat('application/x-maya-data'): event.accept() # 想定外のフォーマットは受け入れない else: event.ignore()
dropEventの実装
続いては、受け入れたアイテムが実際のドロップした時の処理をする「dropEvent」を実装していきます。
from PySide import QtCore, QtGui from maya import cmds class MayaDragDropListView(QtGui.QListView): def __init__(self, *args, **kwargs): super(MayaDragDropListView, self).__init__(*args, **kwargs) self.setDragEnabled(True) self.setAcceptDrops(True) self.setDragDropMode(QtGui.QAbstractItemView.InternalMove) def dragEnterEvent(self, event): mimeData = event.mimeData() # text/plainがあって、application/x-maya-dataがあれば受け入れる if mimeData.hasText() and mimeData.hasFormat('application/x-maya-data'): event.accept() # application/x-maya-dataがあれば受け入れる elif mimeData.hasFormat('application/x-maya-data'): event.accept() # 想定外のフォーマットは受け入れない else: event.ignore() def dropEvent(self, event): mimeData = event.mimeData() model = self.model()# View に設定されたモデルを取得 nodes = []# ノードの一覧を格納する空のリスト # text/plainの場合 if mimeData.hasText():# 1 # 改行で分割して、ノードのリストを作成する nodes = mimeData.text().split('\n') # application/x-maya-dataの場合 elif event.mimeData().hasFormat('application/x-maya-data'): # 一旦おいておく pass # 作成したリストをもとに、アイテムをモデルに追加する for node in nodes:# 2 item = QtGui.QStandardItem(node) item.setFlags(item.flags() &~ QtCore.Qt.ItemIsDragEnabled)#Dragできないようにする model.setItem(model.rowCount(), 0, item) event.accept()
「dragEnterEvent」の時と同様に、持っているフォーマットによって処理を分岐していきます。(1)
「text/plain」の場合は、D&Dされたノード名を文字列で取得することができます。複数ある場合は、改行されながらノード名が羅列していくので、改行で文字列を分割してリストを作成します。
ここでも、「application/x-maya-data」については一旦おいておくことにします(;・∀・)
ノードのリストができたら、繰り返し処理をしながら、「QStandardItem」をモデルに登録していきます(2)。
「application/x-maya-data」の対応
「application/x-maya-data」は初めてみたフォーマットなので、Google先生でいろいろ検索したのですがうまくデータを取り出す方法が見つけられませんでした。現状Mayaの中の人にしかわからないデータのようです。
よく使われている「Outliner」からのD&Dだけ非対応、、、
そんなバカな、、、
でも、、、
逃げ道を発見しました(゚∀゚)
D&Dをする時、大半の人はノードを選択してからD&Dをしているように思えます。
っということは!!
「cmds.ls」で選択を取得すれば、D&Dされたノードと一致するのでは!?っと考えました(強引)。
D&Dされたのにノードの選択がない時は対処できますが、選択と違うノードがD&Dされた時は一致しない内容がリストにでてくる可能性があります。
エンドユーザーからしたら、バグでしかないのでご注意いただければっと思います。
この前提条件をもとに、実装したのが以下のコードになります。
from PySide import QtCore, QtGui from maya import cmds class MayaDragDropListView(QtGui.QListView): def __init__(self, *args, **kwargs): super(MayaDragDropListView, self).__init__(*args, **kwargs) self.setDragEnabled(True) self.setAcceptDrops(True) self.setDragDropMode(QtGui.QAbstractItemView.InternalMove) def dragEnterEvent(self, event): mimeData = event.mimeData() # text/plainがあって、application/x-maya-dataがあれば受け入れる if mimeData.hasText() and mimeData.hasFormat('application/x-maya-data'): event.accept() # application/x-maya-dataがあれば受け入れる elif mimeData.hasFormat('application/x-maya-data'): event.accept() # 想定外のフォーマットは受け入れない else: event.ignore() def dropEvent(self, event): mimeData = event.mimeData() model = self.model()# View に設定されたモデルを取得 nodes = []# ノードの一覧を格納する空のリスト # text/plainの場合 if mimeData.hasText(): # 改行で分割して、ノードのリストを作成する nodes = mimeData.text().split('\n') # application/x-maya-dataの場合 elif event.mimeData().hasFormat('application/x-maya-data'): # データかが解析できないので、選択されたノードを取得する nodes = cmds.ls(sl=True) # 選択されたノードがない場合は、ダイアログを表示して注意喚起をする。 if not nodes: QtGui.QMessageBox.information(None, 'Error', 'Please select a node and then D&amp;amp;amp;amp;amp;D.', QtGui.QMessageBox.Yes) # イベントを無効にする event.ignore() return # 作成したリストをもとに、アイテムをモデルに追加する for node in nodes: item = QtGui.QStandardItem(node) item.setFlags(item.flags() &~ QtCore.Qt.ItemIsDragEnabled)#Dragできないようにする model.setItem(model.rowCount(), 0, item) event.accept()
使用頻度が高そうな「Outliner」だけ強引な実装になってしまいましたが、ユーザーエクスペリエンスが少しでも向上すれば幸いです!(`・ω・´)ゞ
P.S「中の方、、、是非、application/x-maya-dataのパースの仕方を教えてください。。。(´;ω;`)」