Tuesday, 19 May 2015

Introduction to simple Maya UI coding with Python

Maya provides a fairly straightforward way to create UI's for scripts and tools that you develop.  Of course, there are more powerful and flexible options such as QT (via PyQT and PySide), as well as Python's own TkInter if you wanted to toy with it - but for this article I'm going to show you how easy it is to build them using Maya's native system.

How exciting!  A tool to help me google stuff!


Note that this initial article is extremely basic and intended for someone completely new to using Maya's native UI code.  For more advanced information, I'm writing a more indepth article of tips to using many of the other features provided by Maya.

Windows, Layouts and Controls

Creating a Maya UI in python is a very simple process.  We start by creating a window, then we add a Layout (a container that defines how the controls in the UI are laid out) and fill it with controls such as buttons, text boxes and more.  This very simple piece of code does just that.  Its a simple window with a button that doesn't do anything other then to display "Click me".

import maya.cmds as cmds

# Start with the Window
cmds.window(title="Simple UI in Maya" )

# Add a single column layout to add controls into
cmds.columnLayout()

# Add controls to the Layout
cmds.button( label="Click me")

# Display the window
cmds.showWindow()

FYI : The main thing that throws new coders is not understanding that a UI is actually a hierarchy.  When we see code, to most it just looks like a linear dump of commands.  There's no apparent hierarchy stuff visibly going on, but internally there is.  I'll explain a little more later - but for now, just add that as a mental note.

It all starts with a Window

As per the previous source code, you start by creating a window.  When we invoke the window() command, it generates a new window that will eventually hold our UI once we've coded it in.  By default a window is just a blank container floating on screen without anything inside it.

Preventing multiple windows

Now, one thing that can be frustrating is that the window() command will do just that - make a window - and another, and another, and another blindly if we ask it to.  One very important task you should always perform at the start of your UI code is making sure that you close (or delete) the window if it already exists...  This can be done by using a unique id string that relates to your tool, and the deleteUI() command.

import maya.cmds as cmds

# Define an id string for the window first
winID = 'kevsUI'

# Test to make sure that the UI isn't already active
if cmds.window(winID, exists=True):
    cmds.deleteUI(winID)

Once you've checked for this, then you can go ahead and create your UI by using the window() command and passing the id string as the first parameter.  If you don't specify the id string, that checking code just won't find it.

# Now create a fresh UI window
cmds.window(winID)

Creating the contents

Think of a window as a bucket - there's just one big empty space and throwing things into it will just have them fall into the bucket and pile up messily at the bottom.  Obviously a UI shouldn't be a bunch of controls piled in a messy heap, and Maya won't let you just start to throw controls into a window directly anyway.

The next thing you need to do is add a Layout.  This is another container of sorts that defines a structure for how controls will be drawn (or laid out) inside it.  A columnLayout is perhaps the most basic of all of them, providing a vertical container where controls appear underneath each other as they are added.  As far as the concept of hierarchy goes (that I mentioned at the start), this Layout is a child of this window.

# Add a Layout - columnLayout stacks controls vertically
cmds.columnLayout()

When a Layout is created, it automatically becomes a parent object - any lines of code after the Layout command that create controls are the children (contents) of this Layout.

FYI : Just to make it more confusing - creating another Layout will set it as a new parent (any new controls will become its children.)  But it will also be a child Layout of the first Layout. You can start to insert Layouts into each other if you want to separate your UI up into blocks, and by knowing that this occurs is important for creating clean UI code that doesn't just become a messy jumble of Layouts within each other.

Adding some wizz-bang controls

The fun is in adding the controls we need for our UI - buttons, images, check boxes and other handy items.  Its all pretty straight forward, though any field that you want to be able to query information from should be declared as a variable.  In the code below, the button control does not have a value that needs to be queried (its simply used to run a function or command).  However, the textField is an input box that a user can type text into.  As this will likely be queried, its been declared as a variable called whatUSay.

# Add controls into this Layout
whatUSay = cmds.textField()
cmds.button(label='click me')

