3

I have a UI that I am wanting to use threading with inside of Maya. The reason for doing this is so I can run Maya.cmds without hanging/freezing the UI while updating the UI with progress bars, etc.

I have read a few examples from StackOverflow but my code is crashing every second time I run it. Examples I have followed are here and here

import maya.cmds as cmds
from PySide2 import QtWidgets, QtCore, QtGui, QtUiTools
import mainWindow #Main window just grabs the Maya main window and returns the object to use as parent.

class Tool(QtWidgets.QMainWindow):
    def __init__(self, parent=mainWindow.getMayaMainWindow()):
        super(Tool, self).__init__(parent)

        UI = "pathToUI/UI.ui"
        loader = QtUiTools.QUiLoader()
        ui_file = QtCore.QFile(UI)
        ui_file.open(QtCore.QFile.ReadOnly)
        self.ui = loader.load(ui_file, self)

        #Scans all window objects and if one is open with the same name as this tool then close it so we don't have two open.
        mainWindow.closeUI("Tool")      

        ###HERE'S WHERE THE THREADING STARTS###
        #Create a thread
        thread = QtCore.QThread()
        #Create worker object
        self.worker = Worker()
        #Move worker object into thread (This creates an automatic queue if multiples of the same worker are called)
        self.worker.moveToThread(thread)

        #Connect buttons in the UI to trigger a method inside the worker which should run in a thread
        self.ui.first_btn.clicked.connect(self.worker.do_something)
        self.ui.second_btn.clicked.connect(self.worker.do_something_else)
        self.ui.third_btn.clicked.connect(self.worker.and_so_fourth)

        #Start the thread
        thread.start()

        #Show UI
        self.ui.show()

class Worker(QtCore.QObject):
    def __init__(self):
        super(Worker, self).__init__() #This will immediately crash Maya on tool launch
        #super(Worker).__init__() #This works to open the window but still gets an error '# TypeError: super() takes at least 1 argument (0 given)'

    def do_something(self):
        #Start long code here and update progress bar as needed in a still active UI.
        myTool.ui.progressBar.setValue(0)
        print "doing something!"
        myTool.ui.progressBar.setValue(100)

    def do_something_else(self):
        #Start long code here and update progress bar as needed in a still active UI.
        myTool.ui.progressBar.setValue(0)
        print "doing something else!"
        myTool.ui.progressBar.setValue(100)

    def and_so_fourth(self):
        #Start long code here and update progress bar as needed in a still active UI.
        myTool.ui.progressBar.setValue(0)
        print "and so fourth, all in the new thread in a queue of which method was called first!"
        myTool.ui.progressBar.setValue(100)

#A Button inside Maya will import this code and run the 'launch' function to setup the tool
def launch():
    global myTool
    myTool = Tool()

I'm expecting the UI to stay active (not locked up) and the threads to be running Maya cmds without crashing Maya entirely while updating the UIs progress bars.

Any insight on this would be amazing!

1 Answers1

3

From what I see it has the following errors:

  • thread is a local variable that is deleted when the constructor is finished executing causing what is executed to be done in the main thread which is not desired, the solution is to extend the life cycle and for this there are several solutions: 1) make class attribute, 2) pass a parent to the cycle of life they are managed by the parent. In this case use the second solution.

  • You should not modify the GUI from another thread, in your case you have modified the progressBar from another thread, in Qt you must use signals.

  • You must use the @Slot decorator in the methods that are executed in another thread.

  • You indicate that you want to modify myTool but you have not declared it, soglobal myTool will not work by making myTool a local variable to be deleted. The solution is to declare myTool:myTool = None Considering the above, the solution is:

import maya.cmds as cmds
from PySide2 import QtWidgets, QtCore, QtGui, QtUiTools
import mainWindow  # Main window just grabs the Maya main window and returns the object to use as parent.


class Tool(QtWidgets.QMainWindow):
    def __init__(self, parent=mainWindow.getMayaMainWindow()):
        super(Tool, self).__init__(parent)

        UI = "pathToUI/UI.ui"
        loader = QtUiTools.QUiLoader()
        ui_file = QtCore.QFile(UI)
        ui_file.open(QtCore.QFile.ReadOnly)
        self.ui = loader.load(ui_file, self)

        # Scans all window objects and if one is open with the same name as this tool then close it so we don't have two open.
        mainWindow.closeUI("Tool")

        # Create a thread
        thread = QtCore.QThread(self)
        # Create worker object
        self.worker = Worker()
        # Move worker object into thread (This creates an automatic queue if multiples of the same worker are called)
        self.worker.moveToThread(thread)

        # Connect buttons in the UI to trigger a method inside the worker which should run in a thread
        self.ui.first_btn.clicked.connect(self.worker.do_something)
        self.ui.second_btn.clicked.connect(self.worker.do_something_else)
        self.ui.third_btn.clicked.connect(self.worker.and_so_fourth)

        self.worker.valueChanged.connect(self.ui.progressBar.setValue)

        # Start the thread
        thread.start()

        # Show UI
        self.ui.show()


class Worker(QtCore.QObject):
    valueChanged = QtCore.Signal(int)

    @QtCore.Slot()
    def do_something(self):
        # Start long code here and update progress bar as needed in a still active UI.
        self.valueChanged.emit(0)
        print "doing something!"
        self.valueChanged.emit(100)

    @QtCore.Slot()
    def do_something_else(self):
        # Start long code here and update progress bar as needed in a still active UI.
        self.valueChanged.emit(0)
        print "doing something else!"
        self.valueChanged.emit(100)

    @QtCore.Slot()
    def and_so_fourth(self):
        # Start long code here and update progress bar as needed in a still active UI.
        self.valueChanged.emit(0)
        print "and so fourth, all in the new thread in a queue of which method was called first!"
        self.valueChanged.emit(100)

myTool = None

def launch():
    global myTool
    myTool = Tool()
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • Wow this is perfect! I'd like to ask some follow up questions if you don't mind: Why did you pass self to this? thread = QtCore.QThread(self) Can I use these to send other variables like lists, dicts or strings? @QtCore.Slot() QtCore.Signal(int) Why did you add this? myTool = None – Nightingale Oct 07 '19 at 02:47
  • In gerneral it is not save to access or modify any Maya node from another but the main thread unless it is stated in the docs. Thread save maya nodes are the exception. If you need to modify maya from another thread, you should use an idle callback together with a threadsave qeueing system and do any modifications from the main thread. – haggi krey Oct 07 '19 at 07:39
  • @haggikrey I have read about that as well. Because of this, I was playing with the idea of having the UI run in another thread and all Maya cmds on the main thread. I could also be over complicating things. I really just want Maya cmds working and the UI to be fully active. – Nightingale Oct 07 '19 at 08:09