Embed Maya Native UI Objects in PySide2 (Maya 2017+)

Share on LinkedIn0Share on Facebook0Share on Google+0Tweet about this on Twitter0Digg thisShare on Reddit0Share on VKPrint this page

With the new versions of PySide2 and Qt5 in Maya 2017+ the way of embedding a Maya native user interface into a PySide2 widget is not the same anymore.

I knew about PyQt’s version on JustinFX’s Blog post from 2011 on how to mix Maya UI objects and PyQt4, or Nathan Horne’s blog, or examples like this, but I couldn’t find an updated version of it for PySide, which is, for me, the way to go from now on, since PySide comes with Maya by default; it is part of the Qt official wiki and is not as big as PyQt, removing some deprecated features from it’s first API. It’s maybe not as stable as PyQt but for now It has been great for me.

Previously, the way to embed Maya’s UI in PySide was to use shiboken (or sip for PyQt) to unwrap the qt layout instance into a pointer, and then get the object full name in Maya using Maya’s API:

obj = mui.MQtUtil.fullName(long(shiboken2.unwrapInstance(qtObj)))

But this doesn’t work anymore.

The differences are:

  • We use shiboken2, even when the official docs say shiboken (I sent a report about it some days ago. I just checked again…and it is fixed! Good! 🙂 ).
  • There’s no unwrapInstance. You will have to wrap the cpp pointer using shiboken2’s getCppPointer function.

Here’s how you do it:

from PySide2 import QtWidgets
import maya.cmds as cmds
import maya.OpenMayaUI as mui
import shiboken2

# We create a simple window with a QWidget
window = QtWidgets.QWidget()
window.resize(500,500)
# We have our Qt Layout where we want to insert, say, a Maya viewport
qtLayout = QtWidgets.QVBoxLayout(window)

# First use shiboken to unwrap the layout into a pointer. It returns two values, we get the first one.
layout = long(shiboken2.getCppPointer(qtLayout)[0])

# Use Maya API to get the maya name from the pointer
layoutName = mui.MQtUtil.fullName(layout)

# We set that layout as parent, to carry on creating Maya UI using Maya.cmds and create the paneLayout under it.
cmds.setParent(layoutName)
paneLayoutName = cmds.paneLayout()
    
# Create the model panel. I use # to generate a new panel with no conflicting name
modelPanelName = cmds.modelPanel("embeddedModelPanel#", cam='persp')
    
# Find a pointer to the paneLayout that we just created using Maya API
ptr = mui.MQtUtil.findControl(paneLayoutName)
    
# Wrap the pointer into a python QObject. Note that with PyQt QObject is needed. In Shiboken we use QWidget.
paneLayoutQt = shiboken2.wrapInstance(long(ptr), QtWidgets.QWidget)

# Now that we have a QtWidget, we add it to our Qt layout
qtLayout.addWidget(paneLayoutQt)

window.show()

The variable “layoutName” will probably store ‘|’  in this situation, which means “the root”. If you gave the Qt object a name using setObjectName(“name”), you would have “name|” instead.

If you insert this layout somewhere else, deeper in the hierarchy of your Qt layouts, it will return something like ‘|||’, which will return an error when you try to set that as parent for the new Maya’s UI on setParent(layoutName).

The way I solved this was to always use ‘|’ as the layoutName (in case you don’t have any name for the Qt object), so you don’t need to add the first lines anymore until setParent. I tested it, and it works wherever you embed that code.

It becomes just this:

from PySide2 import QtWidgets
import maya.cmds as cmds
import maya.OpenMayaUI as mui
import shiboken2

# We create a simple window with a QWidget
window = QtWidgets.QWidget()
window.resize(500,500)
# We have our Qt Layout where we want to insert, say, a Maya viewport
qtLayout = QtWidgets.QVBoxLayout(window)

# We set the root as parent to carry on creating Maya UI using Maya.cmds and create the paneLayout under it.
cmds.setParent('|')
paneLayoutName = cmds.paneLayout()
    
# Create the model panel. I use # to generate a new panel with no conflicting name
modelPanelName = cmds.modelPanel("embeddedModelPanel#", cam='persp')
    
# Find a pointer to the paneLayout that we just created using Maya API
ptr = mui.MQtUtil.findControl(paneLayoutName)
    
# Wrap the pointer into a python QObject. Note that with PyQt QObject is needed. In Shiboken we use QWidget.
paneLayoutQt = shiboken2.wrapInstance(long(ptr), QtWidgets.QWidget)

# Now that we have a QtWidget, we add it to our Qt layout
qtLayout.addWidget(paneLayoutQt)

window.show()

EDIT:

Again, using ‘|’ as parent is giving me errors in some cases as well and after some research, I found out that it works better if you give the layout a arbitrary qt object name:

