[qt][PySide][maya] windows that run both inside and outside maya

python
qt
maya

#1

So I am experimenting withe PySide/Qt in Maya 2016.
I thought I t might be useful to have windows that can launch both independently as a QApplication() and within maya. At least that way I can work on layouts etc outside of Maya directly.

This is the approach that I got working


import sys
from PySide.QtGui import *

class Form(QDialog):
    def __init__(self, parent=None):
        super(Form, self).__init__(parent)
        self.label=QLabel("hello world")
        layout=QVBoxLayout()
        layout.addWidget(self.label)
        self.setLayout(layout)

#try running otuside of maya
try:
    app = QApplication(sys.argv)
    form = Form()
    form.show()
    app.exec_()
    
#Within in Maya, QApplication exists and we get a RuntimeError, so try running inside of maya
except RuntimeError:
    from shiboken import wrapInstance 
    from maya import OpenMayaUI as omui 
    mayaMainWindowPtr = omui.MQtUtil.mainWindow() 
    mayaMainWindow= wrapInstance(long(mayaMainWindowPtr), QWidget)
    form = Form(parent=mayaMainWindow)
    form.show()

While this works, I would rather have the class handle things itself,
though I am unsure if that is even possible. My attempts to move the functionality inside the form are failing.
has anybody solved this?


#2

Whenever I’ve written something like this, I’ve done it rather similar to yours.
Though I tend to just have all the maya specific code in its own module for launching the tool within maya.

One thing to watch out for, if you run this when a QtCore.QCoreApplication is running, you’ll get some errors about not being able to create a QWidget when no gui is being used. (This is also crashing mayapy for me in 2016) Which can happen if you run maya.standalone.initialize before running your tool.


#3

i develop GUIs outside Maya with PySide so i can launch directly from Wing IDE and validate the layout, buttons, states, etc.

I split the GUI into two files - one file that is pure python, and the other file for use inside Maya which imports the first files and extends the GUI with maya specific functions.


from PySide import QtGui, QtCore

