使用 PySide2 开发 Maya 插件系列二:继承 uic 转换出来的 py 文件中的类 Ui_Form

使用 PySide2 开发 Maya 插件系列二:继承 uic 转换出来的 py 文件中的类 Ui_Form

开发环境:

Wing IDE 6.1

步骤1:

 打开 Wing IDE,创建一个新的 project,保存这个 project 到某个路径下,把之前生产的 py 文件所在的文件夹添加到该 project 中,然后在文件夹下新建一个 py 文件,我这里命名为 PySideTest.py

图中 PySide2ToPySide.py 是一个 PySide2 兼容 PySide 的一个补丁代码,出处链接:http://www.cnblogs.com/hksac/p/9502236.html,如果要用需要把不必要的测试代码删除,修改后的代码如下:

  1 from __future__ import with_statement
  2 
  3 import os
  4 import functools
  5 import imp
  6 import subprocess
  7 import sys
  8 import webbrowser
  9 
 10 
 11 class PySide2Patcher(object):
 12     _core_to_qtgui = set([
 13         "QAbstractProxyModel",
 14         "QItemSelection",
 15         "QItemSelectionModel",
 16         "QItemSelectionRange",
 17         "QSortFilterProxyModel",
 18         "QStringListModel"
 19     ])
 20 
 21 
 22     @classmethod
 23     def _move_attributes(cls, dst, src, names):
 24         """
 25         Moves a list of attributes from one package to another.
 26 
 27         :param names: Names of the attributes to move.
 28         """
 29         for name in names:
 30             if not hasattr(dst, name):
 31                 setattr(dst, name, getattr(src, name))
 32 
 33     @classmethod
 34     def _patch_QTextCodec(cls, QtCore):
 35         """
 36         Patches in QTextCodec.
 37 
 38         :param QTextCodec: The QTextCodec class.
 39         """
 40         original_QTextCodec = QtCore.QTextCodec
 41 
 42         class QTextCodec(original_QTextCodec):
 43             @staticmethod
 44             def setCodecForCStrings(codec):
 45                 pass
 46 
 47         QtCore.QTextCodec = QTextCodec
 48 
 49     @classmethod
 50     def _fix_QCoreApplication_api(cls, wrapper_class, original_class):
 51 
 52         wrapper_class.CodecForTr = 0
 53         wrapper_class.UnicodeUTF8 = 1
 54         wrapper_class.DefaultCodec = wrapper_class.CodecForTr
 55 
 56         @staticmethod
 57         def translate(context, source_text, disambiguation=None, encoding=None, n=None):
 58 
 59             if n is not None:
 60                 return original_class.translate(context, source_text, disambiguation, n)
 61             else:
 62                 return original_class.translate(context, source_text, disambiguation)
 63 
 64         wrapper_class.translate = translate
 65 
 66     @classmethod
 67     def _patch_QCoreApplication(cls, QtCore):
 68 
 69         original_QCoreApplication = QtCore.QCoreApplication
 70 
 71         class QCoreApplication(original_QCoreApplication):
 72             pass
 73         cls._fix_QCoreApplication_api(QCoreApplication, original_QCoreApplication)
 74         QtCore.QCoreApplication = QCoreApplication
 75 
 76     @classmethod
 77     def _patch_QApplication(cls, QtGui):
 78 
 79         original_QApplication = QtGui.QApplication
 80 
 81         class QApplication(original_QApplication):
 82             def __init__(self, *args):
 83                 original_QApplication.__init__(self, *args)
 84                 QtGui.qApp = self
 85 
 86             @staticmethod
 87             def palette(widget=None):
 88 
 89                 return original_QApplication.palette(widget)
 90 
 91         cls._fix_QCoreApplication_api(QApplication, original_QApplication)
 92 
 93         QtGui.QApplication = QApplication
 94 
 95     @classmethod
 96     def _patch_QAbstractItemView(cls, QtGui):
 97 
 98         original_QAbstractItemView = QtGui.QAbstractItemView
 99 
