top of page
interface QT

Lesson 2 : UI in Maya

In this course, you will learn how to create your own interface usable in Maya and other software.

This will enable you to save time and create your own tools, making the script work without the need to rely on commands for execution.

Table of Contents
 

1. Why create your own user interface
2. Install and use Qt 
3. Create elements in Qt
4. Retrieve/execute information with the interface
5. Practical case in maya
6. Sources

1. Why create your own user interface

By developing your own UI, you can design a user experience that precisely matches your application's needs. You can customize the appearance, behavior, and features to specifically meet your users' expectations.

​

​

​

Using tools like Qt and PyQt, you have control over every visual

and functional aspect of your application. This enables you to

create an intuitive and user-friendly interface, ensuring an optimal

user experience.

​

​

Creating a customized UI allows you to ensure it seamlessly integrates

with the specific functionalities of your application. This promotes a

consistent and harmonious user experience.

​

​

In essence, creating your own user interface allows you to provide a unique experience tailored to your application's specific needs, offering total control over the user experience and strengthening your product's identity.

QT for maya

In order to create our own normal user interface, we used QT. QT is a popular cross-platform library for developing Graphical User Interfaces (GUIs) in Python. With Qt, you can create applications with an attractive and functional user interface.

​

Qt is a C++ framework developed by the Qt Company. It provides a variety of tools and components for creating graphical interfaces, mobile applications, embedded applications, and more. PyQt and PySide are two Python wrappers for the Qt library, enabling the use of Qt with Python.

To program our interface, we used an IDE, and the one we're going to use is Visual Studio Code.

We've already used it, and it's absolutely perfect for creating our application. It has the Maya library, and we'll be able to install QT in Visual Studio Code. With all of this, it offers simplicity in use."

2. Install and use Qt 

visual studio code

What you need to know is that Maya offers commands to create its own interfaces using the cmds library within Maya, but it doesn't offer as much flexibility and as many tools as Qt does. However, what's important to note is that Maya's entire interface has been created with Qt, and within Maya, we have direct access to the Pyside2 binding (associated with Qt5), available in Maya without the need for installation. Therefore, to create our interface in Maya, we'll opt for Pyside2. You can use other newer versions of PySide (like PySide6) if you wish, but then you'll need to install this new version into Maya.

cmds maya

To use Qt in Visual Studio Code, you'll need to install the PySide2 library, which is the Python binding for Qt5.

​

To do this, you need to go to the Visual Studio Code terminal and write the following bash command:

"pip install PySide2" using pip. To understand the difference between QT, PySide, and PyQt, I recommend visiting the website KooR.fr."

pyside Qt

From there, you can start creating your first Python scripts that will build an interface using PySide.

​

The first thing to do is to call the library and create your first window by creating an application.

​

For this to work, you need to create an application that sets up an entire environment allowing the interface to function within its own loop without interfering with other software. Within this application, we can create our window and inside it, we can do absolutely anything we want.

import sys

import PySide2.QtWidgets as QT


 

def main_intereface():

    my_window = QT.QWidget()

    my_window.resize(500, 440)

    my_window.setWindowTitle('my first window Qt')

 

    return my_window 
 

if __name__ == '__main__':

    app = QT.QApplication(sys.argv)

    my_window = main_intereface()

    my_window.show()

    sys.exit(app.exec_())

  • QApplication: This is the main class for a Qt application. It manages the main event loop and initializes the application.

​

  • QWidget: It's a fundamental graphical interface element that provides a window or framework for applications.

​

  • show(): This method displays the window on the screen. It needs to be executed outside of the main window method.

​

  • app.exec_(): This is an event loop that waits for user interactions.

​

  • sys.argv: It's a list of command-line arguments passed to the Python script. This allows Qt to process any specified arguments when launching the application.

3. Create elements in Qt

Qt offers various user interface elements such as buttons, text fields, menus, etc. This allows you to have interfaces as you desire, you can literally recreate Maya.

​

Here are some key elements when creating an interface:

  • QPushButton() : creates a button in your window.

  • QLabel() : creates non-editable text, it's a static text.

  • QLineEdit() : creates an input box where the user can write.

  • QCheckBox() : allows you to create checkboxes.

  • QT.QSpinBox() : enables you to create a numerical counter.

​

pyside Qt

