Wednesday 20 May 2015

Python (Maya UI) snippets for the budding TD Part 2

 In this article, I'm going to discuss a variety of features offered by Maya for effective UI development and design.  This is all done in Maya's native UI code (not QT or TkInter) which is easy to use and can create some very nice interfaces with a little work.

If you are unfamiliar with the process of creating a general Maya UI in python, please read this article before you start.


Understanding UI parenting and navigation

As mentioned in my previous article, UI's are hierarchical.  Each time we add a new Layout, we create a new hierarchy.  A Layout is a 'parent' where new controls will be added as its children.

Of course, if we add another Layout after we create an initial one, this new Layout is a child of the previous Layout.  In terms of how this would look visually compared to the code, it would be something like this.



Getting around...

Layouts are used to define how controls are visually laid out in a window.  This could be a group of tabs, in a grid, etc.  Multiple Layouts can be used to define areas of a UI.

As we know, adding a control to a UI places it as a child under the current Layout.  Sometimes the need to add controls to other Layouts in the UI will be needed, and sometimes need to jump up a level to add a new Layout into an existing one.

Changing the currently selected Layout can be done using the setParent() command.  The parameter passed with this command can contain a path (which we can get by using a variable when creating a Layout), or if we only want to jump back one level we can use a double period notation (much the same as is used when navigating folders in a file system)

# Create a Layout
firstLayout = cmds.columnLayout()

# Create a new Layout (this is a "child" of the previous)
secondLayout = cmds.columnLayout()

# Add a control here places it under secondLayout
cmds.button()

# Jump back to the firstLayout
cmds.setParent('..')

# Adding a control is now placed under firstLayout
cmds.textField()

Alternatively we could have also set our path in the setParent() control by using

cmds.setParent(firstLayout)

This second approach using the variable name is a clean and efficient way to manage UI's with multiple layouts, especially those with a lot of them.

Using multiple layouts in a UI

Multiple Layouts can be used to create dynamic and powerful UI's for your tools.  While using setParent() is one way to switch to a parent before creating a new control, we can also create controls within a specific Layout by adding the parent parameter when creating the control itself.

In this example, I am adding a new button to the Layout I created at the bottom of the UI window.  Note that you can also change the color of a Layout.  I used white here to make the other Layout obvious.

import maya.cmds as cmds

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

# Create the main UI Layout
theMasterLayout = cmds.columnLayout()

# Create a button that  add's new buttons into the
# Layout below this one.
cmds.button( label="add new button", command="cmds.button(parent=newLayout)", width=300 )

# Create our second 'child' Layout
newLayout = cmds.columnLayout(backgroundColor=(1,1,1), width=300)

# Place a text message into this layout for reference
cmds.text( label="\nClick button at top to add one here\n" )

# Display the window
cmds.showWindow()

Dynamically creating variables

Remember that using a variable when creating controls gives us the path information we need to query it later on. However lets say we're creating dynamically added controls such as the example above, and we want each one to be declared in a variable for easier reference.

Dynamically generating a variable name can be done in a few ways, some not overly favoured by python users, but ways that work nonetheless.

Exec()

I tend to use exec() to allow me to create a string with the variable name embedded in it. Its quick and dirty (and many python users dislike this approach), but it works.

newButtonVariable = 'buttontoclick'
exec('%s = cmds.button()' % newButtonVariable)

# we can make sure this worked by printing the variable
print buttontoclick

Symbol tables

Alternatively, creating a variable that stores the new variable name we need, can be read by asking python for the locals() or globals() symbol table (a dictionary containing the names of variables, etc created in your script)

newButtonVariable = 'buttontoclick'
locals()[newButtonVariable] = cmds.button()

# check it worked
print buttontoclick

UI layouts for complex tools

The tabLayout is a great option for UI's that contain a range of tools and functions - for instance a rigging tool that offers skeleton creation, a tool for creating control shapes and maybe a tool for adding dynamics to a rig.  Each of these could be built with their own UI windows, though we could also make a single UI that gives the user a 'tool kit' experience.

To use them is fairly straight forward.  Each tab is created as a separate Layout that is stored as the children of a main tabLayout.  You can create your tab contents (ie for each tool) like this.

import maya.cmds as cmds

winID = "kevsTabWin"
if cmds.window(winID, exists=True):
    cmds.deleteUI(winID)

# Create the window
cmds.window(winID,t="tabs")

# Create the tabLayout
tabControls = cmds.tabLayout()

# Create the first tab UI
tab1Layout = cmds.columnLayout()
cmds.button()
cmds.textField()

# We need to go back one to the tabLayout (the parent)
# to add the next tab layout.
cmds.setParent('..')

# Create the second tab UI
tab2Layout = cmds.columnLayout()
cmds.button()
cmds.textField()
cmds.setParent('..')

# Create appropriate labels for the tabs
cmds.tabLayout(tabControls, edit=True, tabLabel=( (tab1Layout,"Welcome"),(tab2Layout,"Human") ) )

# Display the UI
cmds.showWindow(winID)

Frame Layouts

The frameLayout can be used to neatly frame a UI with title bar at the top, but can also be set to allow collapsing and expanding of the frame.  It has the advantage that it fits in with a familiar Maya workflow users are used to, and multiple frameLayout's can be used to populate a UI that may require many sections.

FYI : In the official documentation, the frameLayout only allows one child control under it.  This means to create a detailed UI, you should create a new Layout as the child for the frame then add your UI controls into it.

When using multiple frame layouts underneath each other, we need a main 'container' to stack them into.  For that, a simple columnLayout usually suffices.  To make it easy to jump back to this container later on, declaring it as a variable helps simplify the setParent() process.

import maya.cmds as cmds

winID = "kevsFrameWin"
if cmds.window(winID, exists=True):
    cmds.deleteUI(winID)

# Create the window
cmds.window(winID,t="frames")

# Create a 'container' Layout to keep our frames in
rootLayout = cmds.columnLayout()

# Create a frame layout. Note that I am not declaring a
# variable here as frameLayout are only supposed to
# support a single child, and our child will be another
# layout...  So we really want to reference the child
# rather then the frameLayout parent anyway.
cmds.frameLayout( label="Top frame" )

# Generate a child Layout inside the frame.
# declare a variable here as it will allow us to reference
# contents inside our frame (within this layout).
frameOne = cmds.columnLayout()
cmds.button( label='A button in frame one')

# Jump back to our root container Layout to add a new Frame
# If we were to use the '..' approach, we would need *2* of
# them to get back to the root.
#
# '..' back to the frame parent, then '..' to the root parent
cmds.setParent(rootLayout)

# Next frame layout.  This one we will make collapsable
cmds.frameLayout( label="Collapsable frame", collapsable=True )

# As per the first frame, generate our child layout
frameTwo = cmds.columnLayout()
cmds.text(label="Here's some nice buttons for you to click")
cmds.button( label='Another button' )
cmds.button( label='Yes, a button' )
cmds.button( label='Maybe 3 is enough' )

# To add more, we need to follow the same process. setParent,
# frameLayout, columnLayout, controls inside frame...

# Display the UI
cmds.showWindow(winID)

Laying out things and sizzling up that UI

Nothing looks cooler then a UI with nicely laid out controls, imagery and clean design.  Up to this point, the examples I just added controls.  That means they were stacked and drawn one under another, often left aligned and missing any visual appeal.  Here are a few tips about how to add pizzazz to your layouts.

Banner images

A nice banner image can do wonders for a UI.  To add an image control to a Layout, just use the image() control.  You can use any image format that Maya supports, and transparent pixels/alpha channels are retained when the image is displayed as well.

# Add an image control
cmds.image( image='d:/kevsimage.png' )

Of course, the main issue you will run into when distributing your tools is making sure that you can find the image file on any system that its run on.  To ensure this is not the case, I recommend you have your tools utilise the documents/maya/(maya version)/prefs/icons folder.  If you read my first article with snippets of code, you can find this folder very easily using

filePath = cmds.internalVar(userBitmapsDir=True)

Separators

Nice beveled lines, or invisible blocks that fill space can be added using a separator().  There are a variety of styles, including none (blank), and both height and width can be set as well.

# Add a horizontal line that is 300 pixels wide
cmds.separator( style='in', width=300)

Note that using height will add an even amount of space above and below this line, making it useful to add additional blank spacing.  Note that by using the parameter horizontal=False, the separator will draw a vertical line.

# Add a blank block that gives us a 16 pixel gap between controls
cmds.button( label="First button" )
cmds.separator( style='none', height=16 )
cmds.button( label="16 pixels lower" )

Get familiar with your options

As mentioned a few times in my articles, the best thing you can do is become familiar with the options available to your controls and Layouts.  In most of these, there are spacing options (for example, a columnLayout has a rowSpacing parameter to place controls a certain number of pixels apart).  Everything you need to know can be found in the Autodesk python documentation online.

Other handy layouts

There's a few other useful Layouts that can help design something more structured for your UI.  While I won't talk about them all (formLayout, paneLayout, etc) I will discuss one more easy to use and simple Layout you can consider.

rowColumnLayout() creates a nice grid-type structure.  As controls are added, they start on the left and work their way across and down as you fill up the Layout.  Using a mix of separators and controls, you can achieve some fairly nicely laid out UI's this way.  There are plenty of options in this Layout worth reviewing...

# Laying out the rowColumnLayout is simple as this
cmds.rowColumnLayout( numberOfColumns=3, columnWidth=[ (1,100),(2,32),(3,100) ] )

# Add first line of controls
cmds.text( label="first control" )
cmds.separator (style='none', width=32)
cmds.button( label="first click" )

# Adding more controls starts on the next row down.
cmds.text( label="second control" )
cmds.separator (style='none', width=32)
cmds.button( label="click me next" )

That's about it

This article is just scratching the surface of the features in Maya.  As I say repeatedly, do take the time to read up on the Autodesk Maya documentation to learn what else you can do.

As far as one more tip - all of the parameters for these commands have both a long and short name.  Learn the shorter versions (e.g. instead of title='xxx', just use t='xxx') - you'll see it in brackets next to the long name on the Autodesk docs - as it will make for less typing, and obviously shorter looking code.

I hope this has given you at least one thing to use, if not more.  I'll be back later in the year with a variety of additional python snippets to wet your tastebuds...  Until next time...

4 comments :

  1. This has been extremely helpful, thank you!

    ReplyDelete
  2. You created a great tutorial. The problem I've had with the Maya documentation is it doesn't always show the examples of the various flags; they tend to be very vague.
    You however went over a few, which was very helpful!

    ReplyDelete
  3. Thanks really helpful to understand

    ReplyDelete
  4. Just an small comment regarding dynamically creating controls (and referencing with a variable). I mention this in a later article - but you can actually specify a name for a control by adding it at the start of the arguments passed. To make a button -- cmds.('newButton',label='New button'). If you need to query it later, just use the name again. ie.
    print cmds.button('newButton', label=True, q=True)... Much cleaner.

    ReplyDelete