100         class QAbstractItemView(original_QAbstractItemView):
101             def __init__(self, *args):
102                 original_QAbstractItemView.__init__(self, *args)
103 
104                 if hasattr(self, "dataChanged"):
105                     original_dataChanged = self.dataChanged
106 
107                     def dataChanged(tl, br, roles=None):
108                         original_dataChanged(tl, br)
109                     self.dataChanged = lambda tl, br, roles: dataChanged(tl, br)
110 
111         QtGui.QAbstractItemView = QAbstractItemView
112 
113     @classmethod
114     def _patch_QStandardItemModel(cls, QtGui):
115 
116         original_QStandardItemModel = QtGui.QStandardItemModel
117 
118         class SignalWrapper(object):
119             def __init__(self, signal):
120                 self._signal = signal
121 
122             def emit(self, tl, br):
123                 self._signal.emit(tl, br, [])
124 
125             def __getattr__(self, name):
126                 return getattr(self._signal, name)
127 
128         class QStandardItemModel(original_QStandardItemModel):
129             def __init__(self, *args):
130                 original_QStandardItemModel.__init__(self, *args)
131                 self.dataChanged = SignalWrapper(self.dataChanged)
132 
133         QtGui.QStandardItemModel = QStandardItemModel
134 
135     @classmethod
136     def _patch_QMessageBox(cls, QtGui):
137 
138         button_list = [
139             QtGui.QMessageBox.Ok,
140             QtGui.QMessageBox.Open,
141             QtGui.QMessageBox.Save,
142             QtGui.QMessageBox.Cancel,
143             QtGui.QMessageBox.Close,
144             QtGui.QMessageBox.Discard,
145             QtGui.QMessageBox.Apply,
146             QtGui.QMessageBox.Reset,
147             QtGui.QMessageBox.RestoreDefaults,
148             QtGui.QMessageBox.Help,
149             QtGui.QMessageBox.SaveAll,
150             QtGui.QMessageBox.Yes,
151             QtGui.QMessageBox.YesAll,
152             QtGui.QMessageBox.YesToAll,
153             QtGui.QMessageBox.No,
154             QtGui.QMessageBox.NoAll,
155             QtGui.QMessageBox.NoToAll,
156             QtGui.QMessageBox.Abort,
157             QtGui.QMessageBox.Retry,
158             QtGui.QMessageBox.Ignore
159         ]
160 
161 
162         def _method_factory(icon, original_method):
163 
164             def patch(parent, title, text, buttons=QtGui.QMessageBox.Ok, defaultButton=QtGui.QMessageBox.NoButton):
165 
166                 msg_box = QtGui.QMessageBox(parent)
167                 msg_box.setWindowTitle(title)
168                 msg_box.setText(text)
169                 msg_box.setIcon(icon)
170                 for button in button_list:
171                     if button & buttons:
172                         msg_box.addButton(button)
173                 msg_box.setDefaultButton(defaultButton)
174                 msg_box.exec_()
175                 return msg_box.standardButton(msg_box.clickedButton())
176 
177             functools.update_wrapper(patch, original_method)
178 
179             return staticmethod(patch)
180 
181         original_QMessageBox = QtGui.QMessageBox
182 
183         class QMessageBox(original_QMessageBox):
184 
185             critical = _method_factory(QtGui.QMessageBox.Critical, QtGui.QMessageBox.critical)
186             information = _method_factory(QtGui.QMessageBox.Information, QtGui.QMessageBox.information)
187             question = _method_factory(QtGui.QMessageBox.Question, QtGui.QMessageBox.question)
188             warning = _method_factory(QtGui.QMessageBox.Warning, QtGui.QMessageBox.warning)
189 
190         QtGui.QMessageBox = QMessageBox
191 
192     @classmethod
193     def _patch_QDesktopServices(cls, QtGui, QtCore):
194 
195         if hasattr(QtGui, "QDesktopServices"):
196             return
197 
198         class QDesktopServices(object):
199 
200             @classmethod
201             def openUrl(cls, url):
202                 if not isinstance(url, QtCore.QUrl):
203                     url = QtCore.QUrl(url)
204 
205                 if url.isLocalFile():
206                     url = url.toLocalFile().encode("utf-8")
207 
208                     if sys.platform == "darwin":
209                         return subprocess.call(["open", url]) == 0
210                     elif sys.platform == "win32":
211                         os.startfile(url)
212                         return os.path.exists(url)
213                     elif sys.platform.startswith("linux"):
214                         return subprocess.call(["xdg-open", url]) == 0
215                     else:
216                         raise ValueError("Unknown platform: %s" % sys.platform)
217                 else:
218                     try:
219                         return webbrowser.open_new_tab(url.toString().encode("utf-8"))
220                     except:
221                         return False
222 
223             @classmethod
224             def displayName(cls, type):
225                 cls.__not_implemented_error(cls.displayName)
226 
227             @classmethod
228             def storageLocation(cls, type):
229                 cls.__not_implemented_error(cls.storageLocation)
230 
231             @classmethod
232             def setUrlHandler(cls, scheme, receiver, method_name=None):
233                 cls.__not_implemented_error(cls.setUrlHandler)
234 
235             @classmethod
236             def unsetUrlHandler(cls, scheme):
237                 cls.__not_implemented_error(cls.unsetUrlHandler)
238 
239             @classmethod
240             def __not_implemented_error(cls, method):
241                 raise NotImplementedError(
242                     "PySide2 and Toolkit don't support 'QDesktopServices.%s' yet. Please contact %s" %
243                     (method.__func__, 'asdf@qq.com')
244                 )
245 
246         QtGui.QDesktopServices = QDesktopServices
247 
248     @classmethod
249     def patch(cls, QtCore, QtGui, QtWidgets, PySide2):
250 
251         qt_core_shim = imp.new_module("PySide.QtCore")
252         qt_gui_shim = imp.new_module("PySide.QtGui")
253 
254 
255         cls._move_attributes(qt_gui_shim, QtWidgets, dir(QtWidgets))
256         cls._move_attributes(qt_gui_shim, QtGui, dir(QtGui))
257 
258 
259         cls._move_attributes(qt_gui_shim, QtCore, cls._core_to_qtgui)
260         cls._move_attributes(qt_core_shim, QtCore, set(dir(QtCore)) - cls._core_to_qtgui)
261 
262         cls._patch_QTextCodec(qt_core_shim)
263         cls._patch_QCoreApplication(qt_core_shim)
264         cls._patch_QApplication(qt_gui_shim)
265         cls._patch_QAbstractItemView(qt_gui_shim)
266         cls._patch_QStandardItemModel(qt_gui_shim)
267         cls._patch_QMessageBox(qt_gui_shim)
268         cls._patch_QDesktopServices(qt_gui_shim, qt_core_shim)
269 
270         return qt_core_shim, qt_gui_shim
271 
272 
273 
274 
275 import PySide2
276 from PySide2 import QtCore, QtGui, QtWidgets
277 
278 def _import_module_by_name(parent_module_name, module_name):
279 
280     module = None
281     try:
282         module = __import__(parent_module_name, globals(), locals(), [module_name])
283         module = getattr(module, module_name)
284     except Exception as e:
285         pass
286     return module
287 
288 
289 QtCore, QtGui = PySide2Patcher.patch(QtCore, QtGui, QtWidgets, PySide2)
290 QtNetwork = _import_module_by_name("PySide2", "QtNetwork")
291 QtWebKit = _import_module_by_name("PySide2.QtWebKitWidgets", "QtWebKit")
PySide2ToPySide.py