import sys

import PySide2.QtWidgets as QT


 

def main_intereface():

    my_window = QT.QWidget()

    my_window.resize(500, 440)

    my_window.setWindowTitle('my first window Qt')

 

    #----create element-----

    QT.QPushButton()

    QT.QLabel()

    QT.QCheckBox()

    QT.QSpinBox()

​

    return my_window 
 

if __name__ == '__main__':

    app = QT.QApplication(sys.argv)

    my_window = main_intereface()

    my_window.show()

    sys.exit(app.exec_())

If we run the program with its different elements, we might notice that they are not displayed in the window. Indeed, although we have created our elements and they do exist, they aren't connected to anything; they are not inside a box.

​

What you need to know is that Qt works with a system of box stacking. This means that one box will contain other boxes, potentially nested within other boxes. This allows us to create our hierarchy.

​

To achieve this, we'll need to pass as an argument the variable of the QWidget, which is our main window. For each element created, as the first argument, we'll put the variable "my_window". This way, each element will be a child of and belong to the window "my_window".

​

pyside Qt

import sys

import PySide2.QtWidgets as QT


 

def main_intereface():

    my_window = QT.QWidget()

    my_window.resize(500, 440)

    my_window.setWindowTitle('my first window Qt')

 

    #----create element-----

    QT.QPushButton(my_window)

    QT.QLabel(my_window)

    QT.QCheckBox(my_window)

    QT.QSpinBox(my_window)

​

    return my_window 
 

if __name__ == '__main__':

    app = QT.QApplication(sys.argv)

    my_window = main_intereface()

    my_window.show()

    sys.exit(app.exec_())

Now you have the possibility to see these different elements, but they are overlapping and have no name or indication. For example, we would like there to be text on the button, and the label to have text as currently, it is blank. We'd also like to know what the checkbox corresponds to, perhaps with a small phrase explaining what we're checking.

​

To achieve this, we'll need to use methods that modify the placement of the different elements, allowing us to add text to these elements and much more. To do this, we'll have to store each element we've created in a variable, and from there, we'll have access to a multitude of methods provided by these elements (for those more familiar, each element created with tools is a class buttons, labels, checkboxes are classes).

​

The methods we'll be using are as follows:

  • setGeometry(): This method allows us to place an element exactly where we want it in pixels. It includes four arguments (X, Y, XH, YH). The first two determine the starting position of the element on the X and Y axes, respectively.  XH defines the width the element will take on the X-axis, and the last defines the height the element will take on the Y-axis.

​

  • setText(): This method allows us to assign a name or text to the respective element. It takes one argument, which is the text you want to assign.

pyside Qt interface

#----create element-----

var_Button = QT.QPushButton(my_window)

var_Button.setText('I am a Button')

var_Button.setGeometry(0, 10, 100, 40)

​

var_Label = QT.QLabel(my_window)

var_Label.setText('I am a Label')

var_Label.setGeometry(0, 100, 100, 40)

​

var_Checker = QT.QCheckBox(my_window)

var_Checker.setText('I am a CheckBox')

var_Checker.setGeometry(0, 200, 100, 40)

​

var_SpinBox = QT.QSpinBox(my_window) #The text cannot be placed on this element

var_SpinBox.setGeometry(0, 300, 100, 40)

​

return my_window 

A quick tip: if you're using the setText() method, there's a faster way to do it by directly inputting the text as an argument in the elements you want to create.

#----create element-----

var_Button = QT.QPushButton('I am a Button'my_window)

var_Button.setGeometry(0, 10, 100, 40)

​

var_Label = QT.QLabel('I am a Label'my_window)

var_Label.setGeometry(0, 100, 100, 40)

​

var_Checker = QT.QCheckBox('I am a CheckBox'my_window)

var_Checker.setGeometry(0, 200, 100, 40)

​

var_SpinBox = QT.QSpinBox(my_window) #The text cannot be placed on this element

var_SpinBox.setGeometry(0, 300, 100, 40)

​

return my_window 

The geometry method is very useful for positioning an element exactly where you want it. However, it can be redundant and quickly become inconvenient because the element will have a fixed position. For instance, if we resize the window, the elements won't adapt to the window's size.

pyside Qt interface