class TransformList(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(TransformList, self).__init__(parent)
        centralWidget = QtGui.QWidget()         
        self.setCentralWidget(centralWidget)
        mainLayout = QtGui.QVBoxLayout(centralWidget)
        self.listView = QtGui.QListView()
        mainLayout.addWidget(self.listView)



def _test():
    import sys
    app = QtGui.QApplication([]) 
    win = TransformList()
    win.show() 
    sys.exit(app.exec_())



if __name__ == '__main__':
    _test()

My Maya code will import this files, then subclass the GUI and parent it to the Maya Main Window.


import maya.cmds as cmds

from ui import TransformList

class TransformList_Maya(TransformList):
    def __init__(self, parent=getMayaMainWindow()):
        super(TransformList_Maya, self).__init__(parent)
        self.show()

def showTestUI_Maya():
    global _testUI
    try:
        _testUI.close()
        _testUI.deleteLater()
    except:
        pass
    _testUI = TransformList_Maya()
    return _testUI


#4

I have done something similar, called universal_tool_template,

a template runs in Maya, standalone and also cross-platform in either pyside or pyqt4, and a lot of automated functions, like Ui translation export and load

also, the ui creation can be text based, and support creating complex ui layout in a few lines of code.

here is github code for your download.


#5

Hi Mambo4

I faced some months ago the same necessity: i wanted a Qt launcher that would world exactly the same when launching the QApplication as standalone, as well as when launching the tool within the Maya QApp.
The reason for this is, it’s just better and easier for me when dealing with complicated widgets layouts, QDialogs, QMainWindows, etc to test them outside Maya.

My solution uses a ContextManager to handle this but it is not strictly necessary.

You can have a look at the entry in my blog where i try to explain it: http://jiceq.blogspot.com.es/search/label/Context%20Managers

You could just use the “application()” function and replace the “yield” statement with the code that gets the window both ways, but since the only difference is how you obtain the window instance and the rest of the code is the same i think it is ideal for a context manager. (Don’t Repeat Yourself)

I’ve been coding for almost 2 years little scripts in python for Maya and i’m still learning each day new things!!


#6

I typically use the same approach as rgkovach - primarily because it allows my core UI code to be dropped into multiple applications (maya, max, Mobu, Modo etc) without any bloated code to handle all the possible (and future!) scenarios.

Thus keeping the application agnostic code/modules completely pure ensures they are easy to maintain and allows each host application to subclass and make per-application tweaks or extensions.


#7

[QUOTE=MikeM;30854]I typically use the same approach as rgkovach - primarily because it allows my core UI code to be dropped into multiple applications (maya, max, Mobu, Modo etc) without any bloated code to handle all the possible (and future!) scenarios.

Thus keeping the application agnostic code/modules completely pure ensures they are easy to maintain and allows each host application to subclass and make per-application tweaks or extensions.[/QUOTE]

Yeah, i agree, in my case i havent faced yet the situation where i need to replicate same Ui for different applications so for now my approach works (I only work with Maya). But i agree that if i ever need to extend it to, for example Nuke, my approach would need to add for each new application an if statement that would detect which host app i’m running in.


#8

I thought I would come back to this thread to update how I now approach the issue -if only to make sure I am understanding things correctly. My solution is a just variation on the suggestions above. Here is the complete module. The approach is explained in the comments and doc strings.

'''
widget.py

    a module for QtWidget class that can launch stand-alone or from maya

    The two basic obstacles to this are:

        1: the QApplication : stand-alone we have to instantiate a QApplication object, in Maya we do not (Maya Main Window is the QApplication object)
        2: the QWidget instances `parent` argument : stand-alone = None, in maya, = reference to Maya Main Window

    Each obstacle is tackled with helper functions:

        1: launch_widget()
        2: get_parent()

    *note : We use Maya 2017 so this is using Pyside2.
    for  pyside you'll need to use QtGui instead fo QtWidgets (untested)

'''

from PySide2 import QtWidgets

def get_parent():
    '''
        get a value for a QtQWidget's parent
        if in Maya, returns MayaMainWindow Qt object
        else returns None
        :return: None or MayaWindow Qt Object
    '''

    parent=None
    try:
        parent = {o.objectName(): o for o in QtWidgets.qApp.topLevelWidgets()}["MayaWindow"]
    except Exception as e:
        print e
        pass
    print "parent={}".format(parent)
    return parent


class MyWidget(QtWidgets.QDialog):
    '''
        A basic QtWidget class.
        The only unusual bit is using get_parent() in the __init__()

    '''
    def __init__(self, parent=get_parent(), message="default text"):
        super( MyWidget, self).__init__(parent)
        self.label=QtWidgets.QLabel()
        layout=QtWidgets.QVBoxLayout()
        layout.addWidget(self.label)
        self.setLayout(layout)
        self.label.setText(message)
        self.setWindowTitle("MyWidget")

def launch_widget(message):
    '''
        a wrapper function for convenience

        Save this module as 'widget.py' and use :
            import widget
            widget.launch_widget("my message")
        it works the same in and outside of maya

    :param message: str
    :return: None
    '''

    # try standalone
    try:
        app = QtWidgets.QApplication([])
        widget = MyWidget(message=message)
        widget.show()
        app.exec_()

    # Within in Maya, QApplication exists and we get a RuntimeError, so try running inside of maya
    except RuntimeError:
        widget = MyWidget(message=message)
        widget.exec_() # QDialog instances have the exec_() method. For other QtWidgets use  widget.show() 

# for stand-alone testing
if __name__ == '__main__':
    launch_widget("I Stand Alone...")

Save this module as ‘widget.py’ and use :

    import widget
    widget.launch_widget("my message")

it works the same in and outside of maya.

I’ve used this approach on a few tools so far without issue, but if anyone spots any serious flaws inn my code or my thinking I’d love to hear about it.


#9

I have learned that in the Maya ‘except’ clause , using exec_() only works for an instance of QDialog.
Other QtWidgets such as QMainWindow do not have and exec_() method, and must use show().
Code comments updated accordingly.