PySideTest.py 代码如下:

 1 # -*- coding: utf-8 -*-
 2 import sys
 3 try:
 4     from PySide import QtCore, QtGui
 5     import test_ui_pyside as ui
 6 except:
 7     from PySide2ToPySide import QtCore, QtGui   #注意:不能确保完全兼容,但常用的基本兼容
 8     import test_ui_pyside2 as ui    #使用 pyside2-uic 生成 test_ui_pyside2
 9     
10 class MainWindow(QtGui.QWidget, ui.Ui_Form):    # 如果 designer 新建的时候选的是 MainWindow,则要集成 QtGui.QMainWindow,其它的类型要对应好
11     def __init__(self, parent = None):
12         super(MainWindow, self).__init__(parent)    # 执行父类的__init__()
13         self.setupUi(self)  # 调用 ui.Ui_Form 的 setupUi()
14 
15 def main():
16     """ 和maya中的不一样 """
17     app = QtGui.QApplication(sys.argv)  # window是基于application的,所以在没有application的情况下要创建一个,如果在 maya 中,则不需要,因为maya本身就是一个 application
18     win = MainWindow()  #实例一个window
19     win.show()  # 显示window
20     sys.exit(app.exec_())   # 退出application,同时会释放win
21         
22 if __name__ == '__main__':  #对当前文件进行debug则会运行以下代码,import该文件不会运行,请了解模块默认属性 __name__ 的特点和用处
23     main()