To address this, I recommend using layouts. Layouts are boxes that allow you to place all the elements inside them, organizing and arranging themselves automatically without you needing to do it manually. There are several primary Layouts:

  • QT.QBoxLayout() : This is an abstract class that arranges widgets either horizontally or vertically within a box. It serves as the base class for QHBoxLayout and QVBoxLayout.

​

  • QT.QFormLayout() : This layout is designed for creating form-based layouts typically used for form-like user interfaces. It arranges widgets in rows, usually label-widget pairs.

​

  • QT.QGridLayout() : This layout arranges widgets in a grid. Widgets are placed in cells defined by their row and column positions. It allows for precise positioning of widgets in a grid-like structure.

​

  • QT.QHBoxLayout() : This arranges widgets in a horizontal row, placing them side by side.

​

  • QT.QVBoxLayout() : This arranges widgets in a vertical column, stacking them on top of each other.

​

​

I recommend primarily using QHBoxLayout or QVBoxLayout layouts. Here's an example using both layouts.

The only downside with layouts is that you can no longer use "my_window" as an argument in the various elements because this argument is window-based and doesn't function with layouts. Therefore, if you want to add an element to a layout, you'll need to use the addWidget() method and pass the element you want to add as an argument. You'll need to do this for each element.

pyside Qt interface

#----create element-----

layout_vertical = QT.QVBoxLayout(my_window)#my_window is QWidget, can use it an argument

​

var_Button = QT.QPushButton('I am a Button')#layout_vertical not QWidget, can't use it an argument

layout_vertical.addWidget(var_Button)

​

var_Label = QT.QLabel('I am a Label')

layout_vertical.addWidget(var_Label)

​

var_Checker = QT.QCheckBox('I am a CheckBox')

layout_vertical.addWidget(var_Checker)

​

var_SpinBox = QT.QSpinBox() #The text cannot be placed on this element

layout_vertical.addWidget(var_SpinBox)

​

return my_window 

pyside Qt interface
pyside Qt interface

QVBoxLayout()                                                                        QHBoxLayout()

Using this method system, you have the ability to customize the various elements as you desire. For instance, with setStyleSheet(), you can change the font, text size, have rounded edges on buttons, alter text and background colors, modify the selection color, change the cursor appearance, and more. There's a wide array of elements you can adjust.

​

For example, to delve even deeper into customizing the various elements, you can do this:

pyside Qt interface

#----create element-----

layout_vertical = QT.QVBoxLayout(my_window)#my_window is QWidget, can use it an argument

​

var_Button = QT.QPushButton('I am a Button')#layout_vertical not QWidget, can't use it an argument

var_Button.setStyleSheet("""background-color: rgb(10, 50, 80); 

                                color: #ffffff;

                                font-size: 80px;""")

layout_vertical.addWidget(var_Button)

​

var_Label = QT.QLabel('I am a Label')

layout_vertical.addWidget(var_Label)

​

var_Checker = QT.QCheckBox('I am a CheckBox')

layout_vertical.addWidget(var_Checker)

​

var_SpinBox = QT.QSpinBox() #The text cannot be placed on this element

layout_vertical.addWidget(var_SpinBox)

​

return my_window 

For those more knowledgeable in programming, you might have recognized a bit of the CSS structure. Indeed, Qt uses something quite similar to style its interfaces, it uses a language called QSS, specific to Qt but incorporating the same functionalities and foundations as CSS.

​

It's possible to separate the styling of your interface from the interface itself. What I mean by this is that if you want to style your interface, you need to use the setStyleSheet() method for each created element. This can take up a lot of space in our code because in my example, there are only three lines, but you could end up with around twenty or thirty lines just for one element. If you repeat this operation every time for each element, the code will quickly become unreadable.

​

There's a way to address this, similar to HTML and CSS, by using another file type, QSS, to contain all the styling for your page. This allows you to separate the visual aspect from the interface aspect.

pyside Qt interface

For a simple and basic initial interface, there's not much need to use a QSS file. However, if you intend to create a fairly substantial interface with many functionalities and numerous elements, I recommend visiting this website Koor.fr to learn how to use QSS.

4. Retrieve/execute information with the interface

From here, you've created your first styled elements and positioned them where you want. Now, it's time to understand how to use buttons, retrieve information, for instance, from a checkbox or a live edit, and execute a function when a button is pressed.

