使用 PySide2 開發 Maya 外掛系列二:繼承 uic 轉換出來的 py 檔案中的類 Ui_Form
阿新 • • 發佈:2018-11-19
使用 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 "QAbstractProxyModelPySide2ToPySide.py", 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__, '[email protected]') 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")
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 外掛系列 總覽