FYI : This variable will store a reference to the control, and not the value which is typed into the control.  When we want a value, we need to query the control to get it.  We'll look at that shortly.

Adding functionality to the controls

Obviously a UI that doesn't actually do anything is a tad pointless (even if it looks cool) so we'll add in some code to query the text field and print it to the history pane of the script editor whenever the button is clicked.  For this, I've written a simple function that sits at the top of the code.  The function requires that the control reference is passed to it (ie. the variable that we used when we created the control) so it can do a query.

# Function that queries the textField and prints it to the
# History pane in the script editor
def printTxtField ( fieldID ):
    print cmds.textField( fieldID, query=True, text=True)

The button requires a command to tell it to do something when its clicked on.  In this case, I've added the function name and the variable between quotes as the value for the command parameter

cmds.button(label='click me', command='printTxtField(whatUSay)')

Controls often have MANY parameters that we can set, adjust, query and use to change the way they are displayed and behave.  You should check out the Autodesk documentation and familiarise yourself with what's available in each one.

Now show us the money... Eh, window.

After we've created a window, added a Layout and filled it with controls, we just need to tell Maya to display it using the showWindow() command.  The complete script should now look like this...

import maya.cmds as cmds

# Function that queries the textField and prints it to the
# History pane in the script editor
def printTxtField ( fieldID ):
    print cmds.textField( fieldID, query=True, text=True)

# Define an id string for the window first
winID = 'kevsUI'

# Test to make sure that the UI isn't already active
if cmds.window(winID, exists=True):
    cmds.deleteUI(winID)
    
# Now create a fresh UI window
cmds.window(winID)

# Add a Layout - a columnLayout stacks controls vertically
cmds.columnLayout()

# Add controls into this Layout
whatUSay = cmds.textField()
cmds.button(label='click me', command='printTxtField(whatUSay)')

# Display the window
cmds.showWindow()

Conclusion

If we now run the script in Maya, entering text and pressing the button should display the text into the history pane.  And that is the most basic of UI's for now.  Its fairly straight forward to do something very basic like this - and making sure you understand what is happening with the whole parenting hierarchy is fairly important to moving forward into more advanced stuff.

Of course, its no fun if its this simple.  In the next article (which will be a continuation of my Python snippets for budding TD's), I'll create a collection of tips on using various types of controls and look at some of the other great Layouts - as well as talk more about this hierarchy and how we can jump about inside it to give us some more dynamic results.

Ciao for now!

6 comments :

  1. Hey Kev,

    I'm new to building interfaces for Maya, and I stumbled upon this blog of yours, it was pretty handy and easy to understand, so now I have a basic knowledge of what do all those commands do and how to use them.

    Thanks a lot!
    Alberto Martinez

    ReplyDelete
  2. Great to hear it was helpful! :)

    And thanks for the kind words - I've got a handful of other UI related tips on other articles in here if you are looking for more.

    I'm posting up some new technical material soon - Just created a whole pile of new tools, and hopefully can share a few tips from things I did...

    ReplyDelete
  3. Thank you for posting this great tutorial!
    Big help! :)

    ReplyDelete
  4. well done tutorial! thank you so much for this!

    ReplyDelete
  5. great tutorial!
    Thank you for taking your time to expain all this.

    If I may, I do have a question: why command='printTxtField(whatUSay)'
    instead of
    command=printTxtField(whatUSay)

    does the command have to be a string?

    ReplyDelete
  6. Yup, without the quotes python will evaluate the function when the code is compiled, thinking that printTxtField() is a function that will provide a value for "command=" to use.

    To illustrate, the example below will execute the setUpCommand function to pass back the value for 'command' to use. Comments don't format white space, so that return line would be indented under the function of course. :)

    def setUpCommand():
    return 'printTxtField(whatUSay)'

    whatUSay = cmds.textField()
    cmds.button(label='click me', command=setUpCommand())

    ---

    If I instead placed quotes around the command function like this:

    cmds.button(label='click me', command='setUpCommand()')

    When you clicked the button, it would instead execute 'setUpCommand()' each time, which of course does nothing other than return a string.

    Hopefully that made sense?

    ReplyDelete