这时候已经可以点击debug,运行结果:

步骤2:

设置 wing IDE 的 project 属性 Project->Project Properties...

这样的好处是可以让 IDE 有maya python 模块的命令补全。

新建一个 PySideTest_maya.py,这是提供给 maya 运行的:

 1 # -*- coding: utf-8 -*-
 2 import sys
 3 
 4 import PySideTest
 5 
 6 try:
 7     from PySide import QtCore, QtGui
 8     import shiboken
 9 except:
10     from PySide2ToPySide import QtCore, QtGui
11     import shiboken2 as shiboken
12     
13 import maya.OpenMayaUI as omui
14 def maya_main_window():
15     main_window_ptr = omui.MQtUtil.mainWindow()     #获得maya主窗口的指针,主要是为了让插件界面设置它为父窗口
16     return shiboken.wrapInstance(long(main_window_ptr), QtGui.QWidget)  #把maya主窗口封装从QtGui对象
17     
18 class MainWindow(PySideTest.MainWindow):
19     def __init__(self, parent = None):
20         super(MainWindow, self).__init__(parent)
21         
22         self.setWindowTitle("TestWindow")       #设置窗口标题
23         self.setWindowFlags(QtCore.Qt.Window)   #设置窗口标志为window,这样会使得widget成为独立窗口,不然会附着在maya的左上角,如果UI是继承的是QMainWindow,则不需要设置
24         self.setAttribute(QtCore.Qt.WA_DeleteOnClose)   #设置属性为关闭窗口则释放它的对象,窗口关闭,实例对象还存在,只要再次show即可,如果win再main中不断的新建MainWindow,则需要设置
25         
26 def main():
27     global win
28     try:
29         win.close() #为了不让窗口出现多个,因为第一次运行还没初始化,所以要try,在这里尝试先关闭,再重新新建一个窗口
30     except:
31         pass
32     win = MainWindow(maya_main_window()) #如果把win的初始化放在方法外,则不需要self.setAttribute(QtCore.Qt.WA_DeleteOnClose),同时关闭后再显示,还会保持上一次的窗口状态
33     win.show()
34 
35 if __name__ == "__main__":
36     main()

分别在 maya2015 和 maya2017 的 Script Editor 的 python tab 里编写如下代码:

1 import sys
2 sys.path.append(r'E:\Works\Maya\Scripts\PySideTest') #把代码所在的路径添加到环境变量PATH中,这样可以import它们
3 
4 import PySideTest_maya
5 reload(PySideTest_maya)
6 PySideTest_maya.main()

  选中需要运行的代码,Ctrl+Shift+Enter 运行:

运行结果:

 

回到总览使用 PySide2 开发 Maya 插件系列 总览 

 

posted @ 2018-11-18 13:40  ibingshan  阅读(3157)  评论(0编辑  收藏  举报