TUTORIAL - Image Map Based GUI

I have been asked on many occaisons to explain how to make an image map based GUI, utilizing MEL's web browser control. I decided it would be a good idea to just write up a tutorial. (Bear with me, this is my first tutorial. I'll try to make it as clear as possible.) This tutorial assumes that you already have a character rigged with curve based controller objects. (These scripts were written for Maya 7.0 and might not work in the latest releases.)
Step 1 - Create Image

This is the easy step. Just take a render of your character and add labels for selection. Make sure that you only use rectangles and circles. (I use rectangles for FK controls and circles for IK controls. Spelly here has FK only legs.)

This image is smaller than the actual GUI image, where the text labels are actually readable.

Step 2 - Create HTML "Page"

I use Macromedia Dreamweaver to create the image map code. That way I can draw the selection boxes and circles right on the image.

 

Otherwise, the html for the image map looks like this.

<img src="spelly-gui.jpg" width="300" height="437" border="0" usemap="#Map">
<map name="Map">
<area shape="rect" coords="111,51,165,71" href="mel://spellySelect(%22ctrHead%22,1);">
<area shape="rect" coords="104,79,160,93" href="mel://spellySelect(%22ctrLookAt%22,0);">
...

</map>

Note that the link format is "mel://melCommand();" instead of http:// etc... This is the key to using html to drive MEL scripts.

 

The command we're using for the GUI is a custom selection script which we will write later. The format is...

characterSelect("controlName", $tool);

character = character name, used as a unique command prefix
controlName = name of the control object to be selected
$tool = 0 for "no change", 1 for "rotate", and 2 for "move" (This will be explained in more detail later, just use 1 for rotation only controls, 2 for translation only controls, and 0 for controls that can be both rotated and moved.)

If you're writing the html directly, remember to use "%22" in place of the quotation marks.

Finally, we need to make some changes to the html code that Dreamweaver spits out. (This is to ensure that there is no empty space around the image.) Open up the html file in a text editor and select and copy the entire image and image map block. (From "<img src=..." to "</map>".)

<html>
<head>
<title>Character GUI</title>
</head><body>
<div id="Layer1" style="position:absolute; width:300px; height:437px; z-index:1; top: 0px; left: 0px;">
INSERT IMAGE & IMAGE MAP BLOCK HERE
</div>
</body>
</html>

I'm not 100% sure what the <div> tag does... I found it in an example and just suited it to my needs.


Step 3 - Create MEL Script

