使用 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 插件系列 總覽 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM