Over the past while I've written a couple of Qt-related tools to streamline my UI development. No big write-up this time, I'll just drop the tools and move on.
from PySide2 import QtWidgets def getUserFiles(description="Python File", ext=".py"): """Create a file dialog and return user selected files. Args are string description and allowed file types.""" fileWin = QtWidgets.QFileDialog( getMayaMainWindow(), filter="{0} (*{1})".format(description, ext)) fileWin.setFileMode(QtWidgets.QFileDialog.ExistingFiles) if fileWin.exec_(): inFiles = fileWin.selectedFiles() else: inFiles = [] fileWin.setParent(None) return inFiles
Just a convenience, really, since getting user files via GUI is such a common operation.
import sys import os import pyside2uic def compileUI(inFiles=None): """compile .ui to .py - made easy. pass file as argument, or else a file browser opens for you to select one. Assumes same name for compiled .py""" if not inFiles: # File browser if no file given inFiles = getUserFiles("Qt Designer Files", ".ui") for inFile in inFiles: outFile = inFile.replace(".ui", ".py") pyFile = open(outFile, "w") try: pyside2uic.compileUi(inFile, pyFile, False, 4, False) except: print("Failed. Invalid file name:\n{0}".format(inFile)) raise else: print("Success! Result: {0}".format(outFile)) finally: pyFile.close()
If you prefer to work with .py (or .pyc) over .ui files, you'll be compiling them a lot. May as well shorten it.
from PySide2 import QtWidgets def buildClass(custom, base="QWidget"): """Factory Function to build a single class from the given combination. The second argument (base class) may be class or a string, to allow use without importing QtWidgets from caller's frame. Used frequently with QtDesigner objects.""" if isinstance(base, str): base = getattr(QtWidgets, base) class UiWidg(custom, base): """Qt object composed of compiled (form) class and base class""" def __init__(self, parent=None): super(UiWidg, self).__init__(parent) # the compiled .py file has the content, # this class provides the widget to fill self.setupUi(self) return UiWidg def buildMayaDock(otherCls): """Get a Maya-dockable UI from the given qwidget subclass""" try: assert issubclass(otherCls, QtWidgets.QWidget) except (TypeError, AssertionError): print( "Base class {0} must inherit QWidget in order " "to be a Maya dock widget.".format(otherCls)) return class DockUi(mDock, otherCls): """Some dock signals are replaced by method stubs: - dockCloseEventTriggered - floatingChanged ALSO, .show() method has args the FIRST TIME it is called, by default: dockable=False, floating=True, area="left", allowedArea="all", width=None, height=None, x=None, y=None""" def __init__(self, parent=None): super(DockUi, self).__init__(parent) return DockUi
These two functions just help facilitate easy interfacing. My contribution to the simple buildClass function is allowing the base class to be passed as a string, which allows the calling frame to skimp on importing PySide2 itself. The Maya dock function takes a given QWidget subclass and returns a new type which plays nicely with Maya 2017's new docking system.
You can, of course, chain these together - and even combine with loadUiType if you want to start from a straight .ui file:
dockUi = buildMayaDock(buildClass(*loadUiType(uiFile))) dockUi.show(dockable=True)
Finally I have something that fixes the fact that PySide2 slots don't properly raise exceptions to the main application. The console silence is bad enough, but on top of that, my the UI that triggered the offending slot always locks up until I explicitly .update() it.
import traceback def SlotExceptionRaiser(origSlot): """A decorator function for a PySide2 slot which will raise any errors encountered in execution to the console""" @wraps(origSlot) def wrapper(*args, **kwargs): try: origSlot(*args, **kwargs) except: print("\nUncaught Exception in PySide Slot!\n") traceback.print_exc() #traceback.format_exc() return wrapper
Just decorate any slot function with this guy, and it'll let you know what error occurred and ensure your UI stays free.
EDIT 7/16: It appears that as of Maya 2017 Update 4, this decorator is no longer needed, as errors in slots are now raised properly. The fact that slot exceptions are printed as straight text (without the usual error font formatting) suggests to me that the official fix was more or less identical to the one I posted above. Hooray!
Most of this is basic stuff, but these kind of quality-of-life improvements add up. Til next time!