Now for the FUN part. We will need to write the following MEL procedures. Replace "character" with the unique name of the character the GUI is for. ("CHARACTER" in all caps should be replaced with a UNIQUE transform node name in the character's heirarchy, most likely the "top node".)

global proc characterGUI()
 {
  //loads the GUI
 }

global proc characterGUIWin()
 {
  //builds the GUI window
 }

global proc characterLocateGUI()
 {
  //prompts the user to locate the HTML file
 }

global proc characterLocateFile()
 {
  //loads HTML file location into global variable
 }

global proc characterGetNameSpace()
 {
  //finds character namespace (if present) and loads into global variable
 }

global proc characterSelect(string $control, int $tool)
 {
  //selects desired control and sets proper tool
 }

characterGUI
This procedure will check for namespace and GUI file location.
If the namespace is not loaded, it will call "characterGetNameSpace".
If the GUI file location is not loaded, it will call "characterLocateGUI".
Finally, it will call "characterGUIWin" and load the window.

global proc characterGUI()
 {
  //loads the GUI

  //declare global variables to hold namespace & file location
  global string $characterNameSpace;
  global string $characterGUIlocation;

  string $chNode = "CHARACTER"; //unique name of character's "Top Node"

  //check for namespace
  if (!(`objExists ($characterNameSpace + $chNode)`))
   characterGetNameSpace;

  //check for file location
  if ($characterGUIlocation == "")
   characterLocateGUI;
  else
   characterGUIWin;
 }

characterGUIWin
This procedure builds the GUI window. Easy.

global proc characterGUIWin()
 {
  //builds the GUI window

  global string $characterGUIlocation;

  //delete's the GUI widow if it's already open
  if (`window -exists "characterGUI"`)
   deleteUI "characterGUI";

  window -t "Character GUI" -w 300 -h 437 -rtf 1 "characterGUI";

  columnLayout -w 300;
   paneLayout -w 300 -h 437;
    //width and height should match image
    webBrowser -width 300 -height 437 -url ("file:///" + $characterGUIlocation);
    setParent ..;
   setParent ..;

  showWindow "characterGUI";
 }

characterLocateGUI
This procedure loads up a window to prompt the user to locate the html file.

global proc characterLocateGUI()
 {
  //prompts the user to locate the HTML file

  //delete the window if for some crazy reason, it exists
  if (`window -exists "characterFindWin"`)
   deleteUI "characterFindWin";

  window -w 250 -t "Locate CharacterGUI" -rtf 1 "characterFindWin";

  columnLayout -w 250;
   //button calls spellyLocateFile and closes the window
   button -w 250 -l "Locate characterGUI.htm"
    -c "characterLocateFile; deleteUI \"characterFindWin\";";
   setParent ..;

  showWindow "characterFindWin";
 }

characterLocateFile
This procedure presents a file dialog box to the user and loads the selection into a global variable.

global proc characterLocateFile()
 {
  //loads HTML file location into global variable

  global string $characterGUIlocation;

  $characterGUIlocation = `fileDialog -dm "characterGUI.htm"`;
  characterGUIWin;
 }

characterGetNameSpace
This procedure identifies the namespace (if any) when a character is imported or referenced.

global proc characterGetNameSpace()
 {
  //finds character namespace (if present) and loads into global variable

  global string $characterNameSpace;

  string $chNode = "CHARACTER"; //the unique name of the character's "top node"
  string $ns;

  string $lsBuffer[];
  string $tokBuffer[];

  if (`objExists $chNode`) //sets namespace as blank if no namespace is present
   {
    $ns = "";
   }
  else //searches through every transform node until it finds the "top node"
   {
    $lsBuffer = `lsType "transform"`;
    for ($i=0; $i<(size($lsBuffer)); $i++)
     {
      tokenize $lsBuffer[$i] ":" $tokBuffer;
      for ($j=1; $j<(size($tokBuffer)); $j++)
       {
        if ($tokBuffer[$j] == $chNode)
         {
          $ns = ($tokBuffer[0] + ":");
         }
       }
     }
   }

  $characterNameSpace = $ns;
 }

characterSelect
This procedure selects the desired control (with namespace) and changes current tool if appropriate.

global proc characterSelect(string $control, int $tool)
 {
  //selects desired control and sets proper tool

  global string $characterNameSpace;

  string $ns = $characterNameSpace;

  string $ctxMove = `manipMoveContext`;
  string $ctxRot = `manipRotateContext`;

  if (!(`objExists ($ns + "CHARACTER")`)) characterGetNameSpace;

  select -r ($ns + $control);

  if ($tool == 1) setToolTo $ctxRot;
  else if ($tool == 2) setToolTo $ctxMove;
 }

Step 4 - Add Script Node

First test the script as a normal MEL file in your default scripts directory. Once you've got all the typos and bugs worked out, we'll want to embed the MEL into the scene file as a "Scipt Node". This way, as the character gets moved from machine to machine, you never have to worry about puttung the mel file into the scripts folder on each machine you use. (It's bad enough we have to keep track of the html and jpg files, but at least those can stay in the character project folder.) (If this is not important to you, you can skip this step and simply leave the MEL file in the scripts folder.)

When you're done testing, remove the MEL file from the scripts folder AND the project folder. (If the file is in either of these locations, Maya MAY use them instead of the script node, and this can be a problem if you make changes to the scripts later.)

Open the expression editor and change the "Select Filter" to "By Script Node Name".

Click [New Script Node] and type the name of the node in the "Script Node Name" box. (I use the format scriptCharacterGUI.)

Copy the MEL script file from a text editor and paste it into the "Script" box. Then click [Create].

Change the "Execute On" option to "GUI Open/Close". This tells Maya to execute the script when the file is loaded. (Since the script contains procedure definitions, this just initializes the script so that it is availiable to be used, not actually executing it yet.)

Click [Test Script] to initialize the procedures, then click [Close].

Now type the main script name in Maya's command line (characterGUI). Test everything to make sure it's working, then save and restart Maya, load the character, and test everything again to make sure the script loaded when the character was opened.
Step 5 - The End?
There are many more features that can be added to your GUI, like IK/FK switching/snapping controls, or an object in the scene that launches the GUI automatically when selected. But these will be covered in another tutorial.