PySideでMaya内のD&Dを受け取る方法!!

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() &amp;~ 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されたノードと一致するのでは!?っと考えました(強引)。

1つだけであれば、ノードを選択しなくてもD&DできちゃうのがMayaちゃんです。

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;amp;D.', QtGui.QMessageBox.Yes)
				
				# イベントを無効にする
				event.ignore()
				return
			
		# 作成したリストをもとに、アイテムをモデルに追加する
		for node in nodes:
			item = QtGui.QStandardItem(node)
			item.setFlags(item.flags() &amp;~ QtCore.Qt.ItemIsDragEnabled)#Dragできないようにする
			model.setItem(model.rowCount(), 0, item)
			
		event.accept()

使用頻度が高そうな「Outliner」だけ強引な実装になってしまいましたが、ユーザーエクスペリエンスが少しでも向上すれば幸いです!(`・ω・´)ゞ

P.S「中の方、、、是非、application/x-maya-dataのパースの仕方を教えてください。。。(´;ω;`)」