qtLayout.setObjectName('viewportLayout')

And then set that name as parent, instead of using a Maya UI hierarchy name with | symbols.

cmds.setParent('viewportLayout')

I tested it even with a couple of windows and the objectNames don’t clash and work in this case too. This part makes no sense and I’m still wondering why the same object names work for both setParent instances.

So this should be the final code:

from PySide2 import QtWidgets
import maya.cmds as cmds
import maya.OpenMayaUI as mui
import shiboken2

# We create a simple window with a QWidget
window = QtWidgets.QWidget()
window.resize(500,500)
# We have our Qt Layout where we want to insert, say, a Maya viewport
qtLayout = QtWidgets.QVBoxLayout(window)

# We set a qt object name for this layout.
qtLayout.setObjectName('viewportLayout') 

# We set the given layout as parent to carry on creating Maya UI using Maya.cmds and create the paneLayout under it.
cmds.setParent('viewportLayout')
paneLayoutName = cmds.paneLayout()
    
# Create the model panel. I use # to generate a new panel with no conflicting name
modelPanelName = cmds.modelPanel("embeddedModelPanel#", cam='persp')
    
# Find a pointer to the paneLayout that we just created using Maya API
ptr = mui.MQtUtil.findControl(paneLayoutName)
    
# Wrap the pointer into a python QObject. Note that with PyQt QObject is needed. In Shiboken we use QWidget.
paneLayoutQt = shiboken2.wrapInstance(long(ptr), QtWidgets.QWidget)

# Now that we have a QtWidget, we add it to our Qt layout
qtLayout.addWidget(paneLayoutQt)

window.show()

 

The result is this:

 

Note: I’ve experienced problems with automatic numbering using names like “embeddedModelPanel#” with that # symbol as suffix in the past. Sometimes Maya is unnable to return a unique name, but for this case it works everytime. If you have any problem, an easy solution is to import time module and use time.time() to get a timestamp (a number) and append it to the name of the panel instead of #. It will be extremely unlikely the name being the same as another panel’s name.

Share on LinkedIn0Share on Facebook0Share on Google+0Tweet about this on Twitter0Digg thisShare on Reddit0Share on VKPrint this page

14 comments

  • Any luck getting this approach to run in a class?

  • Any chance to add QWidget to maya layout. I’m trying to add maya frame layout to Qt UI and QWidgets to that frame layout.

    • Hi! I don’t understand your question. You first say “add QWidget -> maya layout” and then you say ” add maya layout to Qt” again. You mean, adding a QWidget to a maya item?

      • Yes, I mean QWidget to maya item!
        Thank you

        • That’s a good point. I never really tried it. That’s good for another article! In the meantime I suggest you visit StackOverflow or a similar forum where you can get an answer faster! I’m quite busy these days. Cheers

        • Vaishak Purushothaman

          you can set objectName on Qt Widget and parent it to a maya layout. Try this minimal example,

          import maya.cmds as mc
          import maya.OpenMayaUI as omui
          from PySide2.QtWidgets import QListWidget

          qt_widget = QListWidget()
          qt_widget.setObjectName(“QT_ListWidget”)
          qt_widget.addItems(“Qt in maya is interesting”.split(” “))

          win = mc.window()
          col_lay = mc.columnLayout(adj=1)
          mc.control(“QT_ListWidget”, e=1, p=col_lay)
          mc.showWindow(win)

          • Vaishak Purushothaman

            please replace the double quotes if you are copying the above code, or you might get a syntax error because the double quote is formatted when I posted the reply,
            Thanks

          • That’s really nice, Vaishak. Thanks for the tip! I’ll try it as soon as possible.

  • Hey there! this might look silly, but i’m just starting at this coding thing! 🙂 how can i make this window to be the Graph Editor?

    • Hi ! It’s a pretty good question, of course. It’s not silly ^_^.

      I had never done this, but opening the GraphEditor I activated “Show All Commands” in the script editor to see what’s going on in the backstage… (Be sure to turn Echo All Commands off again after this, or will make Maya terribly slow).

      After that, I found out a couple of commands that are run when creating the GraphEditor.

      outlinerEditor and animCurveEditor

      So you should create both reading the documentation for those commands in the Maya Help.

      You need to change this line:
      modelPanelName = modelPanel("embeddedModelPanel#", cam='persp')

      for this one:
      modelPanelName = cmds.animCurveEditor("embeddedGraphEditor#", the rest of parameters here... if any)

      But as you can see, this will just create the lower part of the editor. (The timeline). To get both parts you should create a Vertical Layout in Qt that contains the animCurveEditor this way and then repeat the whole process of adding another PaneLayout for the outlinerEditor.

Leave a Reply

Your email address will not be published. Required fields are marked *