​

Let's start with understanding how to execute a piece of code when clicking a button. Qt uses a system of connection between the function you want to call and the button. You establish a connection to the method to trigger its execution.

pyside Qt interface

And to do that, we'll simply create a function that will print("hello") and establish the connection between the button and the function. Firstly, we'll use the "clicked" method, which sends a signal when the button is clicked. Then, we'll use the "connect" method to link the signal emission to a specific function.

pyside Qt interface

#----create element-----

layout_vertical = QT.QVBoxLayout(my_window)

​

var_Button = QT.QPushButton('I am a Button')

layout_vertical.addWidget(var_Button)

​

var_Label = QT.QLabel('I am a Label')

layout_vertical.addWidget(var_Label)

​

var_Checker = QT.QCheckBox('I am a CheckBox')

layout_vertical.addWidget(var_Checker)

​

var_SpinBox = QT.QSpinBox()

layout_vertical.addWidget(var_SpinBox)

​

#----connection button----

var_Button.clicked.connect(myfuncButton)#emit signal and connect the signal to the function

​

return my_window 

function for the button when is pressed

def myfuncButton():

    print('hellow Qt')

Now what we'd like to do is to retrieve information from the interface, for example, to know if the checkbox is checked or not, to retrieve the content from the lineEdit, and to gather these different pieces of information.

​

To do this, it's quite simple, we just need to use methods like value() / isChecked() / text() / etc. Using these methods for any element, we can retrieve its value. For instance, if we want to retrieve the value from a QSpinBox and a checkBox, we would do something like this.

pyside Qt interface

#----create element-----

layout_vertical = QT.QVBoxLayout(my_window)

​

var_Button = QT.QPushButton('I am a Button')

layout_vertical.addWidget(var_Button)

​

var_Label = QT.QLabel('I am a Label')

layout_vertical.addWidget(var_Label)

​

var_Checker = QT.QCheckBox('I am a CheckBox')

layout_vertical.addWidget(var_Checker)

​

var_SpinBox = QT.QSpinBox()

layout_vertical.addWidget(var_SpinBox)

​

#----connection button----

var_Button.clicked.connect(myfuncButton)#emit signal and connect the signal to the function

 

#----print value-----

print(var_Checker.isChecked())

print(var_SpinBox.value())

​

return my_window 

Thanks to this, you can see that we retrieve the values of the elements. However, as we execute our code, we retrieve these values at the current moment, which is when the interface is created. What we actually want is that when we click the button, for example, we want to retrieve these values.

 

For this, we'll need to pass the variables "var_Checker" and "var_SpinBox" as arguments into the "myfuncButton()" function.

​

And in the line to connect the function to the button, we need to slightly change the "Connect" method. Instead of using .connect(myfuncButton), we'll use .connect(lambda: myfuncButton(var_Checker, var_SpinBox))

​

Now that our function takes arguments into account, we need to pass these arguments in the connect(). We also use lambda because if we don't, as soon as the interface is created, the function will execute directly, which is not what we want; we want it to execute only when we press the button.

pyside Qt interface

#----create element-----

layout_vertical = QT.QVBoxLayout(my_window)

​

var_Button = QT.QPushButton('I am a Button')

layout_vertical.addWidget(var_Button)

​

var_Label = QT.QLabel('I am a Label')

layout_vertical.addWidget(var_Label)

​

var_Checker = QT.QCheckBox('I am a CheckBox')

layout_vertical.addWidget(var_Checker)

​

var_SpinBox = QT.QSpinBox()

layout_vertical.addWidget(var_SpinBox)

​

#----connection button----

var_Button.clicked.connect(lambda : myfuncButton(var_Checker, var_SpinBox))

​

return my_window 

function for the button when is pressed

def myfuncButton(var_Checker, var_SpinBox)

    print(var_Checker.isChecked())

    print(var_SpinBox.value())

Now you're able to retrieve the various values and information that the user can input into the interface and use them as needed.

​

Considering we can retrieve information from the interface, we can also do the opposite, we can put information on the interface when the user does something. For instance, we'd like that when the user presses the button and if the checkbox is checked, change the text of the QLabel to 'is Checked', and if the checkbox isn't checked, replace the text of the QLabel with "not checked".

​

