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

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. Learn how to do it here!
Cover Image

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.

This post was originally included in the Virtual Method Studio blog on 17th March 2017, but I moved it here due to its popularity.

If you want to read the comments on the original post visit it here.