Class 4 - UI
This class is about interfaces..
Script Editor window

Here's how you can open and close the script ed. window (eg. from a shelf icon containing the following code):


int $scriptTog = `window -q -vis $gCommandWindow`;
if ($scriptTog == 1)
  window -edit -vis 0 $gCommandWindow; // off 
else
  showWindow $gCommandWindow; // on
$gCommandWindow is Maya's identifier for the Script Editor window.
Alternative ways to input MEL

Short (one line) commands can be typed into the 'Command Line' window:


Cutter


MEL Studio


A new way is to use a web browser to send MEL commands to Maya! See the last section in these notes for some examples.
UI basics

A 'shell' (outer) window:


window -title "Test" testWin;
showWindow;

A window with a button:


window -title "Test" testWin;
columnLayout;
button -l "Click" -w 50 myButton;
showWindow;

Here's how the button can generate a (default) torus:


window -title "Test" testWin;
columnLayout;
button -l "Click" -w 50 -c "torus;" myButton;
showWindow;

Note: 'lsUI -windows' gives you a list of open windows (including Maya's main win. and script ed. win).

When you specify -w and -h values when you create a top-level window, MEL seems to "remember" the size. If you change the size with a new -w and/or -h, the window still comes up with the old size! What to do?

In other words, this is a user setting. The solution is to unset this behavior, and hand-edit userPrefs.mel (just once) to remove the stored size(s). After that you can change sizes at will and MEL will not store/retrieve the previous sizes.

This section shows a small but complete example of how you'd 'save' UI values (a long filename that the user has typed into a text field, a set of checkbox choices, a series of precise floats for animating muscles, etc.) to disk and reapply them the next time around. Result: our user can save/restore UI settings, which is pretty cool. The MEL command that makes this possible is 'optionVar'. Also, the saving to disk takes place in a file called userPrefs.mel, in your <Maya directory>/<version_number>/prefs directory.

The overall logic to create such a UI is this:

Be sure to name the UI element, its optionVar name and the var that holds the UI element's queried value in a consistent fashion. Otherwise things can get very confusing! In the example below we use 'makeWin_myFS', 'makeWin_myFS_F' and '$makeWin_myFS_FVal' for these three names.


// callback proc that saves user settings to disk when the user clicks our
// 'ExitWSettings' button
global proc makeWin_saveSettings() 
{ 
  float $makeWin_myFS_FVal = ` floatSliderGrp -q -v makeWin_myFS`;  
  print("Our slider's val: " + $makeWin_myFS_FVal + "\n"); 
  // serialize to disk (for reading back later)!!!!!!! 
  optionVar -fv makeWin_myFS_F $makeWin_myFS_FVal ; 
  deleteUI testWin; 
  return; 
}// makeWin_saveSettings() 

// This is the 'main()' proc. that the user invokes..
global proc makeWin() 
{ 
  // if there is an existing instance of the UI, 
  // simply pop it up if minimized and then return - 
  // there is really nothing else to do
  if(`window -exists testWin`) 
  { 
    showWindow testWin; // pop up if minimized, otherwise this call is a no-op
    return; 
  }

  // retrieve disk-based saved from the previous time (the very first time 
  // ever, there won't be a disk value of course, and the if() simply fails 
  // in which case and we use '0' to set our slider value)
  float $makeWin_myFS_FVal=0.0; 
  if(`optionVar -exists makeWin_myFS_F`) 
  { 
    // READ serialized (disk) value
    $makeWin_myFS_FVal = `optionVar -q makeWin_myFS_F`; 
  }

  // code for the UI - note that in floatSliderGrp we say 
  // '-v $makeWin_myFS_FVal' - this is where we 'magically' start the slider off 
  // with the value from the previous time.
  window -title "Test" testWin; 
  columnLayout;
  floatSliderGrp -minValue 0 -maxValue 100  -v $makeWin_myFS_FVal  makeWin_myFS;
  button -l "ExitWSettings?!!" -w 200 -c makeWin_saveSettings saveSettingsBtn;
  showWindow;
}// makeWin()
New shelf, new top-level menu

To add/remove a new shelf:


addNewShelfTab "Becky";
deleteShelfTab "Becky"; // brings up a 'Yes/No' prompt dialog box

Note that addNewShelfTab and deleteShelfTab are not built-in MEL commands - they are scripts that ship with Maya (do a 'whatIs' on each to discover its location).

Here's how you would add a menu entry to the toplevel (Main) Maya window:


string $menu = `menu -p $gMainWindow -l "MyMenu" 
		-tearOff true -aob 1 
		-postMenuCommandOnce true
		-familyImage "menuIconDisplay.xpm" myMenu`;
The above would create an empty menu called 'MyMenu'. To populate it with menu items, do this:

setParent -m $menu;
menu -e -dai $menu;

menuItem -l "MyItem1" -c "myItem1Selected";
// more menuItems..

When the user picks MyMenu->MyItem1, your callback proc myItem1Selected() will be run by Maya.
Form layout (from the documentation)

One of the more powerful layouts is the "formLayout". This layout supports absolute and relative positioning of child controls. For example, you may specify that a control`s position remains fixed while its dimensions are relative to the size of the window. This concept is best illustrated with the following example.


window -widthHeight 300 200 TestWindow2;
string $form = `formLayout -numberOfDivisions 100`;
string $b1 = `button -label "A"`;
string $b2 = `button -label "B"`;
string $b3 = `button -label "C"`;
string $b4 = `button -label "D"`;
string $b5 = `button -label "E"`;

formLayout -edit
-attachForm $b1 "top" 5
-attachForm $b1 "left" 5
-attachControl $b1 "bottom" 5 $b2
-attachPosition $b1 "right" 0 75

-attachNone $b2 "top"
-attachForm $b2 "left" 5
-attachForm $b2 "bottom" 5
-attachForm $b2 "right" 5

-attachOppositeControl $b3 "top" 0 $b1
-attachPosition $b3 "left" 5 75
-attachNone $b3 "bottom"
-attachForm $b3 "right" 5

-attachControl $b4 "top" 0 $b3
-attachOppositeControl $b4 "left" 0 $b3
-attachNone $b4 "bottom"
-attachOppositeControl $b4 "right" 0 $b3

-attachControl $b5 "top" 0 $b4
-attachOppositeControl $b5 "left" 0 $b4
-attachNone $b5 "bottom"
-attachOppositeControl $b5 "right" 0 $b4

$form;

showWindow TestWindow2;
The resulting window has button A fixed to the top left corner of the window, while it`s bottom edge is attached to button B and it`s right edge is attached such that its width is 75% of the window`s width. With these attachments button A will grow or shrink as appropriate when the window is resized. Also note the attachments on buttons D and E will align their left and right edges to the button above. Most of the formLayout attachment flags operate as you would expect them to. AttachOppositeForm and attachOppositeControl require some extra explanation. As child controls are added to the form they do not have a position but do have an order and thus an implied position relative to one another. In terms of the attachControl flag the "natural" place for the top of the second child to connect to is the bottom of the first child. The "natural" place for the left edge of the second child to connect to is the right edge of the first one. Thus the second child is, in a sense, right of and below the first, the third is right of and below the second and so on. Consider now that we want to make the second child attach beside the first one and that it must be a relative attachment so that child 2 will stay beside child 1 when the form`s size changes. The regular attachControl lets us connect the left of child 2 to the right edge of child 1, attachControl child2 "left" 0 child1; says to attach the left edge of child 2 to the edge of child 1 that is "nearest" in the left-right direction with a spacing offset of 0 pixels. Remembering that the implicit order has child 2 positioned immediately right of child 1 the "nearest" edge is then child 1`s right edge. But attachControl can`t make the top of child 2 line up with the top of child 1 because of the implied ordering that attachControl follows. That is when attachOppositeControl is used.
attachOppositeControl child2 "top" 0 child1;
says to attach the top edge of child 2 to the edge of child 1 farthest from child2, its top edge, with a spacing offset of 0 pixels. The form knows to place two objects with the same top edge position side by side.

window TestWindow3;
string $form = `formLayout`;
string $b1 = `button -label "AAAAAAAAA"`;
string $b2 = `button -label "BBBBB"`;
string $b3 = `button -label "CCCC"`;

formLayout -edit
-attachForm $b1 "top" 5
-attachForm $b1 "left" 5

-attachControl $b2 "top" 0 $b1
-attachControl $b2 "left" 5 $b1
-attachNone $b2 "right"
-attachNone $b2 "bottom"

-attachControl $b3 "top" 0 $b2
-attachControl $b3 "left" 0 $b2
-attachNone $b3 "right"
-attachNone $b3 "bottom"
$form;
showWindow TestWindow3;
Next we see the action of the attachOppositeControl flag on the position of the second button. Note that as long as the first button is attached to the top of the form this use of attachOppositeControl is the same as doing an attachForm $b2 "top". If button 1 was attached relative to a control that could move the use of attachOppositeControl here would be essential here.

window TestWindow4;
string $form = `formLayout`;
string $b1 = `button -label "AAAAAAAAA"`;
string $b2 = `button -label "BBBBB"`;
string $b3 = `button -label "CCCC"`;

formLayout -edit
-attachForm $b1 "top" 5
-attachForm $b1 "left" 5

-attachOppositeControl $b2 "top" 0 $b1
-attachControl $b2 "left" 5 $b1
-attachNone $b2 "right"
-attachNone $b2 "bottom"

-attachControl $b3 "top" 0 $b2
-attachControl $b3 "left" 5 $b2
-attachNone $b3 "right"
-attachNone $b3 "bottom"
$form;
showWindow TestWindow4;
Now we use attachOppositeControl in the other direction to make the third button fit directly under the second.

window TestWindow5;
string $form = `formLayout`;
string $b1 = `button -label "AAAAAAAAA"`;
string $b2 = `button -label "BBBBB"`;
string $b3 = `button -label "CCCC"`;

formLayout -edit
-attachForm $b1 "top" 5
-attachForm $b1 "left" 5

-attachOppositeControl $b2 "top" 0 $b1
-attachControl $b2 "left" 5 $b1
-attachNone $b2 "right"
-attachNone $b2 "bottom"

-attachControl $b3 "top" 0 $b2
-attachOppositeControl $b3 "left" 0 $b2
-attachNone $b3 "right"
-attachNone $b3 "bottom"
$form;
showWindow TestWindow5;
And to have the third button line up below the first and extend over as far as the left edge of the second we do the following:

window TestWindow6;
string $form = `formLayout`;
string $b1 = `button -label "AAAAAAAAA"`;
string $b2 = `button -label "BBBBB"`;
string $b3 = `button -label "CCCC"`;

formLayout -edit
-attachForm $b1 "top" 5
-attachForm $b1 "left" 5

-attachOppositeControl $b2 "top" 0 $b1
-attachControl $b2 "left" 5 $b1
-attachNone $b2 "right"
-attachNone $b2 "bottom"

-attachControl $b3 "top" 0 $b2
-attachForm $b3 "left" 5
-attachControl $b3 "right" 0 $b2
-attachNone $b3 "bottom"
$form;
showWindow TestWindow6;
Note that now that the "attachForm $b3 "left" 5" places button 3 to the left of button 2 the nearest side for the "-attachControl $b3 "right" 0 $b2" is now button 2`s left edge. And finally, we want the third button to run from the left edge of button 1 to the right edge of button 2.

window TestWindow7;
string $form = `formLayout`;
string $b1 = `button -label "AAAAAAAAA"`;
string $b2 = `button -label "BBBBB"`;
string $b3 = `button -label "CCCC"`;

formLayout -edit
-attachForm $b1 "top" 5
-attachForm $b1 "left" 5

-attachOppositeControl $b2 "top" 0 $b1
-attachControl $b2 "left" 5 $b1
-attachNone $b2 "right"
-attachNone $b2 "bottom"

-attachControl $b3 "top" 0 $b2
-attachOppositeControl $b3 "left" 0 $b1
-attachOppositeControl $b3 "right" 0 $b2
-attachNone $b3 "bottom"
$form;
showWindow TestWindow7;
UI to control node attrs

Here's a way to create a single slider:


window -title "Test" testWin;
columnLayout;
attrFieldSliderGrp -l "Test" -min 0 -max 100 -at persp.tx;
showWindow;

And a group of sliders:


global proc lightInten()
{
  string $lights[] = `ls -lt`;
  string $curLight;

  // Before anything else, return (leave!!) if a window 
  // already exists (because of having run this program 
  // previously) - we don't want two windows that contain 
  // the same UI doing the same thing.
  if (`window -exists lightWin`)
    return;// nothing to do

  // create a window, populate with sliders
  window -title "Light Intensities" lightWin; 
  columnLayout; // will arrange contents (widgets) below each other
  for($curLight in $lights)
    {
      $attr = $curLight + ".intensity"; // create attr string
      attrFieldSliderGrp -min 0 -max 2 -label $attr -at $attr; // creates a slider
    }

  // all done, display to the user
  showWindow lightWin;
}// lightInten()


Callback proc

A callback is simply a proc which gets "called back" when the UI element to which it is attached is activated by the user (eg. when a button is clicked).


window -title "CBTest" testWin;
columnLayout;
button -l "Cone" -w 50 -c "genCone" myButton;
showWindow;
If you run the above you'll get a UI, and when you click 'Cone' you'll get this error:
// Error: line 1: Cannot find procedure "genCone". //
This is because the genCone callback is not defined yet.

Here's the callback plus UI code:


global proc genCone()
{
 cone;
}

window -title "CBTest" testWin;
columnLayout;
button -l "Cone" -w 50 -c "genCone" myButton;
showWindow;

Note that all callbacks HAVE to be 'global'.

UI generators

It is an interesting exercise to write code to autogenerate UI code.

MELANIE - UI generator


MELWin - UI generator, freeware


uiBuilder generates a wrapper UI to collect params (inputs) to feed an existing proc.


guiBuilder is a more general purpose UI builder where you can visually compose a UI by dragging and dropping elements. The result is MEL UI code which you can further edit by hand if you like.

The 'webBrowser' window (new, as of Maya6)

A 'webBrowser' window can be used to bring up a web page (eg. containing doc. for your UI):


window;
columnLayout;
$browser = `webBrowser -width 800 -height 600 -url "www.smartcg.com/tech/cg/courses/MEL2"`;
showWindow;

// Here's how to switch to a new URL:
webBrowser -edit -url "www.cnn.com" $browser;

This is from Maya's documentation: Maya Web Browser is a Maya panel you can use to browse Web content. Like any other Maya panel, it can be placed in the main Maya window alongside other panels, and can be torn off into a separate window. You can also use the Maya Browser to communicate with Maya using MEL commands. To issue a MEL command from the Maya Browser, precede it with  “mel://”. By adding MEL links to HTML and JavaScript code, you can design your own Web interface for Maya.


You can wrap the raw webBrowser invocation into a little launcher program.

Here is a basic HTML file that shows a mel:// call. <a href="mel://sphere -r 2">Sphere, radius 2 </a> sets up a 'Sphere, radius 2' hyperlink, which when clicked, creates in Maya a sphere of radius 2! Run the above wB launcher program, then enter the URL (local, if you've downloaded it) for wBTest.html, which is http://www.smartcg.com/tech/cg/courses/MEL2/notes/Class04/src/wBTest.html. When the browser launches, you should be able to click on 'Sphere, radius 2' link and see a sphere generated in Maya.

How would you embed double quotes inside a mel:// link (double quotes inside double quotes)? Eg. what if you want to create a link such as this:
<a href="mel://string $s = "MEL";"> create string var $s, value "MEL"</a>
The standard solution of 'escaping' (preceding with a \) the inner double quotes won't work. To specify a double quote character inside a hyperlink, you'd denote it as &quot; (this is as per HTML standards). So the HTML for the above example will look like this:
<a href="mel://string $s = &quot;MEL&quot;;"> create string var $s, value "MEL"</a>

You can get MUCH MORE creative than this and design fancy UIs using JavaScript, Flash etc. to send mel:// commands to Maya. This could lead to 'literate' UIs that freely mix documentation and code for the purposes of learning and annotation. Applications include interactive tutorials, 'live' documents (eg. a research thesis with embedded mel:// code), heavily annotated 'cheat' sheets, self-documenting UIs to control scene elements, image-based interfaces (eg. for character rigs), scene/asset managers, etc.