PySide in Maya: "Internal C++ object already deleted" for returned PySide objects

Hello everybody.
I’m currently working in Maya 2014 and I’ve got a following issue.

Suppose you have a QTableWidget. QTableWidget has a method horizontalHeader() which returns a QHeaderView instance.
You can ask this QHeaderView with sortIndicatorSection() to retrieve it’s current sorted column.
So, the whole statement should looks like this:
[PRE]sortedSection = self.myTableWidget.horizontalHeader().sortIndicatorSection()[/PRE]
Now suppose you want to remember this QHeaderView in some instance variable and access this QHeaderView without querying QTableWidget in your other methods:
[PRE]self.headerView = self.myTableWidget.horizontalHeader()[/PRE]
and later:
[PRE]sortedSection = self.headerView.sortIndicatorSection()[/PRE]
This is the problem if you using PySide.
Please run a code below in Maya and press a button, then look a Script Editor.

from PySide import QtGui, QtCore
import shiboken
import maya.OpenMayaUI as omui


mainWin = None


class TestUI(QtGui.QMainWindow):

    def __init__(self, parent):
        super(TestUI, self).__init__(parent=parent)

        self.centralWidget = QtGui.QWidget()
        self.setCentralWidget(self.centralWidget)

        self.layout = QtGui.QVBoxLayout()
        self.centralWidget.setLayout(self.layout)

        self.lineEdit = QtGui.QLineEdit('defaultText')
        self.layout.addWidget(self.lineEdit)
        self.font = self.lineEdit.font()

        self.tableWidget = QtGui.QTableWidget()
        self.layout.addWidget(self.tableWidget)
        self.horHeader = self.tableWidget.horizontalHeader()

        self.button = QtGui.QPushButton('Click Me')
        self.layout.addWidget(self.button)
        self.button.connect(self.button, QtCore.SIGNAL('clicked()'), self.onButtonClicked)

        print 'try to access variables right after creation:'
        self.onButtonClicked()
        print

    def onButtonClicked(self):
        print 'QTableWidget -> QHeaderView: runtime: sortSection =', self.tableWidget.horizontalHeader().sortIndicatorSection()
        try:
            print 'QTableWidget -> QHeaderView: cached: sortSection =', self.horHeader.sortIndicatorSection()
        except Exception as e:
            print 'C++ object is dead. Exception:', str(e)

        print 'QLineEdit -> QFont: runtime: font is italic =', self.lineEdit.font().italic()
        try:
            print 'QLineEdit -> QFont: cached: font is italic =', self.font.italic()
        except Exception as e:
            print 'C++ object is dead. Exception:', str(e)


def run():
    global mainWin
    if not mainWin:
        ptr = omui.MQtUtil.mainWindow()
        if ptr:
            mayaQMainWindow = shiboken.wrapInstance(long(ptr), QtGui.QMainWindow)
        else:
            raise Exception('Cannot find Maya main window.')
        mainWin = TestUI(parent=mayaQMainWindow)
        # mainWin = TestUI(parent=None)

    mainWin.show()
    mainWin.raise_()

It will raise an exception when you will try to access some methods of this “cached” object.
What I’ve found out:
[ul]
[li]Variables are alive just after creation.[/li][li]If you change parent of TestUI to None, instead of Maya main window, everything will be fine.[/li][li]If you run this code in standalone PySide application, again it will be ok. I think cause there will be no Maya main window as parent.[/li][li]Not all PySide objects will be destroyed. As you can see from my code, QFont object is still alive.[/li][li]If you will create an instance of that particular object, instead of querying it (of course it is not the same by logic, just for testing), C++ object will not be destroyed. In my example it will be self.horHeaderCreated = QtGui.QHeaderView(QtCore.Qt.Horizontal)[/li][li]If you using PyQt, everything will be fine. You’ll need to change imports, shiboken to sip and shiboken.wrapInstance to sip.wrapinstance.[/li][li]Currently i’m having a problem with these methods and objects:[/li][/ul]
[PRE]
QScrollArea.horizontalScrollBar() -> QScrollBar
QScrollArea.verticalScrollBar() -> QScrollBar
QTreeView.header() -> QHeaderView
QTableView.horizontalHeader() -> QHeaderView
Q…View/Widget.selectionModel() -> QItemSelectionModel
[/PRE]
May be i’m doing something wrong?
Is there any workarounds except of: not making Maya window a parent of your ui, always get these objects from Qt controls and do not cache them?
Do you have any additional info about this issue (may be its a known problem or some discussion about it)?

