Maya2017以及Nuke10的PySide都升级到PySide2了,之前PySide1的大量代码都无法在新软件上使用,这主要是由于PySide2不仅调整了模块位置,还增删了一系列模块,下面我分享一段Shotgun的兼容代码,把模块位置调整回来:
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")
########################################################################################### # Test In Maya2017 widonw = QtGui.QWidget() widonw.show()
我们把这段代码复制到Maya2017中的Scirpt Editor中执行,可以看到,QtGui.QWidget()成功运行。说明当前PySide2中的模块已经按照PySide1的方式整理好了,兼容问题搞定,你可以沿用PySide1的调用方式来使用PySide2。
当然你也可以采用Autodesk官方提供的解决方案:
try: from PySide2.QtCore import * from PySide2.QtGui import * from PySide2.QtWidgets import * from PySide2 import __version__ from shiboken2 import wrapInstance except ImportError: from PySide.QtCore import * from PySide.QtGui import * from PySide import __version__ from shiboken import wrapInstance
但这种方案有个弊端,所有控件都没有缺少引入模块的标识,代码并不适合阅读。读者各取所长吧。