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

2023-06-25,,

使用 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,如果要用需要把不必要的测试代码删除,修改后的代码如下:

 from __future__ import with_statement

 import os
import functools
import imp
import subprocess
import sys
import webbrowser class PySide2Patcher(object):
_core_to_qtgui = set([
"QAbstractProxyModel",
"QItemSelection",
"QItemSelectionModel",
"QItemSelectionRange",
"QSortFilterProxyModel",
"QStringListModel"
]) @classmethod
def _move_attributes(cls, dst, src, names):
"""
Moves a list of attributes from one package to another. :param names: Names of the attributes to move.
"""
for name in names:
if not hasattr(dst, name):
setattr(dst, name, getattr(src, name)) @classmethod
def _patch_QTextCodec(cls, QtCore):
"""
Patches in QTextCodec. :param QTextCodec: The QTextCodec class.
"""
original_QTextCodec = QtCore.QTextCodec class QTextCodec(original_QTextCodec):
@staticmethod
def setCodecForCStrings(codec):
pass QtCore.QTextCodec = QTextCodec @classmethod
def _fix_QCoreApplication_api(cls, wrapper_class, original_class): wrapper_class.CodecForTr = 0
wrapper_class.UnicodeUTF8 = 1
wrapper_class.DefaultCodec = wrapper_class.CodecForTr @staticmethod
def translate(context, source_text, disambiguation=None, encoding=None, n=None): if n is not None:
return original_class.translate(context, source_text, disambiguation, n)
else:
return original_class.translate(context, source_text, disambiguation) wrapper_class.translate = translate @classmethod
def _patch_QCoreApplication(cls, QtCore): original_QCoreApplication = QtCore.QCoreApplication class QCoreApplication(original_QCoreApplication):
pass
cls._fix_QCoreApplication_api(QCoreApplication, original_QCoreApplication)
QtCore.QCoreApplication = QCoreApplication @classmethod
def _patch_QApplication(cls, QtGui): original_QApplication = QtGui.QApplication class QApplication(original_QApplication):
def __init__(self, *args):
original_QApplication.__init__(self, *args)
QtGui.qApp = self @staticmethod
def palette(widget=None): return original_QApplication.palette(widget) cls._fix_QCoreApplication_api(QApplication, original_QApplication) QtGui.QApplication = QApplication @classmethod
def _patch_QAbstractItemView(cls, QtGui): original_QAbstractItemView = QtGui.QAbstractItemView class QAbstractItemView(original_QAbstractItemView):
def __init__(self, *args):
original_QAbstractItemView.__init__(self, *args) if hasattr(self, "dataChanged"):
original_dataChanged = self.dataChanged def dataChanged(tl, br, roles=None):
original_dataChanged(tl, br)
self.dataChanged = lambda tl, br, roles: dataChanged(tl, br) QtGui.QAbstractItemView = QAbstractItemView @classmethod
def _patch_QStandardItemModel(cls, QtGui): original_QStandardItemModel = QtGui.QStandardItemModel class SignalWrapper(object):
def __init__(self, signal):
self._signal = signal def emit(self, tl, br):
self._signal.emit(tl, br, []) def __getattr__(self, name):
return getattr(self._signal, name) class QStandardItemModel(original_QStandardItemModel):
def __init__(self, *args):
original_QStandardItemModel.__init__(self, *args)
self.dataChanged = SignalWrapper(self.dataChanged) QtGui.QStandardItemModel = QStandardItemModel @classmethod
def _patch_QMessageBox(cls, QtGui): button_list = [
QtGui.QMessageBox.Ok,
QtGui.QMessageBox.Open,
QtGui.QMessageBox.Save,
QtGui.QMessageBox.Cancel,
QtGui.QMessageBox.Close,
QtGui.QMessageBox.Discard,
QtGui.QMessageBox.Apply,
QtGui.QMessageBox.Reset,
QtGui.QMessageBox.RestoreDefaults,
QtGui.QMessageBox.Help,
QtGui.QMessageBox.SaveAll,
QtGui.QMessageBox.Yes,
QtGui.QMessageBox.YesAll,
QtGui.QMessageBox.YesToAll,
QtGui.QMessageBox.No,
QtGui.QMessageBox.NoAll,
QtGui.QMessageBox.NoToAll,
QtGui.QMessageBox.Abort,
QtGui.QMessageBox.Retry,
QtGui.QMessageBox.Ignore
] def _method_factory(icon, original_method): def patch(parent, title, text, buttons=QtGui.QMessageBox.Ok, defaultButton=QtGui.QMessageBox.NoButton): msg_box = QtGui.QMessageBox(parent)
msg_box.setWindowTitle(title)
msg_box.setText(text)
msg_box.setIcon(icon)
for button in button_list:
if button & buttons:
msg_box.addButton(button)
msg_box.setDefaultButton(defaultButton)
msg_box.exec_()
return msg_box.standardButton(msg_box.clickedButton()) functools.update_wrapper(patch, original_method) return staticmethod(patch) original_QMessageBox = QtGui.QMessageBox class QMessageBox(original_QMessageBox): critical = _method_factory(QtGui.QMessageBox.Critical, QtGui.QMessageBox.critical)
information = _method_factory(QtGui.QMessageBox.Information, QtGui.QMessageBox.information)
question = _method_factory(QtGui.QMessageBox.Question, QtGui.QMessageBox.question)
warning = _method_factory(QtGui.QMessageBox.Warning, QtGui.QMessageBox.warning) QtGui.QMessageBox = QMessageBox @classmethod
def _patch_QDesktopServices(cls, QtGui, QtCore): if hasattr(QtGui, "QDesktopServices"):
return class QDesktopServices(object): @classmethod
def openUrl(cls, url):
if not isinstance(url, QtCore.QUrl):
url = QtCore.QUrl(url) if url.isLocalFile():
url = url.toLocalFile().encode("utf-8") if sys.platform == "darwin":
return subprocess.call(["open", url]) == 0
elif sys.platform == "win32":
os.startfile(url)
return os.path.exists(url)
elif sys.platform.startswith("linux"):
return subprocess.call(["xdg-open", url]) == 0
else:
raise ValueError("Unknown platform: %s" % sys.platform)
else:
try:
return webbrowser.open_new_tab(url.toString().encode("utf-8"))
except:
return False @classmethod
def displayName(cls, type):
cls.__not_implemented_error(cls.displayName) @classmethod
def storageLocation(cls, type):
cls.__not_implemented_error(cls.storageLocation) @classmethod
def setUrlHandler(cls, scheme, receiver, method_name=None):
cls.__not_implemented_error(cls.setUrlHandler) @classmethod
def unsetUrlHandler(cls, scheme):
cls.__not_implemented_error(cls.unsetUrlHandler) @classmethod
def __not_implemented_error(cls, method):
raise NotImplementedError(
"PySide2 and Toolkit don't support 'QDesktopServices.%s' yet. Please contact %s" %
(method.__func__, 'asdf@qq.com')
) QtGui.QDesktopServices = QDesktopServices @classmethod
def patch(cls, QtCore, QtGui, QtWidgets, PySide2): qt_core_shim = imp.new_module("PySide.QtCore")
qt_gui_shim = imp.new_module("PySide.QtGui") cls._move_attributes(qt_gui_shim, QtWidgets, dir(QtWidgets))
cls._move_attributes(qt_gui_shim, QtGui, dir(QtGui)) cls._move_attributes(qt_gui_shim, QtCore, cls._core_to_qtgui)
cls._move_attributes(qt_core_shim, QtCore, set(dir(QtCore)) - cls._core_to_qtgui) cls._patch_QTextCodec(qt_core_shim)
cls._patch_QCoreApplication(qt_core_shim)
cls._patch_QApplication(qt_gui_shim)
cls._patch_QAbstractItemView(qt_gui_shim)
cls._patch_QStandardItemModel(qt_gui_shim)
cls._patch_QMessageBox(qt_gui_shim)
cls._patch_QDesktopServices(qt_gui_shim, qt_core_shim) return qt_core_shim, qt_gui_shim import PySide2
from PySide2 import QtCore, QtGui, QtWidgets def _import_module_by_name(parent_module_name, module_name): module = None
try:
module = __import__(parent_module_name, globals(), locals(), [module_name])
module = getattr(module, module_name)
except Exception as e:
pass
return module QtCore, QtGui = PySide2Patcher.patch(QtCore, QtGui, QtWidgets, PySide2)
QtNetwork = _import_module_by_name("PySide2", "QtNetwork")
QtWebKit = _import_module_by_name("PySide2.QtWebKitWidgets", "QtWebKit")

PySide2ToPySide.py

PySideTest.py 代码如下:

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

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

步骤2:

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

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

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

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

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

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

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

运行结果:

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

使用 PySide2 开发 Maya 插件系列二:继承 uic 转换出来的 py 文件中的类 Ui_Form的相关教程结束。

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

下载本文的Word格式文档,以方便收藏与打印。