Maya 2014, PySide, and dockControl

Some time ago I created a simple Toolbox using PySide and a dockControl for it so it would live with the Attribute Editor and Channel Box.

Every once in awhile an artist will hit some errors trying to open the UI and I am trying to track down what is causing the failure (the UI will open correctly on a second attempt).

Here are the types of errors that I am getting and my suspicion is aimed at how I manage the dockControl (otherwise, multiple copies of the window will keep docking themselves).

RuntimeError: Controls must have a layout.  No layout found in window :

This occurs on the line creating the dockControl and assigning the UI as the contents.

RuntimeError: Object's window not found.

This occurs on the line setting the widget for a QScrollArea

I create the UI using a common pattern to ensure only one instance of the UI is open:


def openGUI():
    global toolbox
    try: 
        toolbox.close()
        toolbox.deleteLater()
    except: pass
    toolbox = MyToolbox()

Inside the init method, I create the dockControl. I re-implemented the close method to delete the dockControl.


    def __init__(self, parent=getMayaMainWindow()):
        # usual stuff
        self.dockCtrl = cmds.dockControl(aa=['right', 'left'], a='right', 
                                         content=self.objectName(), w=370, 
                                         label='My Tool Box')

    
    def closeEvent(self, event):
        cmds.deleteUI(self.dockCtrl)
        QtGui.QWidget.closeEvent(self, event)

I feel like I am doing something wrong with managing the visibility of the UI and the dockControl, a chicken-and-the-egg type of scenario.
Any advice?

Thanks.

I assume that closeEvent is somehow raised by doing the toolbox.close()? One thing that was just discussed is that you can’t close a piece of UI from within a callback generated from that UI. You may need to use evalDeferred to close the UI. If the closeEvent asserts, obviously the base class closeEvent isn’t called. There could be some weirdness here. Also, if you do the close via evalDeferred, you many need to defer the MyToolbox() constructor as well if they are sharing any globals.

thanks for the advice, i’ll try messing around with the timing of the UI getting closed and the deletion of the dockControl.

I’m having the same issue. My script is running at startup, but I think the script runs before there’s sufficient UI to parent to, so it fails. Also, once Maya is running if there are no docked tabs on the right side of the screen the script will fail. Opening up a tab will fix this problem. Is there any way to put a check in my script to see if a tab is opened, and if not open a tab (like attr editor/channel box) to parent my UI to?

For stuff like this I like to create a scriptJob that runs on the idle event. When Maya is fully loaded and GTG it will run the idle scriptJob and open the UI. Make sure to use the runOnce option.

You can also use maya.utils.executeDeferred to trigger UI stuff. It works exactly the same as the scriptJob idle event with the runOnce flag.

a note on dockcontrols, use the hideEvent rather than close

I too was struggling with the whole dockControl thing. I’d get winEvent errors and sometimes the UI would not cleanup properly. Also, I wanted to have a function call when the UI closed but both closeEvent and hideEvent gets triggered when you dock and undock the UI.

I started looking into using the MayaQWidgetDockableMixin class.

So I have my UI inherit this class:

class Tool(MayaQWidgetDockableMixin,QtGui.QDialog):
...
    def dockCloseEventTriggered(self):
        """
        close and cleanup
        """
        self.close()
        self.deleteLater()

Then override the dockCloseEventTriggered method. This is only triggered when you truly close the UI.

Be aware though. The MayaQWidgetDockableMixin has a typo in the setDockableParameters. Line 299 is suppose to be dockWidget.setAllowedAreas(areaValue) and not dockWidget.setAllowedArea(areaValue)
Without this fix you UI will just dock to any area regardless what you set for the allowedArea.
http://tech-artists.org/forum/showthread.php?5670

-Sean

Some great info in this thread. The runOnce on idleEvent was just what I needed.

@snolan, I’d like to try the mixin solution out because I’m running into the same problems, but I’m getting this error:

NameError: name ‘MayaQWidgetDockableMixin’ is not defined //

… any thoughts?

Did you import it? You need to do something like the following:

from maya.app.general.mayaMixin import MayaQWidgetDockableMixin

[QUOTE=RFlannery;28286]Did you import it? You need to do something like the following:

from maya.app.general.mayaMixin import MayaQWidgetDockableMixin

[/QUOTE]

Opps, thanks for clarifying. I should have been more clear. :):

Maya 2016 Extension 1 has added “closeCommand” and “state” flags!

http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/Commands/dockControl.html

I’ve found a few threads about how to get around the PySide window bug in Maya, but I haven’t found an elegant solution. I settled on this a while back:

# Development stuff
try:
    main_ui.close()
    main_ui.deleteLater()
except:
    pass

# Show stuff
main_ui= MainWindow.Build()
main_ui.show()

# Development stuff
try:
    main_ui.show()
except:
    main_ui.close()
    main_ui.deleteLater()

I can’t remember where I found this solution, but it works. There has to be a better way to manage dockUI’s though.

I’ve been trying to play around with the closeCommand that rgkovach123 mentioned, but at some point I still get the error:

RuntimeError: Controls must have a layout. No layout found in window : // 

Here’s what I have so far (note, this code is in a separate module from my MainWindow module):


# - mb_aam
#     - cmds
#         dock_ui.py

import os
from functools import partial

try:
	import maya.cmds as mc
except:
	pass

def _close_command(self=None, *args):
	self.close()
	self.deleteLater()

def dock_ui(self=None):
	'''Docking main window'''

	software = os.getenv('SOFTWARE')

	if software == 'maya':

		if mc.dockControl(self.objectName(), q=1, ex=1):
			mc.deleteUI(self.objectName())
		self.dockCtrl = mc.dockControl(
			aa='right', a='right', w=500,
			content = self.objectName(),
			label = 'mb_aam',
			closeCommand = partial(_close_command, self)
		)