Results 1 to 4 of 4

Thread: workspaceControl, migrating dockControl so we can dock into the ChannelBox tabs

  1. #1
    while loop Mark-J's Avatar
    Join Date
    Apr 2009

    Default workspaceControl, migrating dockControl so we can dock into the ChannelBox tabs

    Hi all,

    I'm trying to switch some of our base toolset so that the main UI (cmds built) now docks correctly in the same tab as the channelBox rather than docking to the right side of the main Maya UI. The ChannelBox is now a workspaceControl so I havce to call the UI through that as below, it's a bit of a pain to switch the ui over but I hate the fact that the right dockControl space is now the entire length of the Maya UI, and outside of everything else.

    So I did the following to make the UI tab into the workspace of the channelBox:

    element=mel.eval('getUIComponentDockControl("Channel Box / Layer Editor", false);')  
    windowcall='import Red9.core.Red9_AnimationUtils as r9Anim;animUI=r9Anim.AnimationUI();animUI._showUI()'
    cmds.workspaceControl(animUI.workspaceCnt, label="Red9_Animation", 
                                          tabToControl=(element, -1), 
    cmds.workspaceControl(animUI.workspaceCnt, e=True,vis=True)  # ensure we set visible
    cmds.workspaceControl(animUI.workspaceCnt, e=True, mw=355)  # set minimumWidth
    cmds.workspaceControl(animUI.workspaceCnt, e=True,  r=True)  # raise

    Now that all works but I'll be damned if I can find a way to control and lock the width of this UI. If I go to the modelling tab and then my tab the ui is the correct width, presumably because I set the minimumWidth flag.

    However, if I go to the attributeEditor tab, then mine the ui remains the width of the attributeEditor. There's width query flags in the workspaceControl, but no edit.

    So the question is, how the hell do you set a base width that is respected for these?



  2. #2
    Technical Artist
    Join Date
    Jul 2008
    Austin, TX


    have you tried the "MayaQWidgetDockableMixin"?
    you can hook into the visible state of your tab and change the width when it becomes visible.

  3. #3


    Using some of Maya's python examples and a gist that liorbenhorin was kind enough to share, I've made this little example that you can copy and paste into Maya. Hopefully it can shed some light on how to do this properly.

    The docking behavior is down below in the run2017 method.

    # Copyright 2015 Autodesk, Inc. All rights reserved.
    # Use of this software is subject to the terms of the Autodesk
    # license agreement provided at the time of installation or download,
    # or which otherwise accompanies this software in either electronic
    # or hard copy form.
    Attribute Editor style widget
      * Uses mayaMixin to handle details of using PySide in Maya
      * Assumption that the node name does not change.  For handling node names, look into using MObjectHandle
      * File->New and File->Load not handled
      * Deleting node not handled
    from maya import cmds
    from maya import mel
    from maya import OpenMaya as om
    from maya import OpenMayaUI as omui 
      from PySide2.QtCore import * 
      from PySide2.QtGui import * 
      from PySide2.QtWidgets import *
      from PySide2.QtUiTools import *
      from shiboken2 import wrapInstance 
    except ImportError:
      from PySide.QtCore import * 
      from PySide.QtGui import * 
      from PySide.QtUiTools import *
      from shiboken import wrapInstance 
    from import MayaQWidgetBaseMixin, MayaQWidgetDockableMixin
    import functools
    class MCallbackIdWrapper(object):
        '''Wrapper class to handle cleaning up of MCallbackIds from registered MMessage
        def __init__(self, callbackId):
            super(MCallbackIdWrapper, self).__init__()
            self.callbackId = callbackId
        def __del__(self):
        def __repr__(self):
            return 'MCallbackIdWrapper(%r)'%self.callbackId
    def getDependNode(nodeName):
        """Get an MObject (depend node) for the associated node name
                String representing the node
        :Return: depend node (MObject)
        dependNode = om.MObject()
        selList = om.MSelectionList()
        if selList.length() > 0: 
            selList.getDependNode(0, dependNode)
        return dependNode
    class Example_connectAttr(MayaQWidgetDockableMixin, QScrollArea):
        def __init__(self, node=None, *args, **kwargs):
            super(Example_connectAttr, self).__init__(*args, **kwargs)
            # Member Variables
            self.nodeName = node               # Node name for the UI
            self.attrUI = None                 # Container widget for the attr UI widgets
            self.attrWidgets = {}              # Dict key=attrName, value=widget
            self.nodeCallbacks = []            # Node callbacks for handling attr value changes
            self._deferredUpdateRequest = {}   # Dict of widgets to update
            # Connect UI to the specified node
        def attachToNode(self, nodeName):
            '''Connect UI to the specified node
            self.nodeName = nodeName
            self.attrs = None
            self.nodeCallbacks = []
            # Get a sorted list of the attrs
            attrs = cmds.listAttr(self.nodeName)
            attrs.sort() # in-place sort the attrs
            # Create container for attr widgets
            self.setWindowTitle('ConnectAttrs: %s'%self.nodeName)
            self.attrUI = QWidget(parent=self)
            layout = QFormLayout()
            # Loop over the attrs and construct widgets
            acceptedAttrTypes = set(['doubleLinear', 'string', 'double', 'float', 'long', 'short', 'bool', 'time', 'doubleAngle', 'byte', 'enum'])
            for attr in attrs:
                # Get the attr value (and skip if invalid)
                    attrType = cmds.getAttr('%s.%s'%(self.nodeName, attr), type=True)
                    if attrType not in acceptedAttrTypes:
                        continue # skip attr
                    v = cmds.getAttr('%s.%s'%(self.nodeName, attr))
                except Exception, e:
                    continue  # skip attr
                # Create the widget and bind the function
                attrValueWidget = QLineEdit(parent=self.attrUI)
                # Use functools.partial() to dynamically constructing a function
                # with additional parameters.  Perfect for menus too.
                onSetAttrFunc =  functools.partial(self.onSetAttr, widget=attrValueWidget, attrName=attr)
                attrValueWidget.editingFinished.connect( onSetAttrFunc )
                # Add to layout
                layout.addRow(attr, attrValueWidget)
                # Track the widget associated with a particular attr
                self.attrWidgets[attr] = attrValueWidget
            # Attach the QFormLayout to the root attrUI widget
            # Assign the widget to this QScrollArea
            # Do a 'connectControl' style operation with MMessage callbacks
            if len(self.attrWidgets) > 0:
                # Note: addNodeDirtyPlugCallback better than addAttributeChangedCallback
                # for UI since the 'dirty' check will always refresh the value of the attr
                nodeObj = getDependNode(nodeName)
                cb = om.MNodeMessage.addNodeDirtyPlugCallback(nodeObj, self.onDirtyPlug, None)
                self.nodeCallbacks.append( MCallbackIdWrapper(cb) )
        def onSetAttr(self, widget, attrName, *args, **kwargs):
            '''Handle setting the attribute when the UI widget edits the value for it.
            If it fails to set the value, then restore the original value to the UI widget
            print "onSetAttr", attrName, widget, args, kwargs
                attrType = cmds.getAttr('%s.%s'%(self.nodeName, attrName), type=True)
                if attrType == 'string':
                    cmds.setAttr('%s.%s'%(self.nodeName, attrName), widget.text(), type=attrType)
                    cmds.setAttr('%s.%s'%(self.nodeName, attrName), eval(widget.text()))
            except Exception, e:
                print e
                curVal = cmds.getAttr('%s.%s'%(self.nodeName, attrName))
                widget.setText( str(curVal) )
        def onDirtyPlug(self, node, plug, *args, **kwargs):
            '''Add to the self._deferredUpdateRequest member variable that is then 
            deferred processed by self._processDeferredUpdateRequest(). 
            # get long name of the attr, to use as the dict key
            attrName = plug.partialName(False, False, False, False, False, True)
            # get widget associated with the attr
            widget = self.attrWidgets.get(attrName, None)
            if widget != None:
                # get node.attr string
                nodeAttrName = plug.partialName(True, False, False, False, False, True) 
                # Add to the dict of widgets to defer update
                self._deferredUpdateRequest[widget] = nodeAttrName
                # Trigger an evalDeferred action if not already done
                if len(self._deferredUpdateRequest) == 1:
                    cmds.evalDeferred(self._processDeferredUpdateRequest, low=True)
        def _processDeferredUpdateRequest(self):
            '''Retrieve the attr value and set the widget value
            for widget,nodeAttrName in self._deferredUpdateRequest.items():
                v = cmds.getAttr(nodeAttrName)
                print "_processDeferredUpdateRequest ", widget, nodeAttrName, v
        def deleteControl(self, control):
            if cmds.workspaceControl(control, q=True, exists=True):
                cmds.workspaceControl(control, e=True, close=True)
                cmds.deleteUI(control, control=True)
        def run2017(self):
            # liorbenhorin's snippet
            # The deleteInstances() dose not remove the workspace control, and we need to remove it manually
            workspaceControlName = self.objectName() + 'WorkspaceControl'
            # this class is inheriting, which will eventually call maya.cmds.workspaceControl.
            # I'm calling it again, since the MayaQWidgetDockableMixin dose not have the option to use the "tabToControl" flag,
            # which was the only way i found i can dock my window next to the channel controls, attributes editor and modelling toolkit.
  , area='right', floating=False)
            cmds.workspaceControl(workspaceControlName, e=True, ttc=["AttributeEditor", -1], wp="preferred", mw=420)
            # size can be adjusted, of course
    def main():
        obj = cmds.polyCube()[0]
        ui = Example_connectAttr(node=obj)
        ui.run2017(), floating=True)
        return ui
    if __name__ == '__main__':

  4. #4


    I load my UI the same way but like in your example the resizing layout to the window is not working, I can not found a way to streach my ui to the window when I resize the window.

    Using loadUI works fine but not with this method, I am missing something?

    Do you know how we can do this?



Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts