A popular method for working with blend shape animation for facial animation is to link a camera to the face, often by constraining or parenting it underneath a joint representing the skull. This view then stays with the character, allowing a stable view for creating animation. In this project, we will create an interface optimized for animation of blend shape animation.
First, let s take a quick look at the standard Maya Blend Shape Editor, seen in Figure 11.20.
The Blend Shape Editor provides more control than an animator needs. While allowing an artist to add and remove blend shapes is great for a technical director, once it gets to the animation stage, all those issues should be resolved, and it would be awful if the animator were to accidentally deform the blend shapes . Therefore, we will remove those. Moreover, the huge scroll layout seems like an awful waste of space.
Unfortunately, we cannot easily create a vertical slider as fully featured as that in the Blend Shape Editor. We want our sliders to possess the following attributes:
Horizontal or vertical orientation
Real-time updating of model (rather than on slider release)
Representation of animation data
Unfortunately, we cannot do all three. While MEL provides the facility to connect attributes directly, with the command connectControl , and we could build each individual element out of sliders and fields, they will not give the user feedback on whether a channel is animated. However, if we are simply willing to give up the vertical orientation, we get all this and more, in the form of automatic popup menus with the control attrFieldSliderGrp . This control sets up the two-way connection, gives us pre-made popup menus, and an interface with which animators are familiar.
Our first step is to design the window with all the layout breakdowns needed to create our window, seen in Figure 11.21.
By this point, the process that we will use to find all our data to build our user interface should be readily familiar, so let s get to it.
We begin, as always, by validating our script. See Example 11.17.
Example 11.17: Script validation.
global proc blender () { }
Knowing that we have a valid script, we can begin adding functionality. Before we can build the window, we need to gather data from the scene. Specifically, we need to find the single object with a blendshape node we want to take control of. See Example 11.18.
Example 11.18: Finding the blendshape node of the selected object.
// Find all the info on the blend node // on current object // Build Selection List string $sel[] = `ls -selection`; // Check to make sure the user has a single // object selected if (`size $sel` != 1) error "Please select a blendshape object"; // Get the history listing for the object // to find any blendshapes string $hist[] = `listHistory`; string $attribs[], $blendShapeNode; // Find the blendshape for ($each in $hist) if (`nodeType $each` == "blendShape") $blendShapeNode = $each; // This finds the names of the attributes // that drive the blend shape definition string $contrib[] = `listAttr -multi ($blendShapeNode + ".weight")`;
On the CD | The text for this script is found on the companion CD-ROM as Chapter_11/project_UI06/v01/blender.mel. |
Now that we have our data gathered, we are ready to build the UI. Like any window, we first define the window, using an integer to define its width, aiding in window construction. See Example 11.19.
Example 11.19: The process begins with the creation of a window.
if (`size $contrib` > 0) //we build only if we qualify { // Build Window int $windowWidth = 750; if(`window -exists blenderUI` != 1) { window -maximizeButton false -resizeToFitChildren false -title "Blender" -iconName "Blender" -menuBar false -widthHeight $windowWidth 274 blenderUI;
On the CD | The text for this script is found on the companion CD-ROM as Chapter_11/project_UI06/v02/blender.mel. |
Next, we add the formLayout . While using the form layout makes the process of building a dynamic UI somewhat trickier, it is pretty much the only way to get the exact look we want. See Example 11.20.
Example 11.20: The addition of a form layout.
// Form Layout string $blenderForm = `formLayout -numberOfDivisions 100`;
We will embed the modeling panel in a Pane Layout. Our modeling window will not have the menu bar, nor will it respond to key commands to turn on its shading status, so all that, along with camera assignment, must be done in code. After creating the paneLayout , we create the modelEditor panel. The Model Editor has many arguments that can influence its appearance. We have chosen to set the bare minimum to get a decent view with which to work. After creation, we assign the view to use the perspective camera. We could use a popupMenu or a optionMenu to control which camera to use or view settings. It would also be very easy, through a carefully constructed naming convention or by building artificial connections, to add the capability to switch between different characters . See Example 11.21.
Example 11.21: Adding a modeling panel to our UI.
//Pane Layout string $modPane = `paneLayout -configuration single`; //Modeling Pane string $modEdit = `modelEditor -displayAppearance smoothShaded -grid 0` ; // CHANGE CAMERA USED HERE modelEditor -edit -camera perspShape $modEdit; setParent..; //up to form showWindow blenderUI;
On the CD | The text for this script is found on the CD-ROM as Chapter_11/project_UI06/v03/blender.mel |
If we execute the script, we see something resembling Figure 11.22.
formLayout The collapsed panel is due to not using the power of the formLayout. We add the form layout edit before the window is drawn. We will have the modeling panel take up 45% of the form layout. See Example 11.22.
Example 11.22: Sizing the modeling panel element.
formLayout -edit -attachForm $modPane "top" 2 -attachForm $modPane "left" 2 -attachForm $modPane "bottom" 2 -attachPosition $modPane "right" 0 45 $blenderForm; showWindow blenderUI;
On the CD | The text for this script is found on the companion CD-ROM as Chapter_11/project_UI06/v04/blender.mel. |
After adding this edit statement, our UI appears somewhat similar to Figure 11.23.
Now that we have the first half of the user interface finished, we can move into the dynamically built area.
The first objective will be to place a column layout to which we will add our sliders. Using a column layout is the best way to add an unknown number of controls to a UI and still have some control over its appearance. We capture the name of the column, which is a child of the formLayout , for later alignment. See Example 11.23.
Example 11.23: Adding a column layout to hold future additions to the UI.
string $blenderColumn = `columnLayout -adjustableColumn true -rowSpacing 5`;
To dynamically build the slider controls, we use a for loop to add a rowLayout , attrFieldSliderGroup , and button for each of the shapes that contribute to the blendshape node. Note that while some people like to overcrank the values of the blendshapes, we have limited the slider to between 0 and 1. This is the generally expected behavior, and the values that will be used by most artists most of the time. Should the need arise to go outside the expected range, it is a simple matter to go into the script and alter the limits. See Example 11.24.
Example 11.24: Using a loop to dynamically add controls to our layout.
for ($each in $contrib) { rowLayout -numberOfColumns 2 -columnWidth2 $windowWidth 50 -adjustableColumn 1 -columnAlign 1 center -columnAlign 2 center -columnAttach 1 both 0 -columnAttach 2 both 0 ; attrFieldSliderGrp -min 0.0 -max 1.0 -at ($blendShapeNode + "." + $each) -label $each -adjustableColumn 3 -columnAlign 1 right -columnWidth 1 90 -columnWidth 4 1 ; button -label "Key" -align center -command ("setKeyframe " + $blendShapeNode + "." + $each); setParent..; //back to Column }
On the CD | The text for this script is found on the companion CD-ROM as Chapter_11/project_UI06/v05/blender.mel. |
We now want to modify the formLayout edit statement to account for and place the columnLayout in the formLayout . We will attach the left side of the columnLayout to the right side of the paneLayout that contains the modelPanel . See Example 11.25.
Example 11.25: Using the formLayout command with the edit flag to control the appearance of our UI.
formLayout -edit -attachForm $modPane "top" 2 -attachForm $modPane "left" 2 -attachForm $modPane "bottom" 2 -attachPosition $modPane "right" 0 45 -attachForm $blenderColumn "top" 2 -attachControl $blenderColumn left 5 $modPane -attachForm $blenderColumn "bottom" 2 -attachForm $blenderColumn right 2 $blenderForm;
On the CD | The text for this script is found on the companion CD-ROM as Chapter_11/project_UI06/v06/blender.mel. |
Right now, the UI works, and works as planned, seen in Figure 11.24. Because we used the formLayout , the user is free to resize the UI to his liking, and the 3D viewport and the sliders scale with the window.
Because it is possible for the user to have enough blendshapes to more than fill the UI, in the script review we have incorporated conditional statements to implement in a scroll layout when needed. Note that when the scrollLayout is active, we have to add conditional setParent and formLayout “edit statements.
On the CD | The text for the final script is found on the companion CD-ROM as Chapter_11/project_UI06/final/blender.mel. |
We have seen how the flexibility and power of user-created UIs can be greatly enhanced by incorporating appropriate panel elements. Next, we will cover scripted panels, which take almost the antithesis approach, incorporating controls and layouts into panels.
global proc blender () { // Find All the info on the blend node // on current object string $sel[] = `ls -sl`; if (`size $sel` == 0) error "Please select a blendshape object"; string $hist[] = `listHistory -il 2`; string $attribs[], $blendShapeNode; for ($each in $hist) if (`nodeType $each` == "blendShape") $blendShapeNode = $each; string $contrib[] = `listAttr -multi ($blendShapeNode + ".weight")`; float $conWts[] = `getAttr ($blendShapeNode + ".weight")`; if (`size $contrib` > 0) { // Build Window int $windowWidth = 750; if(`window -exists blenderUI` != 1) { window -maximizeButton false -resizeToFitChildren false -title "Blender" -iconName "Blender" -menuBar false -wh $windowWidth 274 blenderUI; // Form Layout string $blenderForm = `formLayout -numberOfDivisions 100`; // Pane Layout string $modPane = `paneLayout -configuration single`; // Modeling Pane string $modEdit = `modelEditor -displayAppearance smoothShaded -grid 0` ; // CHANGE CAMERA USED HERE modelEditor -edit -camera perspShape $modEdit; setParent..; //up to form string $scrollLay; if (`size $conWts` > 10) $scrollLay = `scrollLayout -horizontalScrollBarThickness 0 -verticalScrollBarThickness 5`; string $blenderColumn = `columnLayout -adjustableColumn true -rowSpacing 5`; for ($i = 0; $i < `size $conWts`; $i++) { rowLayout -numberOfColumns 2 -columnWidth2 ($windowWidth) 50 -adjustableColumn 1 -columnAlign 1 "center" -columnAlign 2 "center" -columnAttach 1 "both" 0 -columnAttach 2 "both" 0 ; attrFieldSliderGrp -min 0.0 -max 1.0 -at ($blendShapeNode + "." + $contrib[$i]) -label $contrib[$i] -adjustableColumn 3 -columnAlign 1 right -columnWidth 1 90 -columnWidth 4 1 ; button -label "Key" -align center -command ("setKeyframe " + $blendShapeNode + "." + $contrib[$i]); setParent..; //back to Column } if (`size $scrollLay` > 0) setParent ..; // up to scroll if needed setParent ..; // up to form if (`size $scrollLay` == 0) { formLayout -edit -attachForm $modPane "top" 2 -attachForm $modPane "left" 2 -attachForm $modPane "bottom" 2 -attachPosition $modPane "right" 0 45 -attachForm $blenderColumn "top" 2 -attachControl $blenderColumn left 5 $modPane -attachForm $blenderColumn "bottom" 2 -attachForm $blenderColumn right 2 $blenderForm; } else { formLayout -edit -attachForm $modPane "top" 2 -attachForm $modPane "left" 2 -attachForm $modPane "bottom" 2 -attachPosition $modPane "right" 0 45 -attachForm $scrollLay "top" 2 -attachControl $scrollLay left 5 $modPane -attachForm $scrollLay "bottom" 2 -attachForm $scrollLay right 2 $blenderForm; } showWindow blenderUI; } } }