To achieve this, we simply need to add the QLabel into the arguments of the "myfuncButton" function and the "connect()". Then, we just need to use the "setText()method to change the text of the QLabel.

pyside Qt interface

#----create element-----

layout_vertical = QT.QVBoxLayout(my_window)

​

var_Button = QT.QPushButton('I am a Button')

layout_vertical.addWidget(var_Button)

​

var_Label = QT.QLabel('I am a Label')

layout_vertical.addWidget(var_Label)

​

var_Checker = QT.QCheckBox('I am a CheckBox')

layout_vertical.addWidget(var_Checker)

​

var_SpinBox = QT.QSpinBox()

layout_vertical.addWidget(var_SpinBox)

​

#----connection button----

var_Button.clicked.connect(lambda : myfuncButton(var_Checker, var_SpinBox, var_Label ))

​

return my_window 

function for the button when is pressed

def myfuncButton(var_Checker, var_SpinBox, var_Label):

    print(var_Checker.isChecked())

    print(var_SpinBox.value())

​

    if var_Checker.isChecked():

        var_Label.setText('is Checked')

    else:

        var_Label.setText('not Checked')

Thanks to these various methods or others, you can retrieve all the different elements you want, modify them, and do whatever you want with them. For the sake of simplicity and efficiency, I recommend creating your interface in the form of a Python class so that it's easier to use and more conventional.

​

Here's an example of what the code could look like as a class.

pyside Qt interface

5. Practical case in maya

To bring all this to life, let's take an example in Maya. We want to create an interface that allows us to choose the number of groups we want to create, select their names, and decide whether we want them to be hierarchical.

​

Firstly, we need to create our interface. It will contain a button to execute the controller creation program, a lineEdit to choose the group's name, a checkbox to determine if the groups should be hierarchical, a SpinBox to select the number of controllers to create. Finally, we'll place all these elements within a layout to organize everything.

​

So, initially, we'll have code that looks like this, with an application that will create a window.

pyside Qt interface

import sys

import PySide2.QtWidgets as QT


 

def main_intereface():

    my_window = QT.QWidget()

    my_window.resize(500, 440)

    my_window.setWindowTitle('my first UI in maya')

 

    return my_window 
 

if __name__ == '__main__':

    app = QT.QApplication(sys.argv)

    my_window = main_intereface()

    my_window.show()

    sys.exit(app.exec_())

But be careful, if you run this program in Maya, you'll encounter a lovely Fatal Error message. Indeed, every time you execute your window, Maya will literally crash. Why? Because you need to understand that Maya already has an application running, and within your interface, you're attempting to create a new application within an existing one. This will inevitably cause Maya to crash every time because it's not possible to create an application within another application.

pyside Qt interface

To fix this, we'll modify the application so that it can retrieve the instance of the existing application, which is Maya in this case, allowing our window to attach itself to the Maya application. We'll replace "QT.QApplication(sys.argv)" with "QT.QApplication.instance()" and we can remove the line "sys.exit(app.exec_())". This is because, by retrieving the instance of the application, we don't need to launch it separately as it's already running.

​

"QT.QApplication.instance()" in PyQt/PySide is used to retrieve the instance of the QApplication object that represents the running application.

It returns a reference to the currently running QApplication instance. This method is typically used to get access to the application instance from anywhere within the code, allowing interactions or modifications to the application's properties and behavior. For instance, it can be used to access settings, manage the application's event loop, or perform other operations related to the application's state. If there's no QApplication instance running, it returns None.

​

In this case, we no longer need the sys library, so we can remove it.

import PySide2.QtWidgets as QT


 

def main_intereface():

    my_window = QT.QWidget()

    my_window.resize(500, 440)

    my_window.setWindowTitle('my first UI in maya')

 

    return my_window 
 

if __name__ == '__main__':

    app = QT.QApplication.instance()

    my_window = main_intereface()

    my_window.show()

Here's a small tip if you want your program to detect whether it needs to create an application or retrieve the instance of the open application. This way, you can run your code on Visual Studio (where it needs to create an application to work) and Maya (where it needs to retrieve the instance of the Maya application to work).

​

We'll need to use a small "if" statement to determine whether it should create a new application or use the existing one.

import PySide2.QtWidgets as QT

import sys
 