I haven’t seen this, but perhaps you could use propertiesas you would in C# to hide the ugliness of finding the widgets every time? You could cache the widget instance, and if the data looks valid, return that, and if not, go searching for it again.

Hi,

I just stumbled into the same issue while preparing our tools to switch from Maya2012 (PyQt) to Maya2015 (PySide).
In my case, the deleted c++ pointer seem to happen to all widgets that have been created in .ui files (QT Designer) and dynamically loaded via a PySide-compatible loadUI function.

Did anyone find a workaround besides re-creating all child-widgets in code again?

Yeah, I’ve had it occasionally happen, especially when we were converting some tools to use PySide rather than Qt4, and it seems like the culprit is Maya’s garbage collector being a little over-zealous (seems to do that a lot with pyside) and I’ve occasionally had luck turning it around by breaking things up to more lines rather than running it all on one. But it’s not like you’re running a lot on one line in this case. There have been a few cases where I’ve had to give up on having things saved like that just because Maya keeps deleting them.

What happens if you create a single global python list that you can stuff references like this into so they don’t get deleted?

[QUOTE=Fridi;28289]Hi,

I just stumbled into the same issue while preparing our tools to switch from Maya2012 (PyQt) to Maya2015 (PySide).
In my case, the deleted c++ pointer seem to happen to all widgets that have been created in .ui files (QT Designer) and dynamically loaded via a PySide-compatible loadUI function.

Did anyone find a workaround besides re-creating all child-widgets in code again?[/QUOTE]

Apologies for the thread hijack, but i have some things working with maya / pyside and .ui files.

I had similar garbage collection disasters with pyside and did a lot of banging my head against the walls. none of the ui loader scripts floating around the net were doing anything stable. here’s what is working for me.

  • don’t try and create a qDialog in qtDesigner. create your .ui file as a qWidget
  • create a qdialog in code, load the widget using QtUiTools.QUiLoader and parent it into your layout

for example:


class ShelfWindow(QtGui.QDialog):
    def __init__(self, parent=QtGui.QApplication.activeWindow()):
        super(ShelfWindow, self).__init__(parent)


        # Load UI file
        loader = QtUiTools.QUiLoader()
        uifile = QtCore.QFile(os.path.join(os.path.dirname(os.path.abspath(__file__)), "uberShelf.ui"))
        uifile.open(QtCore.QFile.ReadOnly)
        self.ui = loader.load(uifile)
        self.centralLayout = QtGui.QVBoxLayout(self)
        self.centralLayout.setContentsMargins(0, 0, 0, 0)
        self.centralLayout.addWidget(self.ui)


all of the widget’s objects are available through self.ui.widgetname

this is working in maya, nuke and houdini.

hope this helps someone.

cheers,
chrisg

This should actually make the whole ui loading process easier since you don’t have to jump through hoops trying to create instances of the window/dialog class which is much more troublesome in PySide than PyQT.

Yeah, aside from a little boilerplate, there’s no real downside to doing it like this.

It’s also easy to change an existing .ui file from dialog to widget through a text editor.

cheers,
chrisg

For a more generalized solution that will protect against garbage-collection of widgets. You can modify this to work with whatever DCC has a host QApplication/QCoreApplication instance:

http://pastebin.com/UxPvxJcN

I used to have separate methods for working with 3ds max/Maya/MoBu/Mari but now I just subclass this base MainWindow/MainDialog class if necessary to add additional functionality. This is part of a set of common libraries that helps make it easier to standardize the widgets and how they are created.

Also, instead of dynamically loading the .ui file, I’d advise using pyside-uic to actually convert it to a .py file first if you absolutely must use .ui files for some reason. This way IDEs (PyCharm) can inspect the actual file and check for invalid/missing references.

I have an example of a script that you can use for that. My actual process is that on build this procedure is run on all .ui files to convert them automatically to .py files if they exist in the working folder structure:

http://pastebin.com/jW7HbPjU

Required cliUtils module: http://pastebin.com/5bLEp079

usage:

python fileUtils.py -f <filename.ui>