def main_intereface():

    my_window = QT.QWidget()

    my_window.resize(500, 440)

    my_window.setWindowTitle('my first UI in maya')

 

    return my_window 
 

if __name__ == '__main__':

    if not QT.QApplication.instance():

        app_start =

        app = QT.QApplication(sys.argv) #if no apllication executed create aplication

    else:

        app_start = 0

        app = QT.QApplication.instance() #if apllication executed create intance

    my_window = main_intereface()

    my_window.show()

    if app_start: # if we create app start the app 

        sys.exit(app.exec_())

pyside Qt interface

Now that we have this, we can start adding the various elements to have our final interface.

import PySide2.QtWidgets as QT

import sys
 

def main_intereface():

    my_window = QT.QWidget()

    my_window.resize(500, 440)

    my_window.setWindowTitle('my first UI in maya')

​

    my_layout = QT.QVBoxLayout(my_window)

   

    name_ctrl = QT.QLineEdit('name controller')

    my_layout.addWidget(name_ctrl)

   

    hierarchy= QT.QCheckBox('Hierarchy')

    my_layout.addWidget(hierarchy)

   

    nmb_ctrl = QT.QSpinBox()

    my_layout.addWidget(nmb_ctrl)

   

    button = QT.QPushButton('create controller')

    my_layout.addWidget(button)

    return my_window 
 

if __name__ == '__main__':

    if not QT.QApplication.instance():

        app_start =

        app = QT.QApplication(sys.argv) #if no apllication executed create aplication

    else:

        app_start = 0

        app = QT.QApplication.instance() #if apllication executed create intance

    my_window = main_intereface()

    my_window.show()

    if app_start: # if we create app start the app 

        sys.exit(app.exec_())

pyside Qt interface
pyside Qt interface

Now that we have this, we can start adding the various elements to have our final interface. We will begin writing the function that will allow us to create controllers in Maya. For this, we will use Maya's CMDS. We will create a transform mode under our transform, where there will be a curve. A for loop will determine how many times we need to create controllers, and a condition will help us decide whether to parent the controllers to each other. It will look like this (don't forget to import the cmds library into your code 'import maya.cmds as cmds').

def createCtrl_with_Button(name_ctrl, nmb_ctrl, hierarchy):
    for i in range(nmb_ctrl.value()):
        grp = cmds.createNode('transform', n=f"root_{name_ctrl.text()}_{str(i)}", ss=True)
        crv = cmds.circle(n= f"curve_{name_ctrl.text()}_{str(i)}")[0]
        cmds.parent(crv, grp)
        
        if
hierarchy.isChecked() and i != 0:
            cmds.parent(f"root_{name_ctrl.text()}_{str(i)}", f"curve_{name_ctrl.text()}_{str(i-1)}")

​this function:

  • It uses a loop to create control objects based on the "nmb_ctrl" parameter.

  • For each iteration in the loop:

    • Creates a new transform node named "root_{name_ctrl}_{i}" using "cmds.createNode"

    • Creates a circle control curve named "curve_{name_ctrl}_{i}" using "cmds.circle".

    • Parents the control curve under the newly created transform node (grp).

    • If hierarchy is True and it's not the first control object (i != 0):

      • Parents the current control's root node under the previous control's root node to create a "hierarchical"   structure

​

Essentially, this function generates control objects with associated curves and arranges them hierarchically if the "hierarchy" flag is set to True. This could be part of a rigging tool or process in Maya to create a series of control objects that are linked in a specific hierarchy.

​

Now, all that's left is to connect the button to the function, and everything will work. You will need to use the same method as we used previously with the button to make it work, using clicked.connect().

button.clicked.connect(lambda :createCtrl_with_Button(name_ctrl, nmb_ctrl, hierarchy))

pyside Qt interface

Here's a small tip: if you want the window to always stay on top, use this method on "my_window". 

"my_window.setWindowFlags(my_window.windowFlags() | Qt.WindowStaysOnTopHint)"

6. Conclusion

Well done! Now you have the ability to create your own interface, connect your functions to an interface, and establish communication between your interface and scripts that will interact in any software.

 

Thanks to this, you have the ability to do absolutely anything that comes to your mind, from creating a program to generating shortcuts for yourself, to even creating colorful interfaces that blink everywhere like a Christmas tree.

pyside Qt interface
pyside Qt interface maya
bottom of page